Phase 1 child of the Web Console epic #631. Adds the operator-facing system-health dashboard on top of the read-only system-health API landed by #634, so runtime problems are visible on a surface instead of being discovered late through failed LLM sessions. - webui/system_health_views.py (new): renders the SystemHealthSnapshot as readiness, stale-runtime parity, version/uptime, dependency, MCP namespace, probe-error, and recovery cards. - webui/app.py: GET /system-health, sharing load_system_health() with the JSON API so page and API cannot disagree. ?deep=1 behaves as on the API. - webui/layout.py: nav entry and health card/badge styles. - tests/test_webui_system_health_dashboard.py (new, 26 cases). - docs/webui-local-dev.md: route, field authority, and redaction split. Readiness honesty is preserved from the API: ready and readiness_complete render separately, a probe that did not run is listed under "Not probed" rather than counted healthy, and mutation safety is never claimed when the runtime is stale or parity is indeterminate. Redaction is split by field kind. Free text (probe details, reasons, probe errors) passes through system_health.redact. Structured fields (commit SHAs, probe names, statuses, timestamps) are HTML-escaped only: redact's opaque-token rule matches any run of 32 or more characters, so routing a 40-character git SHA through it rendered "[redacted]" and blanked the parity evidence the page exists to show. Non-goals honored: no restart or reload controls (Phase 2, #642), no manual process-kill guidance (#630). Read-only throughout. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
333 lines
15 KiB
Markdown
333 lines
15 KiB
Markdown
# Internal web UI — local development (#426)
|
|
|
|
Read-only MVP skeleton for the MCP Control Plane operator console. Gitea,
|
|
MCP capability gates, and `skills/llm-project-workflow/` remain the source of
|
|
truth; this UI only provides route stubs and layout.
|
|
|
|
## Prerequisites
|
|
|
|
- Python 3.11+ with project dependencies installed (`pip install -r requirements.txt`)
|
|
- No secrets in repo, config, or client bundle
|
|
|
|
## Start the server
|
|
|
|
From the repository root (or an issue worktree):
|
|
|
|
```bash
|
|
./scripts/run-webui
|
|
```
|
|
|
|
Or directly:
|
|
|
|
```bash
|
|
python3 -m webui
|
|
```
|
|
|
|
Optional environment variables:
|
|
|
|
| Variable | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `WEBUI_HOST` | `127.0.0.1` | Bind address (keep local for MVP) |
|
|
| `WEBUI_PORT` | `8765` | Listen port |
|
|
| `WEBUI_REPO_ROOT` | repository root | Prompt library workflow hash root |
|
|
| `WEBUI_PROJECT_REGISTRY` | packaged JSON | Project registry path |
|
|
| `GITEA_MCP_CONFIG` | unset | Server-side MCP profile config (never sent to browser) |
|
|
| `GITEA_MCP_PROFILE` | unset | Active MCP profile name (server-side only) |
|
|
|
|
See [webui-deployment.md](webui-deployment.md) for internal-only serving,
|
|
Cloudflare Access/WARP/VPN guidance, and unsafe bind overrides (#435).
|
|
|
|
See
|
|
[architecture/webui-control-plane-console-architecture-adr.md](architecture/webui-control-plane-console-architecture-adr.md)
|
|
for the console architecture: layer and authority boundaries, the redaction
|
|
boundary, `/api/v1/...` versioning, the target page map, and the phase gates
|
|
that govern when a write path may open (#632, epic #631).
|
|
|
|
See [webui-project-registry-api.md](webui-project-registry-api.md) for the
|
|
versioned project registry contract: registry schema versions 1 and 2, project
|
|
status, onboarding checklist state, and the fail-closed error payloads (#635).
|
|
|
|
## Routes (MVP)
|
|
|
|
| Path | Description |
|
|
|------|-------------|
|
|
| `/` | Home / operator overview |
|
|
| `/health` | JSON liveness (`status`, `service`, `mode`, `timestamp`, `uptime_seconds`) |
|
|
| `/api/v1/system/health` | Structured read-only system health (#634) |
|
|
| `/system-health` | System-health dashboard — readiness, version/uptime, dependencies, MCP namespaces, stale-runtime parity (#639) |
|
|
| `/queue` | Live PR and issue queue dashboard (#429) |
|
|
| `/api/queue` | JSON queue export with pagination metadata |
|
|
| `/projects` | Project registry list with status and onboarding progress (#427, #635) |
|
|
| `/projects/{id}` | Project detail + onboarding checklist |
|
|
| `/api/v1/projects` | Versioned JSON registry export (#635) |
|
|
| `/api/v1/projects/{id}` | Versioned JSON project detail (#635) |
|
|
| `/api/projects` | JSON registry export — unversioned Phase 1 alias of `/api/v1/projects` |
|
|
| `/prompts` | Prompt library with per-prompt copy buttons (#428) |
|
|
| `/api/prompts` | JSON prompt export with workflow hashes |
|
|
| `/runtime` | MCP runtime health and stale detection (#430) |
|
|
| `/api/runtime` | JSON runtime health export |
|
|
| `/audit` | Report audit paste + validator preview (#431) |
|
|
| `/api/audit` | JSON validator preview (POST `report_text`, optional `task_kind`) |
|
|
| `/worktrees` | Worktree hygiene dashboard (#432) |
|
|
| `/api/worktrees` | JSON worktree scan with classifications and anomalies |
|
|
| `/actions` | Gated write-action registry — all disabled in MVP (#434) |
|
|
| `/api/actions` | JSON action registry with capability metadata |
|
|
| `/api/actions/{id}/preview` | Mutation ledger preview (GET, read-only) |
|
|
| `/leases` | Lease and collision visibility (#433) |
|
|
| `/api/leases` | JSON lease/collision export |
|
|
|
|
Most routes are GET-only. POST/PUT/PATCH/DELETE return `405` with
|
|
`read-only-mvp`, except `/audit` and `/api/audit` which accept POST for
|
|
local validator preview only (no Gitea mutations, no server-side storage).
|
|
|
|
## System health API (#634)
|
|
|
|
`GET /api/v1/system/health` is the structured, read-only health surface for
|
|
automated readiness checks. It is the first console API under the `/api/v1`
|
|
prefix; the unversioned MVP exports remain as compatibility aliases.
|
|
|
|
`/health` is unchanged for existing consumers — every MVP key is still present
|
|
— and now also carries `started_at`, `uptime_seconds`, and a
|
|
`system_health_api` pointer. It stays deliberately cheap and runs no dependency
|
|
probe, because answering readiness costs real work.
|
|
|
|
**Status codes.** `200` when ready, `503` when a required dependency failed or
|
|
was never probed. Automation can branch on the code without parsing the body.
|
|
|
|
**Query flags.** The Gitea check is a network call, so it is opt-in:
|
|
`GET /api/v1/system/health?deep=1` runs it and caches the result for
|
|
`WEBUI_HEALTH_PROBE_TTL_SECONDS` (default 15s) so dashboard polling does not
|
|
amplify into remote load. Without the flag that probe reports `skipped`.
|
|
|
|
**Dependencies.** `control_plane_db` and `repository` are required and drive
|
|
readiness. `gitea` is optional: when it fails the overall `status` degrades but
|
|
`readiness.ready` stays true, because local inventory is still serveable. Each
|
|
entry carries `status`, `detail`, `required`, and `latency_ms`.
|
|
|
|
Two honesty rules are worth knowing before reading the payload:
|
|
|
|
* `stale_runtime.mutation_safe` is true only when the runtime, checkout, and
|
|
remote-tracking commits are all known and equal. An unfetched remote is
|
|
reported as indeterminate, never as safe.
|
|
* `mcp_namespaces` entries are always `unproven`. A web process runs outside
|
|
the IDE-managed MCP client and cannot invoke a namespace tool, so per #543
|
|
only a `client_namespace` probe can prove that path.
|
|
|
|
Sample response (abridged, healthy):
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "mcp-control-plane-webui",
|
|
"mode": "read-only",
|
|
"api": "/api/v1/system/health",
|
|
"timestamp": "2026-07-22T11:04:18.512034+00:00",
|
|
"readiness": { "ready": true, "complete": true, "reasons": [] },
|
|
"version": {
|
|
"git_sha": "620ed6e9a9550b8da2ceb82d9ab8744e8920490f",
|
|
"git_describe": "v1.1.0-898-g620ed6e",
|
|
"control_plane_schema_version": 4,
|
|
"python_version": "3.14.5",
|
|
"known": true
|
|
},
|
|
"process": { "started_at": "2026-07-22T10:58:02.114+00:00", "uptime_seconds": 376.4 },
|
|
"deep_probes_requested": false,
|
|
"dependencies": [
|
|
{
|
|
"name": "control_plane_db",
|
|
"kind": "sqlite",
|
|
"status": "ok",
|
|
"detail": "schema v4 readable",
|
|
"required": true,
|
|
"healthy": true,
|
|
"latency_ms": 1.482,
|
|
"metadata": { "schema_version": 4, "active_leases": 3 }
|
|
},
|
|
{ "name": "repository", "kind": "git", "status": "ok", "required": true, "healthy": true },
|
|
{ "name": "gitea", "kind": "http", "status": "skipped", "required": false, "healthy": false }
|
|
],
|
|
"mcp_namespaces": [
|
|
{ "namespace": "gitea-author", "required_tool": "gitea_whoami", "status": "unproven" }
|
|
],
|
|
"stale_runtime": { "stale": false, "determinable": true, "mutation_safe": true, "reasons": [] },
|
|
"probe_errors": []
|
|
}
|
|
```
|
|
|
|
No restart, reload, or process-kill control is exposed here: those are Phase 2
|
|
at the earliest, and #630 forbids process-kill recovery outright. Every probe
|
|
opens its subject read-only — the control-plane database is opened through a
|
|
`mode=ro` URI so a health check can never create or migrate a schema.
|
|
|
|
## Report audit (#431)
|
|
|
|
Paste an LLM final report at `/audit` or POST JSON to `/api/audit`. The UI
|
|
reuses `final_report_validator` and review schema checks to surface missing
|
|
proof fields, wrong validation vocabulary, mutation contradictions, and a
|
|
suggested next prompt or issue-comment draft. Task kind can be auto-detected
|
|
or selected explicitly.
|
|
|
|
## Project registry (#427)
|
|
|
|
Versioned registry file: `webui/data/projects.registry.json` (schema version `1`).
|
|
|
|
Override path with `WEBUI_PROJECT_REGISTRY` when operators keep a machine-local
|
|
copy outside git. The registry stores repo identity, remotes, profile names,
|
|
workflow/schema path references, and onboarding checklist steps — never tokens
|
|
or credentials.
|
|
|
|
Seed entry: **Gitea-Tools** on `https://gitea.prgs.cc` with `prgs-author`,
|
|
`prgs-reviewer`, and `prgs-reconciler` profiles.
|
|
|
|
## Prompt library (#428)
|
|
|
|
Prompts are generated at load time from canonical workflow files under
|
|
`skills/llm-project-workflow/workflows/`. SHA-256 hashes are computed from
|
|
`WEBUI_REPO_ROOT` (defaults to the repository root). Prompt bodies are short
|
|
copy/paste starters; canonical workflow files remain the only full policy
|
|
source.
|
|
|
|
## Live queue dashboard (#429)
|
|
|
|
`/queue` loads open PRs and issues for the default registry project (seed:
|
|
**Gitea-Tools** on `https://gitea.prgs.cc`) using existing `gitea_auth` read
|
|
credentials. The UI surfaces pagination proof (returned count, pages fetched,
|
|
`has_more`, `inventory_complete`) and classification badges (`claimed`,
|
|
`blocked`, `in-review`, `duplicate`) when evidence exists.
|
|
|
|
If credentials are missing or the fetch fails, the page shows an explicit error
|
|
instead of an empty queue (fail closed).
|
|
|
|
## Gated actions (#434)
|
|
|
|
`/actions` registers future write actions (claim, comment, review, merge,
|
|
delete branch, create PR/issue). Each entry declares the MCP tool, required
|
|
permission, and profile role from `task_capability_map.py` — aligned with
|
|
`gitea_resolve_task_capability`. Buttons are disabled; previews always render
|
|
a mutation ledger. Direct `attempt_action` calls fail closed without invoking
|
|
MCP tools.
|
|
|
|
## Worktree hygiene (#432)
|
|
|
|
`/worktrees` scans local `branches/` directories and registered git worktrees.
|
|
Each entry is classified (`active-pr`, `active-issue`, `dirty`, `stale-clean`,
|
|
`detached-review`, `unsafe-unknown`, `orphan`). Missing preserved worktrees
|
|
referenced by the issue lock file are flagged as anomalies (#404). The page
|
|
includes a copy/paste canonical cleanup prompt only — no deletion actions.
|
|
|
|
Override scan root with `WEBUI_REPO_ROOT` (defaults to repository root).
|
|
|
|
## Lease visibility (#433)
|
|
|
|
`/leases` surfaces read-only lease and collision state: local issue lock file,
|
|
in-progress claim inventory (#268), reviewer PR lease comments when present
|
|
(`<!-- mcp-review-lease:v1 -->`, #407), duplicate open PRs per issue (#400),
|
|
and duplicate local branches per issue. Links to collision-history backend
|
|
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
|
|
|
|
## Runtime health (#430)
|
|
|
|
`/runtime` surfaces read-only MCP/runtime diagnostics for the default registry
|
|
project: active profile and role kind, authenticated identity (when credentials
|
|
are available), config model/mode, local vs remote `master` SHA sync, shell
|
|
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
|
|
checkout is behind merged safety-gate changes. Restart guidance links to #420;
|
|
no tokens or MCP restart actions are exposed.
|
|
|
|
## System-health dashboard (#639)
|
|
|
|
`/system-health` renders the same snapshot the `/api/v1/system/health` API
|
|
returns, so the page and the API can never disagree. Cards: overall readiness,
|
|
stale-runtime parity, version and uptime, dependency probes, MCP namespaces,
|
|
probe errors (only when present), and recovery pointers. `?deep=1` opts into
|
|
the network probe exactly as the API does; the plain page load stays cheap.
|
|
|
|
Field authority and honesty rules:
|
|
|
|
* `ready` and `readiness_complete` are shown separately. A snapshot whose
|
|
required probes never ran is not the same as one that ran them and passed,
|
|
and the page never collapses the two into an unproven green.
|
|
* A probe that did not run appears under **Not probed**, never as healthy.
|
|
* `stale_runtime.mutation_safe` is displayed verbatim from the API. When the
|
|
runtime is stale, or when parity is indeterminate, the page warns and does
|
|
not claim mutation safety.
|
|
* MCP namespaces are reported `unproven`: the web process runs outside the
|
|
IDE-managed MCP client and cannot prove that path (#543).
|
|
|
|
Redaction is split by field kind. Free text — probe details, readiness and
|
|
parity reasons, probe errors — passes through `system_health.redact`.
|
|
Structured fields — commit SHAs, probe names, statuses, timestamps — are
|
|
HTML-escaped only, because `redact`'s opaque-token rule matches any run of 32
|
|
or more characters and would otherwise blank every 40-character git SHA, which
|
|
is precisely the evidence the parity view exists to show.
|
|
|
|
The dashboard is read-only: no restart, reload, or process-kill control. Those
|
|
arrive in Phase 2 (#642). Recovery guidance points at the sanctioned client
|
|
reconnect / operator restart path — never a manual daemon kill (#630).
|
|
|
|
## Deployment boundary (#435)
|
|
|
|
MVP serves on loopback by default. Binding `0.0.0.0` or `::` is **refused**
|
|
unless `WEBUI_ALLOW_PUBLIC_BIND=1`. Non-loopback hosts log a warning unless
|
|
`WEBUI_ALLOW_REMOTE_BIND=1`. `GET /health` exposes `deployment` metadata.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_gated_actions.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q
|
|
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_deployment_boundary.py -q
|
|
|
|
## Tests (#436)
|
|
|
|
Run the full hermetic web UI suite (all `test_webui_*.py` modules):
|
|
|
|
```bash
|
|
./scripts/test-webui
|
|
```
|
|
|
|
CI / Jenkins multibranch can call the path-filtered gate (runs only when the
|
|
diff touches `webui/`, `tests/test_webui_*`, or web UI docs/scripts):
|
|
|
|
```bash
|
|
./scripts/ci-webui-check
|
|
WEBUI_CI_FORCE=1 ./scripts/ci-webui-check # always run
|
|
```
|
|
|
|
`scripts/test-webui` sets `WEBUI_TEST_OFFLINE=1` by default. In that mode the
|
|
queue, lease, and runtime routes use empty offline snapshots instead of Gitea
|
|
credentials, so CI can run without MCP daemon credential access. Set
|
|
`WEBUI_TEST_OFFLINE=0` only when deliberately validating live fetch behavior.
|
|
|
|
Or invoke unittest directly:
|
|
|
|
```bash
|
|
python3 -m unittest discover -s tests -p 'test_webui_*.py' -q
|
|
```
|
|
|
|
## Lease visibility (#433)
|
|
|
|
`/leases` surfaces read-only lease and collision state: local issue lock file,
|
|
in-progress claim inventory (#268), reviewer PR lease comments when present
|
|
(`<!-- mcp-review-lease:v1 -->`, #407), duplicate open PRs per issue (#400),
|
|
and duplicate local branches per issue. Links to collision-history backend
|
|
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
|
|
|
|
## Runtime health (#430)
|
|
|
|
`/runtime` surfaces read-only MCP/runtime diagnostics for the default registry
|
|
project: active profile and role kind, authenticated identity (when credentials
|
|
are available), config model/mode, local vs remote `master` SHA sync, shell
|
|
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
|
|
checkout is behind merged safety-gate changes. Restart guidance links to #420;
|
|
no tokens or MCP restart actions are exposed.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q
|
|
```
|