Adds read-only /runtime and /api/runtime routes showing active profile, identity, config model, git sync vs remote master, shell health, workflow/schema hashes, and stale-runtime warnings with #420 guidance. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
"""HTML views for MCP runtime health (#430)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import html
|
|
|
|
from webui.runtime_health import FileHash, RuntimeSnapshot
|
|
|
|
|
|
def _hash_rows(items: tuple[FileHash, ...]) -> str:
|
|
if not items:
|
|
return "<p class='muted'>No hashes available.</p>"
|
|
rows = []
|
|
for item in items:
|
|
digest = item.sha256 or item.error or "unavailable"
|
|
rows.append(
|
|
"<tr>"
|
|
f"<td>{html.escape(item.label)}</td>"
|
|
f"<td><code>{html.escape(item.path)}</code></td>"
|
|
f"<td><code>{html.escape(digest[:16] if item.sha256 else digest)}</code></td>"
|
|
"</tr>"
|
|
)
|
|
return (
|
|
"<table class='registry'><thead><tr>"
|
|
"<th>Artifact</th><th>Path</th><th>SHA-256</th>"
|
|
"</tr></thead><tbody>"
|
|
f"{''.join(rows)}</tbody></table>"
|
|
)
|
|
|
|
|
|
def render_runtime_page(snapshot: RuntimeSnapshot) -> str:
|
|
error_block = ""
|
|
if snapshot.fetch_error:
|
|
error_block = (
|
|
'<div class="stub"><p><strong>Runtime snapshot incomplete:</strong> '
|
|
f"{html.escape(snapshot.fetch_error)}</p></div>"
|
|
)
|
|
|
|
identity = snapshot.authenticated_username or "unresolved"
|
|
if snapshot.identity_error:
|
|
identity = f"unresolved ({snapshot.identity_error})"
|
|
|
|
stale_block = ""
|
|
if snapshot.stale_runtime_warning:
|
|
stale_block = (
|
|
'<div class="stub"><p><strong>Stale runtime warning:</strong> '
|
|
f"{html.escape(snapshot.stale_runtime_warning)}</p></div>"
|
|
)
|
|
|
|
shell = snapshot.shell_health or {}
|
|
shell_summary = (
|
|
f"shell_use_allowed={shell.get('shell_use_allowed')} · "
|
|
f"failures={shell.get('consecutive_spawn_failures')} · "
|
|
f"hard_stopped={shell.get('hard_stopped')}"
|
|
)
|
|
|
|
return (
|
|
"<h2>Runtime health</h2>"
|
|
f"<p class='meta'>Project <code>{html.escape(snapshot.project_id)}</code> · "
|
|
f"host <code>{html.escape(snapshot.host)}</code> · "
|
|
f"repo root <code>{html.escape(snapshot.repo_root)}</code></p>"
|
|
f"{error_block}"
|
|
f"{stale_block}"
|
|
"<h3>Active profile</h3>"
|
|
"<table class='detail'>"
|
|
f"<tr><th>Profile</th><td><code>{html.escape(snapshot.profile_name)}</code></td></tr>"
|
|
f"<tr><th>Role kind</th><td>{html.escape(snapshot.role_kind)}</td></tr>"
|
|
f"<tr><th>Identity</th><td>{html.escape(identity)}</td></tr>"
|
|
f"<tr><th>Config model</th><td>{html.escape(snapshot.config_model)}</td></tr>"
|
|
f"<tr><th>Profile mode</th><td>{html.escape(snapshot.profile_mode)} "
|
|
f"({html.escape(snapshot.profile_source)})</td></tr>"
|
|
"</table>"
|
|
"<h3>Server code sync</h3>"
|
|
"<table class='detail'>"
|
|
f"<tr><th>Local HEAD</th><td><code>{html.escape(snapshot.repo_sha or 'unknown')}</code></td></tr>"
|
|
f"<tr><th>Remote {html.escape(snapshot.remote)}/{html.escape('master')}</th>"
|
|
f"<td><code>{html.escape(snapshot.remote_master_sha or 'unknown')}</code></td></tr>"
|
|
f"<tr><th>Commits behind</th><td>{snapshot.commits_behind_master if snapshot.commits_behind_master is not None else 'unknown'}</td></tr>"
|
|
"</table>"
|
|
"<h3>Shell health</h3>"
|
|
f"<p class='meta'>{html.escape(shell_summary)}</p>"
|
|
f"<p class='muted'>{html.escape(str(shell.get('safe_next_action') or ''))}</p>"
|
|
"<h3>Workflow hashes</h3>"
|
|
f"{_hash_rows(snapshot.workflow_hashes)}"
|
|
"<h3>Schema hashes</h3>"
|
|
f"{_hash_rows(snapshot.schema_hashes)}"
|
|
"<h3>Restart / reload</h3>"
|
|
"<p class='muted'>MVP is read-only — restart MCP servers from your IDE/operator "
|
|
"workflow. Related issue: <code>#420</code>. Guidance: "
|
|
f"<code>{html.escape(snapshot.restart_guidance)}</code></p>"
|
|
"<p class='muted'>This page does not expose tokens or perform MCP restarts.</p>"
|
|
) |