Cordum Dashboard
Production dashboard for Cordum. Built with React 19, TypeScript, Vite, and Tailwind.
Local Development
cd dashboard
pnpm install
pnpm run dev
By default, the dashboard uses /config.json from dashboard/public. To point at a local gateway, update the API base URL:
{
"apiBaseUrl": "http://localhost:8081",
"apiKey": "",
"tenantId": "default",
"principalId": "dashboard",
"principalRole": "admin"
}
Runtime Configuration
The production container writes config.json at startup from environment variables:
CORDUM_API_BASE_URL(empty = same origin)CORDUM_API_KEY(only embedded whenCORDUM_DASHBOARD_EMBED_API_KEY=1)CORDUM_DASHBOARD_EMBED_API_KEY(opt-in to embedding API keys inconfig.json)CORDUM_TENANT_IDCORDUM_PRINCIPAL_IDCORDUM_PRINCIPAL_ROLE(set toadminto edit/publish policy bundles when RBAC is enforced)
If you host the dashboard on a different origin than the gateway, set CORDUM_ALLOWED_ORIGINS on the gateway to allow the dashboard origin.
For security, the dashboard does not persist API keys in localStorage; keys live in memory unless you explicitly embed one in config.json.
The live bus stream (/api/v1/stream) authenticates via WebSocket subprotocols:
Sec-WebSocket-Protocol: cordum-api-key, <base64url> (the dashboard sets this automatically when an API key is configured).
The gateway also requires tenant_id as a query parameter for the WebSocket URL when running in multi-tenant mode (the dashboard includes it automatically).
Caching Headers
If you front the dashboard with a static file server or CDN, make sure
index.html and config.json are not cached so new deploys can update runtime
configuration. /assets/ can be cached long-term with immutable headers.
System Config: Observability + Alerting
The System page writes observability and alerting settings into the config service
(scope system, scope_id default) via POST /api/v1/config. Admin role is required
when enterprise RBAC is enabled.
system/default is reserved for general system config only. Policy bundles are
stored under cfg:system:policy (data.bundles) and must be managed through the
policy bundle APIs or a wrapped /api/v1/config write targeting scope=system,
scope_id=policy. The gateway rejects bundles writes to system/default and
migrates legacy startup data from system/default into system/policy.
Example:
{
"observability": {
"otel": {
"enabled": true,
"endpoint": "otel-collector:4317",
"protocol": "grpc",
"headers": {},
"resource_attributes": {
"service.name": "cordum-gateway"
}
},
"grafana": {
"base_url": "https://grafana.example.com",
"dashboards": {
"system_overview": "d/abcd/system-overview",
"workflow_performance": "d/wxyz/workflow-performance"
}
}
},
"alerting": {
"pagerduty": {
"enabled": true,
"integration_key": "pd-key",
"severity": "critical"
},
"slack": {
"enabled": true,
"webhook_url": "https://hooks.slack.com/services/...",
"severity": "error"
}
}
}
Docker Build
docker build -t cordum-dashboard -f dashboard/Dockerfile dashboard
Run the container and point it at the API gateway:
docker run --rm -p 8082:8080 \
-e CORDUM_API_BASE_URL=http://localhost:8081 \
cordum-dashboard
To embed an API key (not recommended for shared environments), add:
-e CORDUM_API_KEY=<your-api-key> \
-e CORDUM_DASHBOARD_EMBED_API_KEY=1
Compose
docker-compose.yml includes cordum-dashboard on port 8082.
The default Compose stack embeds the API key into the dashboard config for local
development (CORDUM_DASHBOARD_EMBED_API_KEY=true). Remove that variable in
shared environments to require manual auth.
Workflow Authoring
The dashboard includes a JSON workflow editor to create/update workflows via POST /api/v1/workflows.
Run Listing
Runs are fetched from the gateway using the paginated endpoint:
GET /api/v1/workflow-runs?limit=50&cursor=<unix>&status=running&workflow_id=<id>
Jobs + Search
The dashboard includes /jobs for paginated job inspection and /search for global lookup.
GET /api/v1/jobs?limit=50&cursor=<micros>&state=RUNNING&topic=job.defaultGET /api/v1/workflow-runs?limit=50(used by search)GET /api/v1/workflows,GET /api/v1/packs
Packs Management
The dashboard can manage pack lifecycle using the gateway APIs:
GET /api/v1/packsPOST /api/v1/packs/install(multipart fieldbundle; optional fieldsforce,upgrade,inactive)POST /api/v1/packs/{id}/verifyPOST /api/v1/packs/{id}/uninstall(JSON body{ "purge": true })
Approvals
Bulk approval/rejection supports optional reason + note payloads:
POST /api/v1/approvals/{job_id}/approvewith{ "reason": "...", "note": "..." }POST /api/v1/approvals/{job_id}/rejectwith{ "reason": "...", "note": "..." }
Approve/Reject buttons appear in the Policy inbox, the Job detail view, and the Run detail Overview/Jobs tabs when a job is marked approval_required.
Remediations
When Safety Kernel returns remediations, the Job detail view shows suggested alternatives and can apply them:
POST /api/v1/jobs/{job_id}/remediate(optional body{ "remediation_id": "..." })
Applied remediations create a new job with updated topic/capability/labels.
Decision Audit Log
Job detail displays a safety decision history using:
GET /api/v1/jobs/{job_id}/decisions
Policy Rules
The policy rules list is sourced from:
GET /api/v1/policy/rules
Policy Bundles + Snapshots
Tags: #dashboard #policy-studio #firewall #governance #security
Firewall Editor (Visual Rules)
Policy bundles can be edited in a visual “Firewall” mode or raw YAML:
- Firewall view renders ordered rules, supports add/edit/duplicate/delete, and allows moving rules up/down.
- Raw YAML remains the escape hatch for power users and legacy tenant policies.
- Simulation highlighting: running bundle simulation highlights the matching rule in Firewall view.
Note: switching from Raw YAML to Firewall mode rewrites formatting/comments because the bundle is reserialized.
Studio Focus
Policy Studio includes a focus selector to show a single section at a time (Bundles, Simulate, Publish, Rules, Diff, Snapshots, Audit). Use All to show every section together.
The policy diff view uses bundle snapshots stored in the config service:
GET /api/v1/policy/bundlesGET /api/v1/policy/bundles/{id}PUT /api/v1/policy/bundles/{id}(requires admin role when enterprise RBAC is enabled)POST /api/v1/policy/bundles/{id}/simulateGET /api/v1/policy/bundles/snapshotsPOST /api/v1/policy/bundles/snapshotswith{ "note": "..." }GET /api/v1/policy/bundles/snapshots/{id}POST /api/v1/policy/publish(requires admin role when enterprise RBAC is enabled)POST /api/v1/policy/rollback(requires admin role when enterprise RBAC is enabled)GET /api/v1/policy/audit
Bundle IDs include / (e.g. secops/workflows). When calling the REST endpoints, replace / with ~
in the {id} path segment or use the bundle_id query parameter.
The Policy Studio editor is read-only unless principalRole (or X-Principal-Role) is set to admin.
Backend enforcement of admin-only actions is provided by the enterprise auth provider.
Workflow Management
DELETE /api/v1/workflows/{id}- Delete a workflow (WorkflowDetailPage > Delete button)GET /api/v1/approvals- List pending approvals (including workflow gate approvals)POST /api/v1/approvals/{job_id}/approve- Approve a job or workflow gate approvalPOST /api/v1/approvals/{job_id}/reject- Reject a job or workflow gate approval
Lock Management
POST /api/v1/locks/renew- Renew an existing lock's TTL (ToolsPage > Locks > Renew button)- Body:
{ "resource": "...", "owner": "...", "ttl_ms": 60000 }
- Body:
DLQ Pagination
The DLQ list can be paginated via:
GET /api/v1/dlq/page?limit=100&cursor=<unix>
Kubernetes
deploy/k8s/base.yaml and deploy/k8s/ingress.yaml include the dashboard deployment and routing. The ingress maps / to the dashboard service and /api/v1 to the gateway.