filtry v jobs a shrnuti

This commit is contained in:
JiriUhlir
2026-05-29 09:23:36 +02:00
parent f651f7efd6
commit 4b4bf55696
2 changed files with 99 additions and 7 deletions
+56 -4
View File
@@ -2,12 +2,12 @@ import html
import json
from urllib.parse import quote
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from app.auth import require_user
from app.db.audit import log_audit_event
from app.db.jobs import cancel_job, get_job, get_job_logs, get_jobs, retry_failed_job
from app.db.jobs import cancel_job, get_job, get_job_logs, get_job_stats, get_jobs, retry_failed_job
from app.routes.deployments import calculate_duration, render_status_pill
from app.templates.layout import page
@@ -15,6 +15,8 @@ router = APIRouter()
LIVE_STATUSES = {"queued", "running", "cancelled_requested"}
JOB_STATUSES = {"queued", "running", "cancelled_requested", "cancelled", "success", "failed"}
JOB_FILTER_STATUSES = ("queued", "running", "success", "failed", "cancelled")
JOB_FILTER_TYPES = ("deploy_app", "deploy_core_service")
def render_job_status(status: str | None) -> str:
@@ -45,9 +47,37 @@ def pretty_json(value: str | None) -> str:
return value
def render_options(values: tuple[str, ...], selected: str, empty_label: str) -> str:
options = [f'<option value="">{html.escape(empty_label)}</option>']
for value in values:
selected_attr = " selected" if selected == value else ""
escaped_value = html.escape(value)
options.append(f'<option value="{escaped_value}"{selected_attr}>{escaped_value}</option>')
return "".join(options)
@router.get("/jobs", response_class=HTMLResponse)
def jobs_page(request: Request, user=Depends(require_user)):
jobs = get_jobs()
def jobs_page(
request: Request,
status: str = Query(""),
job_type: str = Query("", alias="type"),
target: str = Query(""),
user=Depends(require_user),
):
selected_status = status.strip().lower()
selected_type = job_type.strip()
selected_target = target.strip()
if selected_status not in JOB_FILTER_STATUSES:
selected_status = ""
if selected_type not in JOB_FILTER_TYPES:
selected_type = ""
jobs = get_jobs(
status=selected_status or None,
job_type=selected_type or None,
target=selected_target or None,
)
stats = get_job_stats()
refresh = "<script>setTimeout(() => window.location.reload(), 5000);</script>" if any(
(job.get("status") or "").lower() in LIVE_STATUSES for job in jobs
) else ""
@@ -86,6 +116,10 @@ def jobs_page(request: Request, user=Depends(require_user)):
if not rows:
rows = '<tr><td colspan="9">Zatím nejsou evidované žádné joby.</td></tr>'
status_options = render_options(JOB_FILTER_STATUSES, selected_status, "Všechny statusy")
type_options = render_options(JOB_FILTER_TYPES, selected_type, "Všechny typy")
target_value = html.escape(selected_target)
return page(
"Joby",
f"""
@@ -95,6 +129,24 @@ def jobs_page(request: Request, user=Depends(require_user)):
<p class="muted">Fronta portálových a webhook úloh připravená pro centrální worker.</p>
</div>
<div class="stats-grid">
<div class="stat-card stat-warning"><span>Queued</span><strong>{stats.get("queued") or 0}</strong></div>
<div class="stat-card stat-warning"><span>Running</span><strong>{stats.get("running") or 0}</strong></div>
<div class="stat-card stat-danger"><span>Failed</span><strong>{stats.get("failed") or 0}</strong></div>
<div class="stat-card stat-success"><span>Success</span><strong>{stats.get("success") or 0}</strong></div>
</div>
<div class="card">
<h2>Filtry</h2>
<form method="get" action="/portal/jobs" class="filter-form">
<select name="status">{status_options}</select>
<select name="type">{type_options}</select>
<input name="target" value="{target_value}" placeholder="Target">
<button type="submit">Filtrovat</button>
<a class="btn btn-secondary" href="/portal/jobs">Reset</a>
</form>
</div>
<div class="card">
<h2>Fronta</h2>
<table>