Skip to main content

Dashboards

A Dashboard is a layout of charts and KPI cards over the same connector queries the Screens use. One file (config/dashboards.toml) declares every dashboard the app ships. The React DashboardView reads the layout, fans out one query per panel, and renders the grid.

Dashboards are hot-reloadable along with the rest of the config.


At a glance

📊 Users overview · Dashboard↻ RefreshUSERS · TOTAL1 248+12 this monthACTIVE1 10288 % of totalINACTIVE14612 % of totalADMINS183 added · 1 removedUSERS PER STATUS · BARActivePendingDisabledAdminRead-onlyOtherCREATED PER MONTH · LINEJanFebMarAprMayJunJul

Defining a dashboard

[dashboards.myapp.overview]
label = "Users overview"
description = "Snapshot of accounts, statuses and recent growth."
auto_load = true

# One panel per KPI / chart
[[dashboards.myapp.overview.panels]]
id = "users_total"
type = "stat"
label = "Users · total"
query = "users_count" # any SELECT — first row, first column wins
columns = 3 # CSS-grid width inside the dashboard
delta_field = "delta_month" # optional secondary number

[[dashboards.myapp.overview.panels]]
id = "users_per_status"
type = "bar"
label = "Users per status"
query = "users_by_status" # SELECT status, count(*) FROM users GROUP BY status
columns = 6
x = "status"
y = "count"

[[dashboards.myapp.overview.panels]]
id = "created_per_month"
type = "line"
label = "Created per month"
query = "users_created_per_month"
columns = 6
x = "month"
y = "count"

[[dashboards.myapp.overview.panels]]
id = "users_by_role"
type = "pie"
label = "Users by role"
query = "users_by_role"
columns = 4
slice = "role"
value = "count"

The CSS-grid width is 12 columns. A panel with columns = 6 takes half a row; two columns = 3 plus one columns = 6 share a row.


Panel types

typeWhat it rendersRequired fields
statA single big number with an optional delta below.query. Reads the first row's first column. delta_field optional.
barVertical bars per category.x (category), y (numeric).
lineA line over a time / ordered axis.x, y. Sorted by x as returned.
piePie chart per slice.slice (category), value (numeric).
grid (planned)A small DataTable inline.query, optional columns hints.

Each panel binds to one named query on the screen's connector — or another connector when the panel sets connector = "other". Permission is sql:<connector>:<query> — a panel the caller cannot run is dropped, the grid collapses around it.


Layout

A dashboard's panels render in declaration order, flowing left-to-right inside a 12-column grid. A panel with no columns defaults to 4 (three side-by-side).

Optional layout knobs:

FieldEffect
columnsPanel width (1 – 12). Wraps to the next row when overflowing.
rowsOptional vertical span. Default 1.
groupTag panels with a group label — the React UI prints a section header above the first panel of each group.
auto_loadRun the panel's query on dashboard open. Defaults to the dashboard-level auto_load.

REST endpoints

MethodPathPurpose
GET/api/dashboardsEvery accessible dashboard per app.
GET/api/dashboards/{app}One app's dashboards.
GET/api/dashboards/{app}/{id}The dashboard's full layout.
POST/api/dashboards/{app}/{id}/refreshRe-fetch every panel server-side (proxied to the underlying /api/query/…).

The DashboardView calls /api/query/{connector}/{name} directly per panel — the same gate as a TableView. A panel without the required permission is silently dropped.


Tips & best practices

  • Reuse the screen's queries. A dashboard rarely needs new SQL — a users_by_status GROUP BY is one extra query alongside users_get, in the same connector. Keeps the dictionary one and the same.
  • Stat panels are cheap; pie panels are not. A pie over thousands of slices reads poorly. When the cardinality grows past 8, switch to a bar with a LIMIT N plus an Other bucket.
  • Pick a default columns per panel type. Stats look right at 3 (four-up); bar / line at 6 (two-up); pie at 4. The grid then accommodates the mix without arithmetic.
  • A dashboard is permission-pruned. Panels whose query the caller cannot run are dropped. The layout collapses cleanly — design with that in mind: do not chain panels that depend on each other.