Skip to content

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}.

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.

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.

WidgetWhat it shows
Telemetry line chart (timeseries_line_chart)Time-series metrics for one or more devices
Alarm tableFiltered, live-updating alarm list
Entity tableAssets / devices and their fields
Map / gauge / controlGeospatial, single-value, and command widgets
Proof statusProof lifecycle counts grouped by state
Text / markdownStatic 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, and failed counts — a direct view of the proof lifecycle.

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.

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.)

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.

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.

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 with FAILED_PRECONDITION and 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.

Dashboards support tenant-admin edit access, member view/edit permissions, and customer view access through assignments:

  • A member with the dashboards permission 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.

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.