Dashboard
Stem ships an experimental Hotwire + Routed dashboard that surfaces live queue, task, event, and worker data. It connects through the same broker/result-backend contracts as your workers, so Redis, Postgres, and in-memory deployments all work without code changes.
Quick start (local)
-
Ensure the routed ecosystem checkout lives alongside this repo (the dashboard overrides
routed,routed_hotwire, and related packages to../routed_ecosystemrelative topackages/dashboard). If you do not have the routed workspace available,dart pub getinpackages/dashboardwill fail because of the local dependency overrides. -
Install dependencies:
dart pub get
cd dashboard
dart pub get -
Start the server:
dart run bin/dashboard.dart -
Visit
http://127.0.0.1:3080/.
Environment variables
The dashboard reuses StemConfig, so it accepts the same environment settings
as workers and the CLI:
| Variable | Purpose | Default |
|---|---|---|
STEM_BROKER_URL | Broker URL | redis://127.0.0.1:6379/0 |
STEM_RESULT_BACKEND_URL | Result backend URL | broker URL |
STEM_NAMESPACE / STEM_DASHBOARD_NAMESPACE | Namespace | stem |
STEM_TLS_* | TLS configuration | unset |
Supported schemes include redis://, rediss://, postgres://,
postgresql://, and memory://.
final config = StemConfig.fromEnvironment({
'STEM_BROKER_URL': 'redis://127.0.0.1:6379/0',
'STEM_RESULT_BACKEND_URL': 'redis://127.0.0.1:6379/1',
});
stdout.writeln(
'Dashboard config broker=${config.brokerUrl} '
'backend=${config.resultBackendUrl ?? config.brokerUrl}',
);
final config = StemConfig.fromEnvironment({
'STEM_BROKER_URL': 'redis://127.0.0.1:6379/0',
'STEM_RESULT_BACKEND_URL': 'redis://127.0.0.1:6379/1',
'STEM_DEFAULT_QUEUE': 'critical',
'STEM_PREFETCH_MULTIPLIER': '4',
});
stdout.writeln(
'Dashboard defaults queue=${config.defaultQueue} '
'prefetch=${config.prefetchMultiplier}',
);
final config = StemConfig.fromEnvironment({
'STEM_BROKER_URL': 'rediss://redis.example.com:6380/0',
'STEM_TLS_CA_CERT': '/etc/ssl/certs/ca.pem',
'STEM_TLS_ALLOW_INSECURE': 'false',
});
stdout.writeln(
'Dashboard TLS enabled=${config.tls.isEnabled} '
'allowInsecure=${config.tls.allowInsecure}',
);
Deployment & auth guidance
The dashboard does not ship with its own auth layer yet. For anything beyond local use, place it behind your standard perimeter controls:
- Reverse proxy + auth: run behind Nginx/Envoy/Traefik with SSO, basic auth, or IP allowlists.
- Private network: expose the dashboard only inside a VPN/VPC.
- TLS termination: terminate HTTPS at the proxy and forward to
http://127.0.0.1:3080. - Audit controls: restrict who can issue control commands from the UI.
Reverse proxy notes
When deploying behind a proxy, ensure the proxy forwards:
Host(or setX-Forwarded-Host)X-Forwarded-Proto(so URLs and redirects stay correct)X-Forwarded-For(for request logging)
If you mount the dashboard at a subpath, configure the proxy to rewrite to the app root and to pass websocket/Turbo stream upgrades.
What you can do
- Overview: queue + worker cards and busiest queues table.
- Tasks: sortable queue listings, filters, row expansion, and ad-hoc enqueue.
- Events: live stream of queue/worker deltas over Turbo Streams.
- Workers: heartbeat freshness, isolate counts, queue assignments, and control actions (ping, soft shutdown, hard shutdown).
- Queue recovery: replay dead letters per queue.
Required dependencies (local dev)
The dashboard depends on local routed ecosystem overrides:
routed,routed_hotwireserver_testing,routed_testing,property_testingthird_party/dartastic_opentelemetry_sdkstub (keeps tests passing)
Make sure the ../routed_ecosystem checkout exists relative to
packages/dashboard before running dart pub get.
Troubleshooting: “No data”
If the UI loads but queues/workers are empty:
- Broker URL: confirm
STEM_BROKER_URLpoints at the broker you expect. - Namespace: verify
STEM_NAMESPACE/STEM_DASHBOARD_NAMESPACEmatches your worker namespace. - Result backend: set
STEM_RESULT_BACKEND_URLexplicitly if it differs from the broker (worker heartbeats live there). - Redis permissions: for Redis, the dashboard scans
stem:stream:*and readsstem:worker:heartbeatkeys. ACLs must permitSCAN,XLEN,XPENDING,ZCARD, andGETon those keys. - TLS: when using
rediss://, make sureSTEM_TLS_*variables are set. - Workers online: confirm workers are running and emitting heartbeats;
if
stem worker stats --jsonshows no heartbeats, the dashboard will too.
Diagnostics
Use the CLI to confirm what the dashboard should show:
stem health \
--broker "$STEM_BROKER_URL" \
--backend "$STEM_RESULT_BACKEND_URL"
stem worker stats --json
stem observe queues
Notes
- The Events page currently synthesizes deltas from polling. Wiring Stem's signal bus will replace this with true lifecycle events.
- The dashboard uses the same control plane as
stem worker(control queues + command replies), so the UI reflects real worker state.