Dashboards
Build the view your team actually wants to look at
Any data source crossed with any of its own dimensions, filters, and display types, fully combinatorial, arranged on a drag-and-drop grid you rearrange freely.
Flexible, but never a raw column name
A widget picks a data source (events, new issues, heartbeat/uptime incidents, notification deliveries, tickets filed, or the audit log), how to display it (a big number, a chart over time, or a table grouped by whatever dimension that source offers), and optional filters specific to that source. Any combination is valid: a rich, bounded vocabulary rather than raw SQL.
The safety boundary lives in the query engine itself, not the form: every data source explicitly enumerates exactly which columns it can be grouped or filtered by, and that same list is checked again when a widget is saved, at the model layer. Nothing a widget's own config carries ever reaches a database query as a caller-supplied column name, only a value already checked against that source's own allowlist.
Every widget picks its own data source, display type, and filters.
A real drag-and-drop grid
Widgets aren't stacked in a fixed order; each one carries its own position and size, dragged and resized freely on a 12-column grid. Every drag or resize saves the whole layout in one request, not one per widget moved, so rearranging several widgets at once still costs a single round trip.
A viewer sees the exact same grid an admin does, just without the drag/resize handles: read-only, not a different, simplified layout.
The builder live-previews a widget as you configure it.
Business plan only
Enforced at the model layer, the same unbypassable pattern every other gated feature in this app uses (uptime monitoring, SCIM, API access): a dashboard simply can't be saved for an organization whose plan doesn't include it, regardless of which path in the app tries to create one.