Add HTML5 operations SSE updates
This commit is contained in:
@@ -112,12 +112,14 @@ def render_html5_operations(jobs: Iterable[object]) -> str:
|
|||||||
<span>jobs</span>
|
<span>jobs</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="band">
|
<section class="band" hx-ext="sse" sse-connect="/html5/operations/events">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Очередь</h2>
|
<h2>Очередь</h2>
|
||||||
<a class="button" href="/html5">Проекты</a>
|
<a class="button" href="/html5">Проекты</a>
|
||||||
</div>
|
</div>
|
||||||
{render_html5_operation_summary(job_list)}
|
<div data-html5-operations-summary-stream sse-swap="operations-summary" hx-swap="innerHTML">
|
||||||
|
{render_html5_operation_summary(job_list)}
|
||||||
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table data-html5-operations>
|
<table data-html5-operations>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -125,8 +127,7 @@ def render_html5_operations(jobs: Iterable[object]) -> str:
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
data-html5-operations-body
|
data-html5-operations-body
|
||||||
hx-get="/html5/operations/jobs"
|
sse-swap="operations-jobs"
|
||||||
hx-trigger="every 3s"
|
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
>{render_html5_operation_rows(job_list)}</tbody>
|
>{render_html5_operation_rows(job_list)}</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -148,9 +149,6 @@ def render_html5_operation_summary(jobs: Iterable[object]) -> str:
|
|||||||
<div
|
<div
|
||||||
class="ops-summary"
|
class="ops-summary"
|
||||||
data-html5-operations-summary
|
data-html5-operations-summary
|
||||||
hx-get="/html5/operations/summary"
|
|
||||||
hx-trigger="every 3s"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
>
|
||||||
{_metric("Всего", len(job_list))}
|
{_metric("Всего", len(job_list))}
|
||||||
{_metric("В работе", running)}
|
{_metric("В работе", running)}
|
||||||
|
|||||||
@@ -1653,6 +1653,24 @@ async def html5_operation_summary() -> Response:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/html5/operations/events")
|
||||||
|
async def html5_operations_events(once: bool = False) -> StreamingResponse:
|
||||||
|
def stream_operations():
|
||||||
|
while True:
|
||||||
|
jobs = _html5_operation_jobs()
|
||||||
|
yield _html5_sse_event("operations-summary", render_html5_operation_summary(jobs))
|
||||||
|
yield _html5_sse_event("operations-jobs", render_html5_operation_rows(jobs))
|
||||||
|
if once:
|
||||||
|
break
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
stream_operations(),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/editor")
|
@app.get("/html5/projects/{project_id}/editor")
|
||||||
async def html5_project_editor(project_id: str, q: str = "") -> Response:
|
async def html5_project_editor(project_id: str, q: str = "") -> Response:
|
||||||
try:
|
try:
|
||||||
@@ -1683,7 +1701,7 @@ async def html5_project_events(project_id: str, once: bool = False) -> Streaming
|
|||||||
fragment = render_html5_status(project_id, snapshot)
|
fragment = render_html5_status(project_id, snapshot)
|
||||||
except HTTPException as error:
|
except HTTPException as error:
|
||||||
fragment = f'<span>project: {project_id}</span><span>error: {error.detail}</span>'
|
fragment = f'<span>project: {project_id}</span><span>error: {error.detail}</span>'
|
||||||
yield f"event: status\ndata: {fragment}\n\n"
|
yield _html5_sse_event("status", fragment)
|
||||||
if once:
|
if once:
|
||||||
break
|
break
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@@ -8357,6 +8375,11 @@ def _html5_operation_jobs() -> list[OperationJob]:
|
|||||||
return sorted(_operations.jobs.values(), key=lambda job: job.updated_at, reverse=True)[:50]
|
return sorted(_operations.jobs.values(), key=lambda job: job.updated_at, reverse=True)[:50]
|
||||||
|
|
||||||
|
|
||||||
|
def _html5_sse_event(event: str, fragment: str) -> str:
|
||||||
|
data = "\n".join(f"data: {line}" for line in fragment.splitlines())
|
||||||
|
return f"event: {event}\n{data}\n\n"
|
||||||
|
|
||||||
|
|
||||||
def _project_summaries() -> list[ProjectSummaryResponse]:
|
def _project_summaries() -> list[ProjectSummaryResponse]:
|
||||||
project_ids = set(_project_setup.keys())
|
project_ids = set(_project_setup.keys())
|
||||||
stored_snapshots = _storage.list_snapshot_refs()
|
stored_snapshots = _storage.list_snapshot_refs()
|
||||||
|
|||||||
@@ -619,11 +619,22 @@ def test_html5_operations_renders_job_monitor_fragments():
|
|||||||
assert 'data-html5-page="operations"' in page.text
|
assert 'data-html5-page="operations"' in page.text
|
||||||
assert "data-html5-operations-body" in page.text
|
assert "data-html5-operations-body" in page.text
|
||||||
assert "data-html5-operations-summary" in page.text
|
assert "data-html5-operations-summary" in page.text
|
||||||
assert 'hx-get="/html5/operations/jobs"' in page.text
|
assert 'hx-ext="sse"' in page.text
|
||||||
assert 'hx-get="/html5/operations/summary"' in page.text
|
assert 'sse-connect="/html5/operations/events"' in page.text
|
||||||
|
assert 'sse-swap="operations-summary"' in page.text
|
||||||
|
assert 'sse-swap="operations-jobs"' in page.text
|
||||||
|
assert 'hx-trigger="every 3s"' not in page.text
|
||||||
assert project_id in page.text
|
assert project_id in page.text
|
||||||
assert "__next" not in page.text
|
assert "__next" not in page.text
|
||||||
|
|
||||||
|
with client.stream("GET", "/html5/operations/events?once=1") as events:
|
||||||
|
first_chunk = next(events.iter_text())
|
||||||
|
assert "event: operations-summary" in first_chunk
|
||||||
|
assert "event: operations-jobs" in first_chunk
|
||||||
|
assert "data-html5-operations-summary" in first_chunk
|
||||||
|
assert "data-html5-operation" in first_chunk
|
||||||
|
assert project_id in first_chunk
|
||||||
|
|
||||||
rows = client.get("/html5/operations/jobs")
|
rows = client.get("/html5/operations/jobs")
|
||||||
assert rows.status_code == 200
|
assert rows.status_code == 200
|
||||||
assert "text/html" in rows.headers["content-type"]
|
assert "text/html" in rows.headers["content-type"]
|
||||||
|
|||||||
Reference in New Issue
Block a user