Operator UI
The /ui dashboard, REST endpoints, auth, and how to enable it with the coordinator.
The operator UI is a server-rendered web app served by the krishiv-ui crate (Askama templates + axum + vendored JS — no CDN, no third-party fetches). It is co-located with the coordinator.
Where it lives
When the coordinator is started by krishiv local or krishiv cluster:
| Topology | URL |
|---|---|
Local (krishiv local start) | http://127.0.0.1:2002/ui (default) |
Bare-metal cluster (krishiv cluster start) | http://<http-addr>/ui (default 127.0.0.1:2002) |
| Kubernetes (operator CRD) | Service krishiv-coordinator, port 2002, path /ui |
Pages
| Path | Purpose |
|---|---|
/ui | Main jobs table with live updates (vendored JS, no WebSocket — 5 s poll). |
/ui/health | Coordinator and executor health. |
/ui/metrics | Scheduler-specific metrics in human-readable form. |
/ui/submit | Submit-job form (used by krishiv submit workflows). |
REST API
All endpoints return JSON. Pagination via ?limit=&offset= on list endpoints.
| Endpoint | Returns |
|---|---|
GET /api/v1/jobs?limit=&offset= | List of JobSummary with id, name, state, row counts, age. |
GET /api/v1/jobs/{job_id} | Job detail with stages and tasks. |
GET /api/v1/jobs/{job_id}/checkpoints | List of valid checkpoint epochs. |
GET /api/v1/executors | List of executors with slots used / total, lost count, last heartbeat. |
GET /api/v1/queues | Namespace quota snapshot. |
GET /api/v1/openapi.json | OpenAPI 3.1 spec of the management API. |
GET /metrics | Prometheus text format. |
Auth
Set KRISHIV_UI_TOKEN=<token> to require a Bearer token on the UI and all /api/v1 endpoints except /healthz. /healthz stays anonymous so liveness probes work. The CLI and daemons pass KRISHIV_COORDINATOR_BEARER_TOKEN for management calls.
Fail-closed: in production mode (KRISHIV_PRODUCTION=1), starting the coordinator without a UI token is a hard error.
Security headers
Every response carries:
Content-Security-Policy: script-src 'self'— no inline scripts, no third-party CDN.X-Content-Type-Options: nosniffX-Frame-Options: DENY— no clickjacking via iframe.
Co-location with the coordinator
The UI is built as an axum router. The coordinator spawns it:
let ui = UiState::from_shared_coordinator(shared).with_ui_bearer_token(env::var("KRISHIV_UI_TOKEN").ok());
let router = coordinator_http_router(shared).merge(krishiv_ui::router(ui));
axum::serve(listener, router).await?;
The same router serves /healthz, /readyz, /metrics, the /api/v1/* management surface, and /ui.
Embedding in another app
You can mount the UI under a sub-path of your own axum app:
let app = Router::new()
.nest("/krishiv", krishiv_ui::router(ui_state))
.route("/healthz", get(my_health));