Files
appfactory-portal/app/routes/workers.py
T
2026-05-29 10:40:15 +02:00

173 lines
5.6 KiB
Python

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.audit import log_audit_event
from app.db.workers import get_worker, get_workers
from app.templates.layout import page
router = APIRouter()
def render_worker_badge(online: bool) -> str:
if online:
return '<span class="pill pill-success">ONLINE</span>'
return '<span class="pill pill-danger">OFFLINE</span>'
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("/workers", response_class=HTMLResponse)
def workers_page(request: Request, user=Depends(require_user)):
workers = get_workers()
online_count = sum(1 for worker in workers if worker.get("online"))
total_count = len(workers)
offline_count = total_count - online_count
log_audit_event(
user,
action="workers.view",
target_type="workers",
metadata={
"online": online_count,
"offline": offline_count,
"total": total_count,
},
)
rows = ""
for worker in workers:
worker_id_raw = worker.get("id", "") or ""
worker_id = html.escape(worker_id_raw)
worker_url_id = quote(worker_id_raw, safe="")
status = html.escape(worker.get("status", "") or "")
last_seen_at = html.escape(worker.get("last_seen_at", "") or "")
started_at = html.escape(worker.get("started_at", "") or "")
badge = render_worker_badge(bool(worker.get("online")))
current_job_id = worker.get("current_job_id")
current_job = ""
if current_job_id is not None:
current_job_label = html.escape(str(current_job_id))
current_job = f'<a href="/portal/jobs/{current_job_label}">#{current_job_label}</a>'
rows += f"""
<tr>
<td>
<strong><a href="/portal/workers/{worker_url_id}">{worker_id}</a></strong>
</td>
<td>{badge}<br><span class="muted">{status}</span></td>
<td>{last_seen_at}</td>
<td>{current_job}</td>
<td>{started_at}</td>
<td class="actions-cell"><a class="btn" href="/portal/workers/{worker_url_id}">Detail</a></td>
</tr>
"""
if not rows:
rows = '<tr><td colspan="6">Zatím nejsou evidovaní žádní workeři.</td></tr>'
return page(
"Workers",
f"""
<div class="card">
<h2>Workers</h2>
<p class="muted">Přehled worker procesů obsluhujících AppFactory joby.</p>
</div>
<div class="stats-grid">
<div class="stat-card stat-success"><span>Online Workers</span><strong>{online_count}</strong></div>
<div class="stat-card stat-danger"><span>Offline Workers</span><strong>{offline_count}</strong></div>
<div class="stat-card stat-total"><span>Total Workers</span><strong>{total_count}</strong></div>
</div>
<div class="card">
<h2>Workers</h2>
<table>
<tr>
<th>Worker ID</th>
<th>Status</th>
<th>Last Seen</th>
<th>Current Job</th>
<th>Started At</th>
<th>Akce</th>
</tr>
{rows}
</table>
</div>
""",
user=user,
)
@router.get("/workers/{worker_id}", response_class=HTMLResponse)
def worker_detail_page(worker_id: str, request: Request, user=Depends(require_user)):
worker = get_worker(worker_id)
if not worker:
raise HTTPException(status_code=404, detail="Worker not found")
log_audit_event(
user,
action="workers.view",
target_type="worker",
target_id=worker_id,
)
worker_id_html = html.escape(worker.get("id", "") or "")
status = html.escape(worker.get("status", "") or "")
last_seen_at = html.escape(worker.get("last_seen_at", "") or "")
started_at = html.escape(worker.get("started_at", "") or "")
badge = render_worker_badge(bool(worker.get("online")))
current_job_id = worker.get("current_job_id")
current_job = ""
if current_job_id is not None:
current_job_label = html.escape(str(current_job_id))
current_job = f'<a href="/portal/jobs/{current_job_label}">#{current_job_label}</a>'
metadata = html.escape(pretty_json(worker.get("metadata_json")))
metadata_block = ""
if worker.get("metadata_json"):
metadata_block = f"""
<div class="card">
<h2>Metadata</h2>
<pre class="log-viewer log-stdout">{metadata}</pre>
</div>
"""
return page(
f"Worker {worker_id_html}",
f"""
<div class="card">
<h2>Worker {worker_id_html}</h2>
<p>
<a class="btn" href="/portal/workers">&larr; Zpět na workers</a>
</p>
</div>
<div class="card">
<h2>Souhrn</h2>
<table>
<tr><th>Worker ID</th><td>{worker_id_html}</td></tr>
<tr><th>Status</th><td>{badge}<br><span class="muted">{status}</span></td></tr>
<tr><th>Last Seen</th><td>{last_seen_at}</td></tr>
<tr><th>Current Job</th><td>{current_job}</td></tr>
<tr><th>Started At</th><td>{started_at}</td></tr>
</table>
</div>
{metadata_block}
""",
user=user,
)