Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

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 when CORDUM_DASHBOARD_EMBED_API_KEY=1)
  • CORDUM_DASHBOARD_EMBED_API_KEY (opt-in to embedding API keys in config.json)
  • CORDUM_TENANT_ID
  • CORDUM_PRINCIPAL_ID
  • CORDUM_PRINCIPAL_ROLE (set to admin to 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>

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.default
  • GET /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/packs
  • POST /api/v1/packs/install (multipart field bundle; optional fields force, upgrade, inactive)
  • POST /api/v1/packs/{id}/verify
  • POST /api/v1/packs/{id}/uninstall (JSON body { "purge": true })

Approvals

Bulk approval/rejection supports optional reason + note payloads:

  • POST /api/v1/approvals/{job_id}/approve with { "reason": "...", "note": "..." }
  • POST /api/v1/approvals/{job_id}/reject with { "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/bundles
  • GET /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}/simulate
  • GET /api/v1/policy/bundles/snapshots
  • POST /api/v1/policy/bundles/snapshots with { "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 approval
  • POST /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 }

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.