import html import json from urllib.parse import quote from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse from app.auth import require_user from app.db.jobs import get_job, get_job_logs, get_jobs from app.routes.deployments import calculate_duration, render_status_pill from app.templates.layout import page router = APIRouter() LIVE_STATUSES = {"queued", "running"} def render_job_status(status: str | None) -> str: return render_status_pill(status) def pretty_json(value: str | None) -> str: if not value: return "{}" try: return json.dumps(json.loads(value), ensure_ascii=False, indent=2, sort_keys=True) except (TypeError, ValueError): return value @router.get("/jobs", response_class=HTMLResponse) def jobs_page(request: Request, user=Depends(require_user)): jobs = get_jobs() refresh = "" if any( (job.get("status") or "").lower() in LIVE_STATUSES for job in jobs ) else "" rows = "" for job in jobs: job_id = html.escape(str(job.get("id", ""))) status = render_job_status(job.get("status")) source = html.escape(job.get("source", "") or "") target_type = html.escape(job.get("target_type", "") or "") target_id = html.escape(job.get("target_id", "") or "") job_type = html.escape(job.get("type", "") or "") created_by = html.escape(job.get("created_by_display_name") or job.get("created_by_username") or "") created_at = html.escape(job.get("created_at", "") or "") started_at = html.escape(job.get("started_at", "") or "") finished_at = html.escape(job.get("finished_at", "") or "") row_class = ' class="running-row"' if (job.get("status") or "").lower() == "running" else "" rows += f""" #{job_id}
{job_type} {status} {source} {target_type}: {target_id} {created_by} {created_at} {started_at} {finished_at} Detail """ if not rows: rows = 'Zatím nejsou evidované žádné joby.' return page( "Joby", f""" {refresh}

Joby

Fronta portálových a webhook úloh připravená pro centrální worker.

Fronta

{rows}
ID Status Source Target Created by Created at Started at Finished at Akce
""", user=user, ) @router.get("/jobs/{job_id}", response_class=HTMLResponse) def job_detail_page(job_id: int, request: Request, user=Depends(require_user)): job = get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") status_value = (job.get("status") or "").lower() refresh = "" if status_value in LIVE_STATUSES else "" title = f"Job #{html.escape(str(job.get('id', job_id)))}" status = render_job_status(job.get("status")) target_type = job.get("target_type", "") or "" target_id = job.get("target_id", "") or "" target_id_html = html.escape(target_id) target_url = f"/portal/apps/{quote(target_id, safe='')}" if target_type == "app" else "/portal/jobs" duration = html.escape(calculate_duration(job.get("started_at"), job.get("finished_at"))) payload = html.escape(pretty_json(job.get("payload_json"))) result = html.escape(pretty_json(job.get("result_json"))) error_text = html.escape(job.get("error_text", "") or "") log_blocks = "" for log in get_job_logs(job_id): stream = html.escape(log.get("stream", "") or "system") created_at = html.escape(log.get("created_at", "") or "") message = html.escape(log.get("message", "") or "") class_name = "log-stderr" if stream == "stderr" else "log-stdout" log_blocks += f"""
{created_at} · {stream}
{message}
""" if not log_blocks: log_blocks = '

Zatím nejsou uložené žádné logy.

' return page( title, f""" {refresh}

{title}

← Zpět na joby Detail targetu

Souhrn

Typ{html.escape(job.get("type", "") or "")}
Status{status}
Source{html.escape(job.get("source", "") or "")}
Target{html.escape(target_type)}: {target_id_html}
Created by{html.escape(job.get("created_by_display_name") or job.get("created_by_username") or "")}
Created at{html.escape(job.get("created_at", "") or "")}
Started at{html.escape(job.get("started_at", "") or "")}
Finished at{html.escape(job.get("finished_at", "") or "")}
Duration{duration}
Worker{html.escape(job.get("worker_id", "") or "")}

Payload

{payload}

Result / error

{result}
{error_text}

Logy

{log_blocks}
""", user=user, )