added jobs handling.

This commit is contained in:
JiriUhlir
2026-05-29 09:16:37 +02:00
parent 88601451dc
commit f651f7efd6
2 changed files with 203 additions and 4 deletions
+88 -4
View File
@@ -3,20 +3,36 @@ import json
from urllib.parse import quote
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.responses import HTMLResponse, RedirectResponse
from app.auth import require_user
from app.db.jobs import get_job, get_job_logs, get_jobs
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.routes.deployments import calculate_duration, render_status_pill
from app.templates.layout import page
router = APIRouter()
LIVE_STATUSES = {"queued", "running"}
LIVE_STATUSES = {"queued", "running", "cancelled_requested"}
JOB_STATUSES = {"queued", "running", "cancelled_requested", "cancelled", "success", "failed"}
def render_job_status(status: str | None) -> str:
return render_status_pill(status)
status_value = status or ""
normalized = status_value.lower()
if normalized not in JOB_STATUSES:
return render_status_pill(status)
if normalized == "success":
class_name = "pill pill-success"
elif normalized in {"queued", "running", "cancelled_requested"}:
class_name = "pill pill-warning"
elif normalized in {"failed", "cancelled"}:
class_name = "pill pill-danger"
else:
class_name = "pill pill-muted"
return f'<span class="{class_name}">{html.escape(status_value)}</span>'
def pretty_json(value: str | None) -> str:
@@ -119,6 +135,25 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)):
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 "")
actions = ""
if status_value == "failed":
actions += f"""
<form method="post" action="/portal/jobs/{html.escape(str(job_id))}/retry" onsubmit="return confirm('Retry job #{html.escape(str(job_id))}?');">
<button type="submit">Retry Job</button>
</form>
"""
if status_value in {"queued", "running"}:
actions += f"""
<form method="post" action="/portal/jobs/{html.escape(str(job_id))}/cancel" onsubmit="return confirm('Cancel job #{html.escape(str(job_id))}?');">
<button type="submit" class="danger">Cancel Job</button>
</form>
"""
if actions:
actions = f"""
<div class="inline-form">
{actions}
</div>
"""
log_blocks = ""
for log in get_job_logs(job_id):
@@ -146,6 +181,7 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)):
<a class="btn" href="/portal/jobs">&larr; Zpět na joby</a>
<a class="btn btn-secondary" href="{target_url}">Detail targetu</a>
</p>
{actions}
</div>
<div class="card">
@@ -183,3 +219,51 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)):
""",
user=user,
)
@router.post("/jobs/{job_id}/retry")
def retry_job_action(job_id: int, user=Depends(require_user)):
job = get_job(job_id)
if not job:
raise HTTPException(status_code=404, detail="Job not found")
if (job.get("status") or "").lower() != "failed":
raise HTTPException(status_code=400, detail="Only failed jobs can be retried")
new_job_id = retry_failed_job(job_id, user)
if not new_job_id:
raise HTTPException(status_code=409, detail="Job can no longer be retried")
log_audit_event(
user,
action="job.retry",
target_type="job",
target_id=job_id,
metadata={"new_job_id": new_job_id},
)
return RedirectResponse(url=f"/portal/jobs/{new_job_id}", status_code=303)
@router.post("/jobs/{job_id}/cancel")
def cancel_job_action(job_id: int, 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()
if status_value not in {"queued", "running"}:
raise HTTPException(status_code=400, detail="Only queued or running jobs can be cancelled")
result = cancel_job(job_id)
if not result:
raise HTTPException(status_code=409, detail="Job can no longer be cancelled")
log_audit_event(
user,
action="job.cancel",
target_type="job",
target_id=job_id,
metadata=result,
)
return RedirectResponse(url=f"/portal/jobs/{job_id}", status_code=303)