Add HTML5 setup SSE updates
This commit is contained in:
@@ -1038,7 +1038,13 @@ def render_html5_project_setup(*, project_id: str, projects: Iterable[object], s
|
||||
sources = getattr(setup, "import_sources", []) or []
|
||||
source_cards = "".join(_import_source_card(source) for source in sources)
|
||||
content = f"""
|
||||
<main class="workspace setup-workspace" data-html5-page="setup" data-project-id="{escape(project_id)}">
|
||||
<main
|
||||
class="workspace setup-workspace"
|
||||
data-html5-page="setup"
|
||||
data-project-id="{escape(project_id)}"
|
||||
hx-ext="sse"
|
||||
sse-connect="/html5/projects/{quote(project_id)}/setup/events"
|
||||
>
|
||||
{_topbar(project_id, project_nav)}
|
||||
<section class="setup-layout">
|
||||
<aside class="panel">
|
||||
@@ -1187,7 +1193,7 @@ def render_html5_import_check(project_id: str, check: object | None = None) -> s
|
||||
def render_html5_import_job(project_id: str, job: object | None = None) -> str:
|
||||
if job is None:
|
||||
return f"""
|
||||
<div class="import-job" data-html5-import-job>
|
||||
<div class="import-job" data-html5-import-job sse-swap="setup-import-job" hx-swap="outerHTML">
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<p class="muted padded">Фоновая задача импорта для проекта {escape(project_id)} еще не запускалась.</p>
|
||||
</div>
|
||||
@@ -1199,14 +1205,14 @@ def render_html5_import_job(project_id: str, job: object | None = None) -> str:
|
||||
source = str(payload.get("source") or "")
|
||||
stage = str(payload.get("stage") or "")
|
||||
logs = payload.get("logs") if isinstance(payload.get("logs"), list) else []
|
||||
poll = ' hx-trigger="every 2s" hx-swap="outerHTML"' if status in {"QUEUED", "RUNNING"} else ""
|
||||
logs_html = "".join(f"<li>{escape(str(item))}</li>" for item in logs[-6:])
|
||||
return f"""
|
||||
<div
|
||||
class="import-job"
|
||||
data-html5-import-job
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/jobs/{quote(job_id)}"
|
||||
{poll}
|
||||
sse-swap="setup-import-job"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<div class="check-head">
|
||||
@@ -1231,7 +1237,7 @@ def render_html5_setup_summary(project_id: str, setup: object) -> str:
|
||||
class="setup-summary"
|
||||
data-html5-setup-summary
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/summary"
|
||||
hx-trigger="every 5s"
|
||||
sse-swap="setup-summary"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="section-title">
|
||||
|
||||
@@ -2021,6 +2021,24 @@ async def html5_project_setup_summary(project_id: str) -> Response:
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/projects/{project_id}/setup/events")
|
||||
async def html5_project_setup_events(project_id: str, once: bool = False) -> StreamingResponse:
|
||||
async def stream_setup():
|
||||
while True:
|
||||
setup = _project_setup_response(project_id)
|
||||
yield _html5_sse_event("setup-summary", render_html5_setup_summary(project_id, setup))
|
||||
yield _html5_sse_event("setup-import-job", render_html5_import_job(project_id, _html5_latest_import_job(project_id)))
|
||||
if once:
|
||||
break
|
||||
await asyncio.sleep(2)
|
||||
|
||||
return StreamingResponse(
|
||||
stream_setup(),
|
||||
media_type="text/event-stream",
|
||||
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/html5/projects/{project_id}/setup/source")
|
||||
async def html5_project_setup_source(project_id: str, request: Request) -> Response:
|
||||
form = await _html5_form_data(request)
|
||||
@@ -8392,6 +8410,19 @@ def _html5_operation_jobs() -> list[OperationJob]:
|
||||
return sorted(_operations.jobs.values(), key=lambda job: job.updated_at, reverse=True)[:50]
|
||||
|
||||
|
||||
def _html5_latest_import_job(project_id: str) -> OperationJob | None:
|
||||
jobs = [
|
||||
job
|
||||
for job in _operations.jobs.values()
|
||||
if job.payload.get("project_id") == project_id and _operation_value(getattr(job, "kind", "")) == "SERVER_IMPORT"
|
||||
]
|
||||
return max(jobs, key=lambda job: job.updated_at) if jobs else None
|
||||
|
||||
|
||||
def _operation_value(value: object) -> str:
|
||||
return str(getattr(value, "value", value))
|
||||
|
||||
|
||||
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"
|
||||
|
||||
@@ -533,6 +533,11 @@ def test_html5_project_setup_renders_server_fragments():
|
||||
assert "HTML5 Setup Demo" in setup.text
|
||||
assert "data-html5-settings-panel" in setup.text
|
||||
assert "data-html5-setup-summary" in setup.text
|
||||
assert 'hx-ext="sse"' in setup.text
|
||||
assert f'sse-connect="/html5/projects/{project_id}/setup/events"' in setup.text
|
||||
assert 'sse-swap="setup-summary"' in setup.text
|
||||
assert 'sse-swap="setup-import-job"' in setup.text
|
||||
assert 'hx-trigger="every 5s"' not in setup.text
|
||||
assert f'hx-get="/html5/projects/{project_id}/setup/summary"' in setup.text
|
||||
assert f'hx-post="/html5/projects/{project_id}/setup/settings"' in setup.text
|
||||
assert f'hx-post="/html5/projects/{project_id}/setup/source"' in setup.text
|
||||
@@ -582,6 +587,8 @@ def test_html5_project_setup_renders_server_fragments():
|
||||
assert "data-html5-import-job" in import_job.text
|
||||
assert "SERVER_IMPORT" not in import_job.text
|
||||
assert "hx-get" in import_job.text
|
||||
assert 'sse-swap="setup-import-job"' in import_job.text
|
||||
assert 'hx-trigger="every 2s"' not in import_job.text
|
||||
assert "<html" not in import_job.text
|
||||
|
||||
jobs = client.get("/operations/jobs", params={"project_id": project_id, "kind": "SERVER_IMPORT"})
|
||||
@@ -591,6 +598,8 @@ def test_html5_project_setup_renders_server_fragments():
|
||||
assert job_fragment.status_code == 200
|
||||
assert "data-html5-import-job" in job_fragment.text
|
||||
assert job_id in job_fragment.text
|
||||
assert 'sse-swap="setup-import-job"' in job_fragment.text
|
||||
assert 'hx-trigger="every 2s"' not in job_fragment.text
|
||||
assert "<html" not in job_fragment.text
|
||||
|
||||
html5_import = client.post(f"/html5/projects/{project_id}/setup/import")
|
||||
@@ -611,8 +620,18 @@ def test_html5_project_setup_renders_server_fragments():
|
||||
assert "data-html5-setup-summary" in summary.text
|
||||
assert "INDEXED" in summary.text
|
||||
assert "mock_indexed" in summary.text
|
||||
assert 'sse-swap="setup-summary"' in summary.text
|
||||
assert 'hx-trigger="every 5s"' not in summary.text
|
||||
assert "<html" not in summary.text
|
||||
|
||||
with client.stream("GET", f"/html5/projects/{project_id}/setup/events?once=1") as events:
|
||||
first_chunk = next(events.iter_text())
|
||||
assert "event: setup-summary" in first_chunk
|
||||
assert "event: setup-import-job" in first_chunk
|
||||
assert "data-html5-setup-summary" in first_chunk
|
||||
assert "data-html5-import-job" in first_chunk
|
||||
assert project_id in first_chunk
|
||||
|
||||
|
||||
def test_html5_operations_renders_job_monitor_fragments():
|
||||
client = TestClient(app)
|
||||
|
||||
Reference in New Issue
Block a user