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
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
type | What it renders | Required fields |
|---|---|---|
stat | A single big number with an optional delta below. | query. Reads the first row's first column. delta_field optional. |
bar | Vertical bars per category. | x (category), y (numeric). |
line | A line over a time / ordered axis. | x, y. Sorted by x as returned. |
pie | Pie 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:
| Field | Effect |
|---|---|
columns | Panel width (1 – 12). Wraps to the next row when overflowing. |
rows | Optional vertical span. Default 1. |
group | Tag panels with a group label — the React UI prints a section header above the first panel of each group. |
auto_load | Run the panel's query on dashboard open. Defaults to the dashboard-level auto_load. |
REST endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/dashboards | Every 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}/refresh | Re-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_statusGROUP BYis one extra query alongsideusers_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 Nplus an Other bucket. - Pick a default
columnsper 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.