"""HTML views for the system-health dashboard (#639). Renders the read-only :class:`~webui.system_health.SystemHealthSnapshot` produced by the Phase 1 system-health API (#634). The page offers no restart, reload, or process-kill control: those are Phase 2 work, and manual process kills are the contamination path #630 exists to prevent. Every free-text field passes through :func:`webui.system_health.redact` before it reaches HTML, so a probe detail that captured a token or a credentialed URL cannot leak through the dashboard even though the API redacts it already. """ from __future__ import annotations import html from webui.system_health import ( STATUS_DEGRADED, STATUS_DOWN, STATUS_OK, STATUS_SKIPPED, STATUS_UNPROVEN, DependencyProbe, SystemHealthSnapshot, redact, ) _STATUS_BADGE_CLASS = { STATUS_OK: "badge-health-ok", STATUS_DEGRADED: "badge-health-degraded", STATUS_DOWN: "badge-health-down", STATUS_SKIPPED: "badge-health-skipped", STATUS_UNPROVEN: "badge-health-unproven", } def _safe(value: object) -> str: """Escape free text for HTML after redacting anything secret-shaped. Use this for every value that can carry arbitrary text — probe details, reasons, probe errors — because those are where a credential could ride along. """ return html.escape(redact(str(value))) def _esc(value: object) -> str: """Escape a structured field for HTML without redacting it. Commit SHAs, probe names, statuses, and timestamps are enumerated or machine-generated, never credential-bearing. They must not go through :func:`redact`: its opaque-token rule matches any 32-plus-character run, so a 40-character git SHA would render as ``[redacted]`` and the parity view — the one thing an operator reads this page for — would be blank. """ return html.escape(str(value)) def _status_badge(status: str) -> str: css = _STATUS_BADGE_CLASS.get(status, "badge-health-unproven") return f'{_esc(status)}' def _reason_list(reasons: tuple[str, ...], *, empty: str) -> str: if not reasons: return f"
{html.escape(empty)}
" items = "".join(f"{html.escape(headline)}
" "| Service | {_esc(snapshot.service)} |
|---|---|
| Mode | {_esc(snapshot.mode)} |
| Ready | {_esc(snapshot.ready)} |
| Readiness evidence complete | " f"{_esc(snapshot.readiness_complete)} |
| Deep probes requested | " f"{_esc(snapshot.deep_probes_requested)} |
| Observed at | {_esc(snapshot.timestamp)} |
| Git SHA | {_esc(version.git_sha or 'unknown')} |
|---|---|
| Git describe | " f"{_esc(version.git_describe or 'unknown')} |
| Control-plane schema | " f"{_esc(schema if schema is not None else 'unknown')} |
| Python | {_esc(version.python_version)} |
| Version status | {html.escape(known)} |
| Started at | {_esc(snapshot.started_at)} |
| Uptime | " f"{snapshot.uptime_seconds:.3f}s ({uptime_hours:.2f}h) |
No dependency probes were reported.
" rows = [] for probe in probes: latency = ( f"{probe.latency_ms:.1f} ms" if probe.latency_ms is not None else "n/a" ) rows.append( "{_esc(probe.name)}| Dependency | Kind | Status | Requirement | " "Latency | Detail | " "
|---|
Degraded dependencies: " f"{_esc(names)}
Not probed: " f"{_esc(names)} — these contribute no evidence and are not counted " "as healthy.
Details are redacted at the API boundary and again " "before rendering; credentials are never displayed.
" "No MCP namespaces are declared.
" else: rows = [] for entry in snapshot.mcp_namespaces: rows.append( "{_esc(entry.get('namespace'))}{_esc(entry.get('required_tool'))}| Namespace | Required tool | Status | " "IDE-proven | Reason | " "
|---|
The web process runs outside the IDE-managed MCP " "client, so namespace health is reported as unproven rather than " "guessed (#543).
" "Stale runtime: " "the running code, the checkout, and the remote-tracking commit " "disagree. Capability gates may be evaluating obsolete code — " "do not treat this runtime as mutation-safe.
Staleness " "indeterminate: parity could not be proven, so this " "runtime is not reported as mutation-safe.
| Daemon head | " f"{_esc(stale.daemon_head or 'unknown')} |
|---|---|
| Checkout head | " f"{_esc(stale.checkout_head or 'unknown')} |
| Remote head | " f"{_esc(stale.remote_head or 'unknown')} |
| Stale | {_esc(stale.stale)} |
| Determinable | {_esc(stale.determinable)} |
| Mutation safe | {_esc(stale.mutation_safe)} |
This dashboard is read-only. Restart and reload " "controls arrive in Phase 2 (#642); until then recovery runs through " "the sanctioned client reconnect / operator restart path.
" "docs/webui-local-dev.md for the documented "
"recovery sequence.