Overview
A Custom dashboard is a per-workspace, per-user mosaic of DashboardWidgets that surfaces the metrics, lists, and alerts the user wants on their landing page. Dashboards live at /ws/{slug}/dashboards and the active dashboard renders as the workspace home (/ws/{slug}/).
The system ships with a per-role default layout so a freshly invited member always lands on a useful screen, and lets each user personalise from there. Widgets are arranged by drag-and-drop, persist per user, and are extensible via the plugin SDK.
Why it exists
Operators, approvers, owners, and viewers have wildly different "what do I need right now" needs. A change-heavy engineer wants the queue of in-flight CHGs, an incident commander wants the open-incident grid and the MTTR sparkline, an owner wants the executive summary and the DORA card. A single fixed homepage cannot serve all five roles; a fully blank customisable canvas overwhelms new users on day one. The CustomDashboard model splits the difference: opinionated defaults per role, full personalisation on top.
Key concepts
CustomDashboard
A CustomDashboard (CustomDashboard: workspace, owner (UserProfile), name, is_default, layout JSONField, created_at) holds the user-personalised dashboard for a single user. The layout JSON is an array of {widget_kind, widget_id, x, y, w, h, config} objects. Each user can have many dashboards but exactly one is_default=True per workspace, used as their homepage.
DashboardWidget registry
The DashboardWidget registry (WB.18) is the platform-side catalogue of every widget kind a dashboard can host. Each kind exposes:
kind— a stable identifier (open_changes,incident_grid,mttr_sparkline, etc.).default_size—{w, h}in grid units (12-column grid).config_schema— JSON Schema for the widget's per-instance configuration (filters, time range, group-by).render— the React component to mount.data_loader— the React Query hook that fetches the widget's payload.
Six built-in widget kinds
The product ships with six widgets:
- Open changes queue — table of in-flight
ChangeRequestrows, configurable filter and sort. - Incident grid — colour-coded grid of open incidents by severity and status.
- DORA card — the four DORA metrics (deploy frequency, lead time, change-failure rate, MTTR) with a 7/30/90-day trend.
- MTTR sparkline — single-metric trend chart for mean-time-to-resolve.
- Recent activity — workspace-wide audit-log feed, filtered to the user's subscriptions.
- My approvals — pending
ApprovalSteprows assigned to the user.
Additional widget kinds appear automatically when an installed plugin registers them via the SDK (see Plugin SDK below).
Per-role default layouts
When a user opens the workspace for the first time, the system materialises a default CustomDashboard from the role default:
- Owner — DORA card + Recent activity + Open changes queue + My approvals.
- Admin — Recent activity + Incident grid + Open changes queue + My approvals.
- Approver — My approvals (large) + Open changes queue + Incident grid.
- Engineer — Open changes queue + Incident grid + MTTR sparkline + My approvals.
- Viewer — DORA card + Incident grid + Recent activity.
The defaults are seeded from apps/dashboards/defaults.py; admins can override the per-role default for the workspace by saving any dashboard as the "role default" from the action menu.
Drag-reorder via @dnd-kit
The layout editor uses @dnd-kit for keyboard-accessible drag-and-drop with collision detection. Drag a widget by its header to a new grid slot; release to commit. The grid auto-flows other widgets out of the way. Resize handles on each widget corner change w/h in the layout JSON. Edit mode is toggled per dashboard with a pencil icon — outside edit mode the dashboard is locked to prevent accidental drag.
Layout persistence
Layout changes are debounced (1 second) and persisted in two places:
- localStorage under
vigilo_dashboard_layout_{workspace_slug}_{dashboard_id}for instant reload, even offline. - Server via
PATCH /api/v1/dashboards/{id}/so the layout follows the user across browsers.
On load, the server payload always wins over local cache to avoid stale state after a cross-device edit.
Plugin SDK
Third-party widgets are added via the Plugin SDK: a plugin declares one or more widget kinds in its manifest.json, ships its React render component as an ESM bundle, and registers a data loader. Once the plugin is enabled on a workspace, the new widget kinds appear in the Add widget picker and can be added to any dashboard. See Platform plugins for SDK details.
Common workflows
Personalise the homepage
- Open the workspace; the role default dashboard renders.
- Click the Edit pencil in the top-right.
- Drag widgets to reorder, resize from the corner handles, or click Add widget to pick from the registry.
- Click Save (or wait for the 1-second debounce) to persist.
Add a new dashboard
- From
/ws/{slug}/dashboards, click New dashboard, give it a name. - Add widgets from the registry; configure each (filter, time range) via the widget's gear menu.
- Toggle Default to make this dashboard your homepage (replaces the previous default for your user).
Use a plugin widget
- Admin enables the plugin under Settings → Integrations → Plugins.
- The widget kind appears in the Add widget picker for all members of the workspace.
- Add it like any built-in widget.
Permissions
- All roles can edit their own dashboards.
- Admins / owners can override the per-role default layout for the workspace.
- Owners can disable specific widget kinds workspace-wide (e.g., hide DORA card from viewers if the org doesn't want metrics in the viewer role).
Troubleshooting
Layout reverts on reload — The 1-second debounce was interrupted by a page navigation before the server persist completed. Wait a beat after the last drag before navigating, or click Save explicitly.
Plugin widget shows "Component failed to load" — The plugin bundle returned a 4xx or threw at mount. Open the browser console for the stack trace, and check Settings → Integrations → Plugins for plugin health.
Drag-and-drop doesn't work with the keyboard — Make sure you have Edit mode on. In edit mode, focus a widget and press space to pick it up, arrow keys to move, space again to drop. @dnd-kit honours prefers-reduced-motion.
Default layout changed without me asking — An admin updated the per-role default and your dashboard had is_role_default=True. Your customisations are preserved on any non-role-default dashboard.