feat: add control panel
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# CASAN Ops Console (Plan-13 Track 1) — read-only Control Panel
|
||||
# CASAN Ops Console (Plan-13 Track 1/2/3/4 + Command Center) — Control Panel
|
||||
|
||||
Real **NestJS API + React UI** that surfaces CASAN harness telemetry. This is the Level-3
|
||||
`casan-platform` **Control Panel** component. **Read-only** ("Đọc ≠ Ghi"): it never writes
|
||||
settings or bypasses a gate — management (settings/RBAC/approval) is Plan-13 Track 2/3
|
||||
(future, soft-blocked by Plan-14).
|
||||
Real **NestJS API + React UI** that surfaces CASAN harness telemetry and governed settings
|
||||
management. This is the Level-3 `casan-platform` **Control Panel** component.
|
||||
Monitoring remains read-only ("Đọc ≠ Ghi"). Settings writes go through RBAC and the
|
||||
harness-owned governance CLI; the UI never writes harness files directly or bypasses a gate.
|
||||
|
||||
```
|
||||
backend/ NestJS read-only API (/api/v1 + /healthz) over .specify telemetry
|
||||
frontend/ React + Vite + Tailwind + TanStack Query Ops Console
|
||||
backend/ NestJS API (/api/v1 + /healthz) over .specify telemetry + governed settings
|
||||
frontend/ React + Vite + Tailwind + TanStack Query Ops Console + Settings/Approvals/Kill-switch/FinOps/Command pages
|
||||
```
|
||||
|
||||
## Run (local)
|
||||
@@ -18,26 +18,111 @@ npm run console:ui # Vite UI → http://127.0.0.1:5174 (proxie
|
||||
```
|
||||
Open http://127.0.0.1:5174 — panels show REAL metrics from `.specify/logs/**`.
|
||||
|
||||
## API (all read-only, `ok()`-enveloped except `/healthz`)
|
||||
## API (`ok()`-enveloped except `/healthz`)
|
||||
`GET /api/v1/overview` · `runs` (+ `runs/:traceId`) · `governance` · `security` ·
|
||||
`incidents` · `tools` · `traceability` · `drift` · `cost` · `GET /healthz` (200 fresh /
|
||||
503 stale — fail-loud, mirrors `dashboard-server.py`).
|
||||
|
||||
Command Center:
|
||||
|
||||
- `GET /api/v1/command` — Plan-13 §8.6 read-only executive surface. Returns eight
|
||||
evidence-backed widgets with provenance envelopes
|
||||
`{source, artifact_path, commit, run_at, verified, status}`, plus executive briefing
|
||||
rows and a live governance ticker. The `/command` UI exposes the same data with a
|
||||
VI/EN briefing toggle and evidence drawer.
|
||||
|
||||
Settings management:
|
||||
|
||||
- `GET /api/v1/settings` — list policy/current values/audit tail; viewer-readable.
|
||||
- `POST /api/v1/settings` — governed setting write. Requires `x-casan-role` with write
|
||||
permission; calls `rbac-check.py` before `control-plane-settings.py set`.
|
||||
- `POST /api/v1/settings/rollback` — governed rollback through the same core CLI.
|
||||
|
||||
Local management headers: `x-casan-actor`, `x-casan-role`, `x-casan-project`,
|
||||
`x-casan-tenant`. Missing role defaults to `viewer`, so writes fail closed.
|
||||
|
||||
Kill-switch management:
|
||||
|
||||
- `GET /api/v1/kill-switch` — list active kill-switches from `kill-switch.sh status`.
|
||||
- `POST /api/v1/kill-switch/engage` — RBAC-gated engage (`operator` or stronger).
|
||||
- `POST /api/v1/kill-switch/clear` — RBAC-gated clear (`org-admin`; strict approval remains
|
||||
enforced by the harness CLI in production mode).
|
||||
|
||||
Approval inbox / HITL:
|
||||
|
||||
- `GET /api/v1/approvals?status=pending` — list proposals and oversight tail.
|
||||
- `POST /api/v1/approvals/submit` — submit a governed proposal; delegation is resolved
|
||||
by harness `approval-inbox.py` + `delegation-policy.yaml`.
|
||||
- `POST /api/v1/approvals/decide` — approve/reject with SoD and reason; approved
|
||||
settings proposals apply through `control-plane-settings.py`.
|
||||
|
||||
FinOps/SLO:
|
||||
|
||||
- `/finops` UI reads `GET /api/v1/cost` plus `GET /api/v1/settings`.
|
||||
- Provider cost/tokens come from provider usage telemetry.
|
||||
- KPI/SLO tiles come from `14-business-kpi-report.json`.
|
||||
- Budget status uses `cost.absolute_cap_usd` when configured; otherwise it shows
|
||||
`not configured`.
|
||||
|
||||
Data sources + aggregation mirror `packages/casan-harness/tests/generate-agentops-dashboard.py`.
|
||||
App root + telemetry paths resolve via the same marker walk-up as `casan-paths.sh`
|
||||
(`.specify` or `packages/casan-harness`) and honor `CASAN_DASHBOARD_*` env overrides.
|
||||
|
||||
## Security posture (MVP)
|
||||
Binds `127.0.0.1`, no auth (read-only local ops). Refuses a non-loopback bind under
|
||||
`CASAN_PROFILE=prod` / `CASAN_CP_STRICT=1` — off-loopback exposure needs TLS/OIDC (Plan-13
|
||||
Track 4). Auth/login (reuse OKR JWT) is a follow-up.
|
||||
Binds `127.0.0.1` by default. Refuses a non-loopback bind under `CASAN_PROFILE=prod` /
|
||||
`CASAN_CP_STRICT=1` unless `CASAN_CP_TRUST_AUTH_PROXY=1` is set for an authenticated reverse
|
||||
proxy that overwrites identity headers. Management endpoints are RBAC-gated via the harness
|
||||
`rbac-check.py`; IdP group claims such as `casan-approver` are mapped to RBAC roles through
|
||||
the same harness engine.
|
||||
|
||||
## Test
|
||||
```bash
|
||||
npm run console:test # backend telemetry reader/service + healthz logic
|
||||
npm run console:test # backend telemetry/settings/approvals/kill-switch/auth mapping/command contract
|
||||
npm run console:build # backend tsc + frontend typecheck/vite build
|
||||
```
|
||||
|
||||
## Production-Like Smoke
|
||||
```bash
|
||||
docker compose -f docker-compose.control-panel.yml config
|
||||
bash packages/casan-control-panel/scripts/local-prod-smoke.sh
|
||||
```
|
||||
|
||||
The scaffold includes `Dockerfile.control-panel-api`, `Dockerfile.control-panel-ui`, and
|
||||
`nginx/control-panel.conf`. Nginx protects UI/API through oauth2-proxy `auth_request`,
|
||||
overwrites browser-supplied `X-CASAN-*` headers, and passes IdP group claims to the API for
|
||||
RBAC mapping. The local smoke starts a self-signed HTTPS stack with a mock OIDC IdP and
|
||||
expects `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin`; it also
|
||||
asserts the Command Center returns all eight widgets with provenance envelopes and invokes
|
||||
`managed-prod-smoke.sh` with the authenticated mock-IdP cookie jar. A passing local run
|
||||
therefore emits both `CP_LOCAL_SMOKE_PASS ...` and
|
||||
`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=8`.
|
||||
|
||||
Managed production readiness, once the host has real TLS files and an enterprise OIDC
|
||||
env file:
|
||||
|
||||
```bash
|
||||
CASAN_CP_TLS_DIR=/opt/casan-control-panel/tls \
|
||||
CASAN_CP_OAUTH_ENV=/opt/casan-control-panel/oauth2-proxy.env \
|
||||
bash packages/casan-control-panel/scripts/prod-readiness-check.sh
|
||||
```
|
||||
|
||||
Managed production endpoint smoke, once DNS/TLS/OIDC are deployed:
|
||||
|
||||
```bash
|
||||
CASAN_CP_BASE_URL=https://control-panel.example.com \
|
||||
bash packages/casan-control-panel/scripts/managed-prod-smoke.sh
|
||||
```
|
||||
|
||||
That unauthenticated smoke must report auth protection and spoofed-header blocking. To also
|
||||
verify authenticated identity mapping and Command Center behind the enterprise IdP, pass an
|
||||
exported browser cookie jar for a real logged-in session:
|
||||
|
||||
```bash
|
||||
CASAN_CP_BASE_URL=https://control-panel.example.com \
|
||||
CASAN_CP_COOKIE_JAR=/secure/path/control-panel-cookies.txt \
|
||||
bash packages/casan-control-panel/scripts/managed-prod-smoke.sh
|
||||
```
|
||||
|
||||
## Not in this pass
|
||||
Track 2 settings writes (wrap `control-plane-settings.py`), Track 3 RBAC + approval inbox
|
||||
(Plan-14), Track 4 docker/deploy + TLS/OIDC + FinOps/SLO. See
|
||||
Live managed host/cert/enterprise IdP traffic cutover. See
|
||||
`docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md`.
|
||||
|
||||
Reference in New Issue
Block a user