Add HTML5 operations monitor
This commit is contained in:
@@ -70,6 +70,54 @@ def render_html5_project_rows(projects: Iterable[object]) -> str:
|
||||
return project_rows
|
||||
|
||||
|
||||
def render_html5_operations(jobs: Iterable[object]) -> str:
|
||||
job_list = list(jobs)
|
||||
return _page(
|
||||
"SFERA HTML5 operations",
|
||||
f"""
|
||||
<main class="shell" data-html5-page="operations">
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">SFERA HTML5</p>
|
||||
<h1>Операции сервера</h1>
|
||||
<p class="lead">Очередь фоновых задач отрисовывается API-сервером и обновляется htmx polling без React runtime.</p>
|
||||
</div>
|
||||
<div class="hero-metrics">
|
||||
<strong>{len(job_list)}</strong>
|
||||
<span>jobs</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="band">
|
||||
<div class="section-title">
|
||||
<h2>Очередь</h2>
|
||||
<a class="button" href="/html5">Проекты</a>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table data-html5-operations>
|
||||
<thead>
|
||||
<tr><th>Job</th><th>Проект</th><th>Статус</th><th>Stage</th><th>Сообщение</th></tr>
|
||||
</thead>
|
||||
<tbody
|
||||
data-html5-operations-body
|
||||
hx-get="/html5/operations/jobs"
|
||||
hx-trigger="every 3s"
|
||||
hx-swap="innerHTML"
|
||||
>{render_html5_operation_rows(job_list)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def render_html5_operation_rows(jobs: Iterable[object]) -> str:
|
||||
rows = "\n".join(_operation_row(job) for job in jobs)
|
||||
if not rows:
|
||||
return '<tr><td colspan="5" class="muted">Фоновые операции пока не запускались</td></tr>'
|
||||
return rows
|
||||
|
||||
|
||||
def render_html5_editor(
|
||||
*,
|
||||
project_id: str,
|
||||
@@ -474,6 +522,29 @@ def _project_link(project: object, active_project_id: str) -> str:
|
||||
return f'<a class="project-link{active}" href="/html5/projects/{quote(project_id)}/editor">{escape(name)}</a>'
|
||||
|
||||
|
||||
def _operation_row(job: object) -> str:
|
||||
job_id = str(getattr(job, "job_id", ""))
|
||||
kind = str(getattr(job, "kind", ""))
|
||||
status = _enum_text(getattr(job, "status", ""))
|
||||
payload = getattr(job, "payload", {}) or {}
|
||||
project_id = str(payload.get("project_id") or "")
|
||||
stage = str(payload.get("stage") or "")
|
||||
message = str(payload.get("message") or getattr(job, "error", "") or "")
|
||||
project_link = (
|
||||
f'<a href="/html5/projects/{quote(project_id)}/setup">{escape(project_id)}</a>'
|
||||
if project_id
|
||||
else '<span class="muted">-</span>'
|
||||
)
|
||||
return f"""
|
||||
<tr data-html5-operation="{escape(job_id)}">
|
||||
<td><strong>{escape(kind)}</strong><small>{escape(job_id)}</small></td>
|
||||
<td>{project_link}</td>
|
||||
<td>{escape(status)}</td>
|
||||
<td>{escape(stage or "-")}</td>
|
||||
<td>{escape(message or "-")}</td>
|
||||
</tr>"""
|
||||
|
||||
|
||||
def _topbar(project_id: str, project_nav: str) -> str:
|
||||
return f"""
|
||||
<header class="topbar" data-html5-topbar>
|
||||
|
||||
@@ -42,6 +42,8 @@ from api_server.html5 import (
|
||||
render_html5_project_rows,
|
||||
render_html5_import_check,
|
||||
render_html5_import_job,
|
||||
render_html5_operation_rows,
|
||||
render_html5_operations,
|
||||
render_html5_settings_panel,
|
||||
render_html5_setup_summary,
|
||||
render_html5_source,
|
||||
@@ -1612,6 +1614,22 @@ async def html5_delete_project(project_id: str, request: Request) -> Response:
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/operations")
|
||||
async def html5_operations() -> Response:
|
||||
return Response(
|
||||
render_html5_operations(_html5_operation_jobs()),
|
||||
media_type="text/html; charset=utf-8",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/operations/jobs")
|
||||
async def html5_operation_jobs() -> Response:
|
||||
return Response(
|
||||
render_html5_operation_rows(_html5_operation_jobs()),
|
||||
media_type="text/html; charset=utf-8",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/projects/{project_id}/editor")
|
||||
async def html5_project_editor(project_id: str, q: str = "") -> Response:
|
||||
try:
|
||||
@@ -7837,6 +7855,10 @@ def _current_import_source(project_id: str) -> ImportSourceKind:
|
||||
return ImportSourceKind.XML_DUMP
|
||||
|
||||
|
||||
def _html5_operation_jobs() -> list[OperationJob]:
|
||||
return sorted(_operations.jobs.values(), key=lambda job: job.updated_at, reverse=True)[:50]
|
||||
|
||||
|
||||
def _project_summaries() -> list[ProjectSummaryResponse]:
|
||||
project_ids = set(_project_setup.keys())
|
||||
stored_snapshots = _storage.list_snapshot_refs()
|
||||
|
||||
Reference in New Issue
Block a user