CnDashboardPage
Top-level dashboard page component — the dashboard equivalent of CnIndexPage. Assembles a complete dashboard from a widgets definition array and a layout array. Supports custom widgets via scoped slots, Nextcloud Dashboard API widgets, tile widgets, and an optional drag-and-drop edit mode.
Wraps: CnDashboardGrid, CnWidgetWrapper, CnWidgetRenderer, CnTileWidget
Try it
Widget types
| Type | How to use |
|---|---|
| Tile | Widget def has type: 'tile' — renders as a quick-access link tile |
| Custom | Provide a #widget-{widgetId} scoped slot (escape hatch — beats every built-in branch when a slot exists) |
| Chart | Widget def has type: 'chart' — declarative apexcharts mount via CnChartWidget; chart inputs ride widgetDef.props |
| NC Dashboard API | Widget def has itemApiVersions — auto-rendered via CnWidgetRenderer |
The dispatcher resolves widgets in that order. The custom-slot branch beats the chart branch so apps that need bespoke apexcharts behaviour outside the manifest contract can fall back to #widget-{id} without losing the rest of the manifest.
Chart widget
const WIDGETS = [{
id: 'sla-trend',
title: 'SLA trend',
type: 'chart',
props: {
chartKind: 'line', // line | bar | donut | area | pie | radialBar
series: [{ name: 'SLA %', data: [82, 88, 91, 93] }],
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
options: { stroke: { width: 3 } }, // deep-merged with CnChartWidget defaults
height: 280,
// Reserved for a future cycle — not read at render time today:
// dataSource: { url: '/index.php/apps/myapp/api/charts/sla' }
// dataSource: { register: 'cases', schema: 'case', groupBy: 'caseType', aggregate: 'count' }
},
}]
Forwarded props keys (everything else is ignored): chartKind (→ type), series, categories, labels, options, colors, toolbar, legend, height, width, unavailableLabel.
Usage
<template>
<CnDashboardPage
title="Dashboard"
:widgets="WIDGETS"
:layout="layout"
:loading="loading"
:allow-edit="true"
@layout-change="saveLayout"
@edit-toggle="onEditToggle">
<!-- Custom widgets -->
<template #widget-kpis="{ item }">
<CnKpiGrid :items="kpiData" />
</template>
<template #widget-cases-chart="{ item }">
<CnChartWidget type="pie" :series="chartSeries" :labels="chartLabels" />
</template>
<!-- Per-widget header actions -->
<template #widget-kpis-actions="{ item }">
<NcButton type="tertiary" @click="refreshKpis">Refresh</NcButton>
</template>
<template #header-actions>
<NcButton @click="addWidget">Add widget</NcButton>
</template>
</CnDashboardPage>
</template>
<script>
const WIDGETS = [
{ id: 'kpis', title: 'Key Metrics', type: 'custom' },
{ id: 'cases-chart', title: 'Cases by status', type: 'custom', iconClass: 'icon-chart' },
]
const DEFAULT_LAYOUT = [
{ id: 1, widgetId: 'kpis', gridX: 0, gridY: 0, gridWidth: 12, gridHeight: 2, showTitle: false },
{ id: 2, widgetId: 'cases-chart', gridX: 0, gridY: 2, gridWidth: 6, gridHeight: 4 },
]
</script>
Use the useDashboardView composable to manage widget state and layout persistence:
import { useDashboardView } from '@conduction/nextcloud-vue'
const { widgets, layout, loading, onLayoutChange } = useDashboardView({
widgets: WIDGETS,
defaultLayout: DEFAULT_LAYOUT,
loadLayout: () => loadFromConfig('dashboard_layout'),
saveLayout: (l) => saveToConfig('dashboard_layout', l),
})
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | String | '' | Page title |
description | String | '' | Description shown below the title |
widgets | Array | [] | Widget definition objects (see Widget definition below) |
layout | Array | [] | Grid placement objects (see Layout item below) |
loading | Boolean | false | Show loading spinner instead of the grid |
allowEdit | Boolean | false | Show the Edit/Done toggle button |
columns | Number | 12 | Number of grid columns |
cellHeight | Number | 80 | Grid cell height in pixels |
gridMargin | Number | 12 | Gap between grid items in pixels |
editLabel | String | 'Edit' | Label for the edit button |
doneLabel | String | 'Done' | Label for the done button |
emptyLabel | String | 'No widgets configured' | Empty state message |
unavailableLabel | String | 'Widget not available' | Fallback for unknown widget IDs |
Widget definition
| Field | Type | Description |
|---|---|---|
id | String | Unique widget identifier |
title | String | Widget title shown in the wrapper header |
type | String | 'custom' (default), 'tile', or 'chart'. 'chart' mounts CnChartWidget; chart inputs ride props |
props | Object | Free-form widget-specific props. For chart widgets: { chartKind, series, categories, labels, options, colors, toolbar, legend, height, width, unavailableLabel, dataSource? } |
iconUrl | String | Header icon image URL |
iconClass | String | Header icon CSS class |
titleIconPosition | String | Position of the widget-{id}-title-icon slot: 'left' (before title) or 'right' (after actions, default) |
titleIconColor | String | CSS color applied to the title-icon slot container (e.g. '#e74c3c') |
buttons | Array | Footer buttons: [{ text, link }] |
itemApiVersions | Number[] | NC Dashboard API versions — triggers auto-rendering |
reloadInterval | Number | Auto-refresh interval in seconds (NC widgets) |
Layout item
| Field | Type | Description |
|---|---|---|
id | String | Number | Unique placement ID |
widgetId | String | References a widget id from the widgets array |
gridX | Number | Column start (0-based) |
gridY | Number | Row start (0-based) |
gridWidth | Number | Width in grid columns |
gridHeight | Number | Height in grid rows |
showTitle | Boolean | Whether to show the wrapper header (default true) |
styleConfig | Object | Style overrides passed to CnWidgetWrapper |
Events
| Event | Payload | Description |
|---|---|---|
layout-change | layout[] | Emitted when the user drags or resizes a widget; payload is the full updated layout array |
edit-toggle | boolean | Emitted when the Edit/Done button is clicked; payload is the new editing state |
Slots
| Slot | Scope | Description |
|---|---|---|
header-actions | — | Extra buttons in the page header (right side) |
widget-{widgetId} | { item, widget } | Custom content for a specific widget |
widget-{widgetId}-actions | { item, widget } | Header action buttons for a specific widget |
widget-{widgetId}-title-icon | { item, widget } | Extra icon in the widget header; position and color controlled by titleIconPosition / titleIconColor on the widget definition |
empty | — | Custom empty state when no layout items exist |
Reference (auto-generated)
The tables below are generated from the SFC source via vue-docgen-cli. They reflect what's actually in CnDashboardPage.vue and update automatically whenever the component changes.
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | '' | Page title | |
description | string | '' | Page description (shown below title) | |
widgets | Array<{ id: string, title: string, type: string, iconUrl: string, iconClass: string, buttons: Array, itemApiVersions: number[], reloadInterval: number, props: object }> | [] | Widget definitions array. Each widget defines metadata for rendering. Custom widgets: { id: 'my-widget', title: 'My Widget', type: 'custom' } NC API widgets: { id: 'calendar', title: 'Calendar', itemApiVersions: [1,2], ... } Tile widgets: { id: 'tile-files', type: 'tile', title: 'Files', icon: 'M12...', iconType: 'svg', backgroundColor: '#0082c9', textColor: '#fff', linkType: 'app', linkValue: 'files' } Chart widgets: { id: 'sla', type: 'chart', title: 'SLA trend', props: { chartKind: 'line', series: [{ name: 'SLA %', data: [82, 88, 91] }], categories: ['Q1', 'Q2', 'Q3'], options: { stroke: { width: 3 } } } } | |
layout | union | [] | Layout array defining widget positions in the grid. Each item: { id: 'unique-id', widgetId: 'my-widget', gridX: 0, gridY: 0, gridWidth: 4, gridHeight: 3 } Additional properties (showTitle, styleConfig, tile config) are passed through. | |
loading | boolean | false | Whether the dashboard is loading | |
allowEdit | boolean | false | Whether to show the edit toggle button | |
columns | number | 12 | Number of grid columns | |
cellHeight | number | 80 | Grid cell height in pixels | |
gridMargin | number | 12 | Grid margin in pixels | |
editLabel | string | () => t('nextcloud-vue', 'Edit') | Label for the edit button | |
doneLabel | string | () => t('nextcloud-vue', 'Done') | Label for the done button (when editing) | |
emptyLabel | string | () => t('nextcloud-vue', 'No widgets configured') | Label for the empty state | |
unavailableLabel | string | () => t('nextcloud-vue', 'Widget not available') | Label for unavailable widgets |
Events
| Name | Payload | Description |
|---|---|---|
layout-change | — | |
edit-toggle | — |
Slots
| Name | Bindings | Description |
|---|---|---|
header-actions | — | |
actions | — | |
empty | — | |
'widget-' + item.widgetId + '-title-icon' | name, item, widget | |
'widget-' + item.widgetId + '-actions' | name, item, widget | |
'widget-' + item.widgetId | name, item, widget |