diff --git a/pipekit/web/app.py b/pipekit/web/app.py index 77437d1..ce7d154 100644 --- a/pipekit/web/app.py +++ b/pipekit/web/app.py @@ -232,9 +232,51 @@ async def module_run_action(module_id: int, request: Request, raise HTTPException(404, f"module id={module_id} not found") run_id = repo.create_run(module_id) background.add_task(_run_in_background, module_id, run_id, dry) + if request.headers.get("HX-Request"): + hx_target = request.headers.get("HX-Target", "") + if hx_target.startswith("module-status-"): + return _module_status_pill_response(request, module_id, force_poll=True) + module = repo.get_module(module_id) + recent_runs = repo.list_runs(module_id=module_id, limit=10, + exclude_status="dry_run") + return _templates.TemplateResponse( + request, "_module_live.html", + _ctx(module=module, recent_runs=recent_runs, force_poll=True)) return RedirectResponse(url=f"/runs/{run_id}", status_code=303) +@_router.get("/modules/{module_id}/live-fragment", response_class=HTMLResponse) +def module_live_fragment(request: Request, module_id: int): + module = repo.get_module(module_id) + if module is None: + raise HTTPException(404, f"module id={module_id} not found") + recent_runs = repo.list_runs(module_id=module_id, limit=10, + exclude_status="dry_run") + return _templates.TemplateResponse( + request, "_module_live.html", + _ctx(module=module, recent_runs=recent_runs, force_poll=False)) + + +@_router.get("/modules/{module_id}/status-pill", response_class=HTMLResponse) +def module_status_pill(request: Request, module_id: int): + if repo.get_module(module_id) is None: + raise HTTPException(404, f"module id={module_id} not found") + return _module_status_pill_response(request, module_id) + + +def _module_status_pill_response(request: Request, module_id: int, + force_poll: bool = False): + module = repo.get_module(module_id) + recent = repo.list_runs(module_id=module_id, limit=1) + if recent: + module["last_status"] = recent[0]["status"] + else: + module["last_status"] = None + return _templates.TemplateResponse( + request, "_module_status_pill.html", + _ctx(module=module, force_poll=force_poll)) + + def _run_in_background(module_id: int, run_id: int, dry_run: bool) -> None: try: engine.run_module(module_id, run_id=run_id, dry_run=dry_run) diff --git a/pipekit/web/templates/_module_live.html b/pipekit/web/templates/_module_live.html new file mode 100644 index 0000000..47bd24e --- /dev/null +++ b/pipekit/web/templates/_module_live.html @@ -0,0 +1,43 @@ +{# Partial: recent runs panel + out-of-band status pill update. + Rendered by both module_detail.html (on load) and the live-fragment endpoint. + Polls every 3s while running; stops automatically when idle. #} + +
+
+
Recent runs + last {{ recent_runs|length }} + all → +
+
+ {% if recent_runs %} + + + + {% for r in recent_runs %} + + + + + + + + {% endfor %} + +
idstarteddurationstatusrows
#{{ r.id }}{{ r.started_at or '—' }}{{ r.duration_s | duration }}{{ r.status }}{{ r.row_count if r.row_count is not none else "—" }}
+ {% else %} +
No runs yet.
+ {% endif %} +
+
+
+ +{# Out-of-band: keep the status pills in the page header in sync #} + + {% if module.running %}running{% endif %} + {% if not module.enabled %}disabled{% endif %} + diff --git a/pipekit/web/templates/_module_status_pill.html b/pipekit/web/templates/_module_status_pill.html new file mode 100644 index 0000000..9697935 --- /dev/null +++ b/pipekit/web/templates/_module_status_pill.html @@ -0,0 +1,18 @@ +{# Partial: status cell for one module row on the index page. + Swaps itself (outerHTML) every 3s while running; stops when idle. #} + + {% if module.running %} + running + {% elif not module.enabled %} + disabled + {% elif module.last_status %} + {{ module.last_status }} + {% else %} + never ran + {% endif %} + diff --git a/pipekit/web/templates/module_detail.html b/pipekit/web/templates/module_detail.html index 9f9b0a6..9d762aa 100644 --- a/pipekit/web/templates/module_detail.html +++ b/pipekit/web/templates/module_detail.html @@ -8,14 +8,22 @@ {{ module.name }} module #{{ module.id }} - {% if module.running %}running{% endif %} - {% if not module.enabled %}disabled{% endif %} + + {% if module.running %}running{% endif %} + {% if not module.enabled %}disabled{% endif %} + -
+
-
+
@@ -179,32 +187,7 @@ -
-
Recent runs - last {{ recent_runs|length }} - all → -
-
- {% if recent_runs %} - - - - {% for r in recent_runs %} - - - - - - - - {% endfor %} - -
idstarteddurationstatusrows
#{{ r.id }}{{ r.started_at or '—' }}{{ r.duration_s | duration }}{{ r.status }}{{ r.row_count if r.row_count is not none else "—" }}
- {% else %} -
No runs yet.
- {% endif %} -
-
+ {% include "_module_live.html" %} {% endblock %} diff --git a/pipekit/web/templates/modules_index.html b/pipekit/web/templates/modules_index.html index 09c34e2..39b4a8b 100644 --- a/pipekit/web/templates/modules_index.html +++ b/pipekit/web/templates/modules_index.html @@ -34,23 +34,19 @@ {{ m.merge_strategy }} {{ m.dest_table }} {{ m.last_run_at or "—" }} - - {% if m.running %} - running - {% elif not m.enabled %} - disabled - {% elif m.last_status %} - {{ m.last_status }} - {% else %} - never ran - {% endif %} - + {% with module=m %}{% include "_module_status_pill.html" %}{% endwith %} {{ m.last_row_count if m.last_row_count is not none else "—" }} -
+
-
+