styles combine

This commit is contained in:
JiriUhlir
2026-05-28 10:02:25 +02:00
parent 198f68381e
commit 614ccb5e71
4 changed files with 122 additions and 19 deletions
+5
View File
@@ -125,6 +125,11 @@ def index():
<p class="muted">Vytváření záloh a kopírování příkazů pro obnovu. Obnova je záměrně ruční a chráněná.</p> <p class="muted">Vytváření záloh a kopírování příkazů pro obnovu. Obnova je záměrně ruční a chráněná.</p>
<a class="btn" href="/portal/backups">Spravovat zálohy</a> <a class="btn" href="/portal/backups">Spravovat zálohy</a>
</div> </div>
<div class="card">
<h2>Nasazení</h2>
<p class="muted">Historie posledních běhů nasazení, stavů a výstupů z deploy procesu.</p>
<a class="btn" href="/portal/deployments">Zobrazit nasazení</a>
</div>
</div> </div>
<div class="card"> <div class="card">
+103 -18
View File
@@ -1,30 +1,79 @@
from fastapi import APIRouter, HTTPException, Request import html
from fastapi import APIRouter, HTTPException
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from app.db.apps import get_deployment, get_deployments from app.db.apps import get_deployment, get_deployments
from app.templates.layout import page
router = APIRouter() router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@router.get("/deployments", response_class=HTMLResponse) @router.get("/deployments", response_class=HTMLResponse)
async def deployments_page(request: Request): async def deployments_page():
deployments = get_deployments() deployments = get_deployments()
return templates.TemplateResponse( rows = ""
request, for deployment in deployments:
"deployments.html", deployment_id = html.escape(str(deployment.get("id", "")))
{ app_id = html.escape(deployment.get("app_id", "") or "")
"deployments": deployments, kind = html.escape(deployment.get("kind", "") or "")
}, status = html.escape(deployment.get("status", "") or "")
started_at = html.escape(deployment.get("started_at", "") or "")
finished_at = html.escape(deployment.get("finished_at", "") or "")
returncode = deployment.get("returncode")
returncode_label = "" if returncode is None else html.escape(str(returncode))
rows += f"""
<tr>
<td>
<strong>#{deployment_id}</strong><br>
<span class="muted">{started_at}</span>
</td>
<td>{app_id}</td>
<td>{kind}</td>
<td><span class="pill">{status}</span></td>
<td>{returncode_label}</td>
<td>{finished_at}</td>
<td class="actions-cell">
<a class="btn" href="/portal/deployments/{deployment_id}">Detail</a>
</td>
</tr>
"""
if not rows:
rows = '<tr><td colspan="7">Zatím nejsou evidovaná žádná nasazení.</td></tr>'
return page(
"Nasazení",
f"""
<div class="card">
<h2>Historie nasazení</h2>
<p class="muted">Přehled posledních běhů nasazení a jejich výsledků.</p>
<a class="btn" href="/portal">&larr; Zpět na portál</a>
</div>
<div class="card">
<h2>Nasazení</h2>
<table>
<tr>
<th>ID</th>
<th>Aplikace</th>
<th>Typ</th>
<th>Status</th>
<th>Kód</th>
<th>Dokončeno</th>
<th>Akce</th>
</tr>
{rows}
</table>
</div>
""",
) )
@router.get("/deployments/{deployment_id}", response_class=HTMLResponse) @router.get("/deployments/{deployment_id}", response_class=HTMLResponse)
async def deployment_detail_page( async def deployment_detail_page(
request: Request,
deployment_id: int, deployment_id: int,
): ):
deployment = get_deployment(deployment_id) deployment = get_deployment(deployment_id)
@@ -32,10 +81,46 @@ async def deployment_detail_page(
if not deployment: if not deployment:
raise HTTPException(status_code=404, detail="Deployment not found") raise HTTPException(status_code=404, detail="Deployment not found")
return templates.TemplateResponse( title = f"Nasazení #{html.escape(str(deployment.get('id', deployment_id)))}"
request, app_id = html.escape(deployment.get("app_id", "") or "")
"deployment-detail.html", kind = html.escape(deployment.get("kind", "") or "")
{ status = html.escape(deployment.get("status", "") or "")
"deployment": deployment, started_at = html.escape(deployment.get("started_at", "") or "")
}, finished_at = html.escape(deployment.get("finished_at", "") or "")
returncode = deployment.get("returncode")
returncode_label = "" if returncode is None else html.escape(str(returncode))
stdout = html.escape(deployment.get("stdout", "") or "")
stderr = html.escape(deployment.get("stderr", "") or "")
return page(
title,
f"""
<div class="card">
<h2>{title}</h2>
<p class="muted">Detail běhu nasazení včetně výstupu procesu.</p>
<p>
<a class="btn" href="/portal/deployments">&larr; Zpět na nasazení</a>
<a class="btn btn-secondary" href="/portal">Zpět na portál</a>
</p>
</div>
<div class="card">
<h2>Souhrn</h2>
<table>
<tr><th>Aplikace</th><td>{app_id}</td></tr>
<tr><th>Typ</th><td>{kind}</td></tr>
<tr><th>Status</th><td><span class="pill">{status}</span></td></tr>
<tr><th>Návratový kód</th><td>{returncode_label}</td></tr>
<tr><th>Spuštěno</th><td>{started_at}</td></tr>
<tr><th>Dokončeno</th><td>{finished_at}</td></tr>
</table>
</div>
<div class="card">
<h2>Výstup</h2>
<pre>{stdout}</pre>
<h2>Chyba</h2>
<pre>{stderr}</pre>
</div>
""",
) )
+12
View File
@@ -149,6 +149,18 @@ button:hover,
text-decoration: none; text-decoration: none;
} }
.btn + .btn {
margin-left: 8px;
}
.btn-secondary {
background: var(--secondary);
}
.btn-secondary:hover {
background: var(--secondary-dark);
}
.danger { .danger {
background: var(--danger); background: var(--danger);
} }
+1
View File
@@ -20,6 +20,7 @@ def page(title: str, body: str) -> str:
<nav> <nav>
<a href="/portal">Aplikace</a> <a href="/portal">Aplikace</a>
<a href="/portal/new-app">Nová aplikace</a> <a href="/portal/new-app">Nová aplikace</a>
<a href="/portal/deployments">Nasazení</a>
<a href="/portal/backups">Zálohy</a> <a href="/portal/backups">Zálohy</a>
</nav> </nav>
</header> </header>