Skip to content

Dashboards & Widgets

A dashboard is a node like any other: a grid of widget instances with per-breakpoint layouts, opened as a workbench tab (/dashboard/$dashboardId). Widgets are declarative — a widget describes what data it needs as a saved-view query and how to render it; the runtime executes the query reactively, so a task created anywhere in the workspace updates a live widget immediately. Widgets never touch the store directly.

Dashboards carry variables — most importantly a time range — that the runtime interpolates into every widget’s query ($name placeholders). Change the range once and every widget on the board re-queries.

WidgetShows
MetricA single aggregate (count, sum, average) over a query
Task listTasks matching a filter, editable inline
Saved-view tableAny saved view, in compact table form
Bar / line / area / pie chartsAggregates by property, via ECharts
Social feedRecent items from the saved-content graph
Page linksA curated set of page references
Recent itemsRecently touched nodes across the workspace
Pin boardYour pinned nodes
Mini calendarA month view of dated items

Add widgets from the picker; each widget’s configuration panel is auto-generated from its declared config fields (property pickers, query filters, display options).

A widget definition is Grafana-shaped — manifest, config fields, query, renderer:

import type { WidgetDefinition } from '@xnetjs/dashboard'
const taskCountWidget: WidgetDefinition = {
manifest: { id: 'task-count', name: 'Open task count', icon: 'check' },
configFields: [
{ key: 'project', type: 'node-ref', label: 'Project', schema: 'project' }
],
query: (config, vars) => ({
schema: 'task',
filter: { project: config.project, status: { not: 'done' } },
range: vars.timeRange
}),
Renderer: ({ rows }) => <Metric value={rows.length} />
}

Plugins register widgets with a WidgetContribution. The same definition also renders on canvases — any widget can be dropped onto a canvas as a live card.

The widget host assigns a trust tier — a widget can never self-declare one:

TierSourceIsolation
built-inShips with the appNone needed
pluginInstalled extensionPlugin permission model
userWritten in the in-app editorSES lockdown() + per-widget Compartment in a Web Worker
marketplaceThird-partySandboxed iframe (allow-scripts only)

User-tier widgets render through safe node trees materialized via tag and style allowlists — no window, document, fetch, or store access; runaway code is terminated and the worker respawned. Permission prompts appear when you add a widget that requests capabilities.

The widget editor (a regular tab) persists your code as a node and registers it as a user-tier widget — edit, save, and watch it render sandboxed on the board. It syncs like any other node, so your custom widgets follow you across devices.