"""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"
  • {_safe(reason)}
  • " for reason in reasons) return f"" def _readiness_card(snapshot: SystemHealthSnapshot) -> str: """Overall readiness. ``ready`` and ``readiness_complete`` are shown separately on purpose: a snapshot whose required probes never ran is not the same as one that ran them and passed, and collapsing the two would render an unproven green. """ if snapshot.ready and snapshot.readiness_complete: headline = "Ready" elif snapshot.ready: headline = "Ready (incomplete evidence)" else: headline = "Not ready" return ( "
    " f"

    Readiness {_status_badge(snapshot.status)}

    " f"

    {html.escape(headline)}

    " "" f"" f"" f"" "" f"" "" f"" f"" "
    Service{_esc(snapshot.service)}
    Mode{_esc(snapshot.mode)}
    Ready{_esc(snapshot.ready)}
    Readiness evidence complete{_esc(snapshot.readiness_complete)}
    Deep probes requested{_esc(snapshot.deep_probes_requested)}
    Observed at{_esc(snapshot.timestamp)}
    " "

    Readiness reasons

    " f"{_reason_list(snapshot.readiness_reasons, empty='No readiness objections recorded.')}" "
    " ) def _version_card(snapshot: SystemHealthSnapshot) -> str: version = snapshot.version uptime_hours = snapshot.uptime_seconds / 3600.0 known = ( "resolved" if version.known else "unresolved — version fields could not be read from the checkout" ) schema = version.control_plane_schema_version return ( "
    " "

    Version and uptime

    " "" f"" "" f"" "" f"" f"" f"" f"" "" f"" "
    Git SHA{_esc(version.git_sha or 'unknown')}
    Git describe{_esc(version.git_describe or 'unknown')}
    Control-plane schema{_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{snapshot.uptime_seconds:.3f}s ({uptime_hours:.2f}h)
    " "
    " ) def _dependency_rows(probes: tuple[DependencyProbe, ...]) -> str: if not probes: return "

    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( "" f"{_esc(probe.name)}" f"{_esc(probe.kind)}" f"{_status_badge(probe.status)}" f"{_esc('required' if probe.required else 'optional')}" f"{html.escape(latency)}" f"{_safe(probe.detail)}" "" ) return ( "" "" "" "" f"{''.join(rows)}
    DependencyKindStatusRequirementLatencyDetail
    " ) def _dependency_card(snapshot: SystemHealthSnapshot) -> str: degraded = [probe for probe in snapshot.dependencies if probe.ran and not probe.healthy] not_run = [probe for probe in snapshot.dependencies if not probe.ran] banner = "" if degraded: names = ", ".join(sorted(probe.name for probe in degraded)) banner += ( "

    Degraded dependencies: " f"{_esc(names)}

    " ) if not_run: names = ", ".join(sorted(probe.name for probe in not_run)) banner += ( "

    Not probed: " f"{_esc(names)} — these contribute no evidence and are not counted " "as healthy.

    " ) return ( "
    " "

    Dependencies

    " f"{banner}" f"{_dependency_rows(snapshot.dependencies)}" "

    Details are redacted at the API boundary and again " "before rendering; credentials are never displayed.

    " "
    " ) def _namespace_card(snapshot: SystemHealthSnapshot) -> str: if not snapshot.mcp_namespaces: body = "

    No MCP namespaces are declared.

    " else: rows = [] for entry in snapshot.mcp_namespaces: rows.append( "" f"{_esc(entry.get('namespace'))}" f"{_esc(entry.get('required_tool'))}" f"{_status_badge(str(entry.get('status') or STATUS_UNPROVEN))}" f"{_esc(entry.get('ide_namespace_proven'))}" f"{_safe(entry.get('reason'))}" "" ) body = ( "" "" "" "" f"{''.join(rows)}
    NamespaceRequired toolStatusIDE-provenReason
    " ) return ( "
    " "

    MCP namespaces

    " f"{body}" "

    The web process runs outside the IDE-managed MCP " "client, so namespace health is reported as unproven rather than " "guessed (#543).

    " "
    " ) def _stale_runtime_card(snapshot: SystemHealthSnapshot) -> str: stale = snapshot.stale_runtime if stale.stale: warning = ( "

    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.

    " ) elif not stale.determinable: warning = ( "

    Staleness " "indeterminate: parity could not be proven, so this " "runtime is not reported as mutation-safe.

    " ) else: warning = "" return ( "
    " "

    Stale-runtime parity

    " f"{warning}" "" "" f"" "" f"" "" f"" f"" f"" f"" "
    Daemon head{_esc(stale.daemon_head or 'unknown')}
    Checkout head{_esc(stale.checkout_head or 'unknown')}
    Remote head{_esc(stale.remote_head or 'unknown')}
    Stale{_esc(stale.stale)}
    Determinable{_esc(stale.determinable)}
    Mutation safe{_esc(stale.mutation_safe)}
    " f"{_reason_list(stale.reasons, empty='Runtime, checkout, and remote agree.')}" "
    " ) def _probe_error_card(snapshot: SystemHealthSnapshot) -> str: if not snapshot.probe_errors: return "" return ( "
    " "

    Probe errors

    " f"{_reason_list(snapshot.probe_errors, empty='')}" "
    " ) def _recovery_card() -> str: """Sanctioned recovery pointers only — never a manual process kill (#630).""" return ( "
    " "

    Recovery

    " "

    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.

    " "" "
    " ) def render_system_health_page(snapshot: SystemHealthSnapshot) -> str: """Render the full system-health dashboard body.""" return ( "

    System health

    " "

    Read-only view of the Phase 1 system-health API " "(/api/v1/system/health). Reload this page to refresh; " "nothing here polls or mutates on your behalf.

    " f"{_readiness_card(snapshot)}" f"{_stale_runtime_card(snapshot)}" f"{_version_card(snapshot)}" f"{_dependency_card(snapshot)}" f"{_namespace_card(snapshot)}" f"{_probe_error_card(snapshot)}" f"{_recovery_card()}" )