import html import json import math from urllib.parse import quote, urlencode from fastapi import APIRouter, Depends, HTTPException, Query, 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() DEFAULT_PAGE_SIZE = 20 def render_worker_badge(online: bool) -> str: if online: return 'online' return 'offline' 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, status: str = Query(""), q: str = Query(""), page_number: int = Query(1, alias="page", ge=1), user=Depends(require_user), ): all_workers = get_workers() selected_status = status.strip().lower() query = q.strip() online_count = sum(1 for worker in all_workers if worker.get("online")) total_count = len(all_workers) offline_count = total_count - online_count workers = all_workers if selected_status == "online": workers = [worker for worker in workers if worker.get("online")] elif selected_status == "offline": workers = [worker for worker in workers if not worker.get("online")] else: selected_status = "" if query: workers = [worker for worker in workers if query.lower() in (worker.get("id", "") or "").lower()] total_filtered = len(workers) total_pages = max(1, math.ceil(total_filtered / DEFAULT_PAGE_SIZE)) if page_number > total_pages: page_number = total_pages offset = (page_number - 1) * DEFAULT_PAGE_SIZE workers = workers[offset : offset + DEFAULT_PAGE_SIZE] 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'#{current_job_label}' rows += f""" {worker_id} {badge}
{status} {last_seen_at} {current_job} {started_at} Detail """ if not rows: rows = 'Zatím nejsou evidovaní žádní workeři.' live_table_attr = ' data-live-table="workers.recent"' if not selected_status and not query and page_number == 1 else "" first_item = offset + 1 if total_filtered else 0 last_item = min(offset + len(workers), total_filtered) def page_url(page: int) -> str: params = {"page": page} if selected_status: params["status"] = selected_status if query: params["q"] = query return f"/portal/workers?{urlencode(params)}" pagination = f""" """ status_options = "".join( f'' for value, label in [("", "Všichni workeři"), ("online", "Online"), ("offline", "Offline")] ) return page( "Workery", f"""

Workery

Přehled worker procesů obsluhujících úlohy portálu.

Online workery{online_count}
Offline workery{offline_count}
Celkem workerů{total_count}

Filtry

Reset

Workery

{pagination} {rows}
ID workeru Status Naposledy viděn Aktuální úloha Spuštěn Akce
{pagination}
""", 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'#{current_job_label}' metadata = html.escape(pretty_json(worker.get("metadata_json"))) metadata_block = "" if worker.get("metadata_json"): metadata_block = f"""

Metadata

{metadata}
""" return page( f"Worker {worker_id_html}", f"""

Worker {worker_id_html}

← Zpět na workery

Souhrn

ID workeru{worker_id_html}
Status{badge}
{status}
Naposledy viděn{last_seen_at}
Aktuální úloha{current_job}
Spuštěn{started_at}
{metadata_block} """, user=user, )