Dashboards
Dashboards are how operators and customers actually see CORE-M data. The dashboard builder composes widgets in a responsive grid, wires them to data sources via aliases, and renders them over realtime or historical time windows. This page covers the builder’s internals: how edits stay consistent under concurrency, what the widget catalog offers, how time windows and aggregation map onto the two telemetry stores, and how dashboards are shared, exported, and versioned.
Dashboard records carry dashboard_id, tenant_id, name, description,
layout_json, alias_config, timewindow_config, permissions, and version,
keyed by {tenant_id}:{dashboard_id}.
The builder and optimistic concurrency
Section titled “The builder and optimistic concurrency”Dashboards are edited collaboratively, so the builder uses optimistic concurrency
keyed on the dashboard version (an etag). You save against the version you loaded;
the server accepts only if nothing changed underneath you.
flowchart TD
load([Load DB1 at version 7]) --> edit["Edit layout locally"]
edit --> save{Save based on v7}
save -->|server still at v7| ok["Apply change,<br/>version → 8,<br/>push dashboard.updated"]
save -->|server now at v8| conflict["ABORTED / HTTP 409<br/>response includes current_version=8"]
conflict --> reload([Reload latest and retry])
A successful save bumps the version (7 → 8) and pushes dashboard.updated to
WebSocket viewers. A stale save — editing against version 7 when the server is
already at version 8 — is rejected with ABORTED / HTTP 409, the response
includes current_version=8, and no changes are applied. The UI surfaces this so
the editor can reload and retry rather than silently clobbering someone else’s work.
Widget catalog
Section titled “Widget catalog”The builder offers a catalog of built-in widget types. Each catalog entry defines a
widget_type_id, name, category, a config_schema_json (so config is validated
with field-level errors), default_config_json, query_capabilities, and
required_permissions.
| Widget | What it shows |
|---|---|
Telemetry line chart (timeseries_line_chart) | Time-series metrics for one or more devices |
| Alarm table | Filtered, live-updating alarm list |
| Entity table | Assets / devices and their fields |
| Map / gauge / control | Geospatial, single-value, and command widgets |
| Proof status | Proof lifecycle counts grouped by state |
| Text / markdown | Static annotation |
Two widgets worth a closer look:
- Telemetry line chart. Its config requires a data-source alias, metric keys, a time window, an aggregation, and display options. Invalid config is rejected with field-level validation errors.
- Proof status. Queries current proof lifecycle counts grouped by state and shows
accepted,pending_batch,anchored,confirmed,final, andfailedcounts — a direct view of the proof lifecycle.
Aliases
Section titled “Aliases”Widgets reference aliases rather than hard-coded device IDs. An alias like
selected_boilers resolves at runtime to a concrete set of devices (e.g. D1, D2);
the dashboard runtime queries those devices and records the resolved aliases in
the response for traceability. Aliases are what make a dashboard reusable across
device sets without rewiring every widget.
Time windows, aggregation, and downsampling
Section titled “Time windows, aggregation, and downsampling”A dashboard’s time window is either realtime or historical, and the choice determines which telemetry store backs it.
A realtime window (e.g. type realtime, interval 15m) is driven by the live
subscription: the dashboard BFF pushes points over WebSocket from
telemetry.live.{tenant}.{device}, and widgets keep only the rolling visible window
(the last 15 minutes). This maps onto the
hot store, which holds exactly that rolling
window plus fast backfill on chart load.
A historical window (e.g. “Last 24 hours”, “30 days”) queries
TimescaleDB through the aggregate query API rather than Aerospike. A 30-day
query for temperature with aggregation=avg and interval=1h returns one point
per hour per series, with timezone-adjusted bucket timestamps — served from
continuous aggregates rather than a raw scan.
Aggregation functions
Section titled “Aggregation functions”Historical queries pick an aggregation function applied per bucket:
none, avg, min, max, sum, count, first, last, delta, percentile.
(The percentile path covers p50/p95/p99-style summaries used for latency and distribution views.)
Downsampling and limits
Section titled “Downsampling and limits”Tenants have a maximum points-per-query budget (max_dashboard_points). When a query
would exceed it, the runtime either downsamples to fit or rejects with
RESOURCE_EXHAUSTED, per the widget’s policy — and the response identifies the
limiting setting so the user knows what to adjust (shorten the range, coarsen the
interval, or aggregate). This keeps a careless “raw, 90 days, 1-second” query from
trying to ship millions of points to a browser.
SCADA symbols and the resource library
Section titled “SCADA symbols and the resource library”The tenant resource library holds images, SVG symbols, SCADA symbols, JSON
templates, and widget assets. Uploaded resources are content-addressed and indexed
with content_hash, mime_type, size_bytes, and owner.
A safe upload is sanitized, stored with its content hash, and appears immediately in the builder’s resource picker.
Export / import bundles
Section titled “Export / import bundles”Dashboards export and import as bundles with schema versioning and dependency validation.
- Export produces a bundle containing the dashboard JSON, widget configs, alias
configs, resource references,
schema_version, and content hashes. Crucially, no secrets or API keys are included — bundles are safe to share. - Import validates dependencies before applying. If a bundle references a resource
hash (e.g.
H1) that is present neither in the tenant’s resource library nor in the bundle payload, the import is rejected withFAILED_PRECONDITIONand the response lists the missing resource hashes so you can supply them.
Content hashes are what make import deterministic: a referenced SCADA symbol is the exact bytes that were exported, or the import fails loudly rather than silently substituting.
Sharing and permissions
Section titled “Sharing and permissions”Dashboards support tenant-admin edit access, member view/edit permissions, and customer view access through assignments:
- A member with the
dashboardspermission whose group is assigned a dashboard sees it rendered with only the widgets whose underlying data sources they may access. - A customer user sees only dashboards assigned to their customer; requesting an
unassigned dashboard is rejected with
PERMISSION_DENIED. Assignment and real-time revocation are covered in the entity model.
Versioning
Section titled “Versioning”Dashboards maintain active and draft versions, and every save increments the version used for the optimistic-concurrency check. Combined with export bundles, this gives a clean promotion path: edit a draft, export it, and import it into another tenant or environment with full dependency validation.