Ga naar hoofdinhoud

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

Loading CnDashboardPage playground…

Widget types

TypeHow to use
TileWidget def has type: 'tile' — renders as a quick-access link tile
CustomProvide a #widget-{widgetId} scoped slot (escape hatch — beats every built-in branch when a slot exists)
ChartWidget def has type: 'chart' — declarative apexcharts mount via CnChartWidget; chart inputs ride widgetDef.props
NC Dashboard APIWidget 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

PropTypeDefaultDescription
titleString''Page title
descriptionString''Description shown below the title
widgetsArray[]Widget definition objects (see Widget definition below)
layoutArray[]Grid placement objects (see Layout item below)
loadingBooleanfalseShow loading spinner instead of the grid
allowEditBooleanfalseShow the Edit/Done toggle button
columnsNumber12Number of grid columns
cellHeightNumber80Grid cell height in pixels
gridMarginNumber12Gap between grid items in pixels
editLabelString'Edit'Label for the edit button
doneLabelString'Done'Label for the done button
emptyLabelString'No widgets configured'Empty state message
unavailableLabelString'Widget not available'Fallback for unknown widget IDs
dateRangeObjectnullOptional date-range header descriptor — see Date-range header

Widget definition

FieldTypeDescription
idStringUnique widget identifier
titleStringWidget title shown in the wrapper header
typeString'custom' (default), 'tile', or 'chart'. 'chart' mounts CnChartWidget; chart inputs ride props
propsObjectFree-form widget-specific props. For chart widgets: { chartKind, series, categories, labels, options, colors, toolbar, legend, height, width, unavailableLabel, dataSource? }
iconUrlStringHeader icon image URL
iconClassStringHeader icon CSS class
titleIconPositionStringPosition of the widget-{id}-title-icon slot: 'left' (before title) or 'right' (after actions, default)
titleIconColorStringCSS color applied to the title-icon slot container (e.g. '#e74c3c')
buttonsArrayFooter buttons: [{ text, link }]
itemApiVersionsNumber[]NC Dashboard API versions — triggers auto-rendering
reloadIntervalNumberAuto-refresh interval in seconds (NC widgets)

Layout item

FieldTypeDescription
idString | NumberUnique placement ID
widgetIdStringReferences a widget id from the widgets array
gridXNumberColumn start (0-based)
gridYNumberRow start (0-based)
gridWidthNumberWidth in grid columns
gridHeightNumberHeight in grid rows
showTitleBooleanWhether to show the wrapper header (default true)
styleConfigObjectStyle overrides passed to CnWidgetWrapper

Events

EventPayloadDescription
layout-changelayout[]Emitted when the user drags or resizes a widget; payload is the full updated layout array
edit-togglebooleanEmitted when the Edit/Done button is clicked; payload is the new editing state
date-range-change{ from, to, preset }Emitted on every range change AND on mount when the date-range feature is enabled. Tracks the picker's current value.

Slots

SlotScopeDescription
header-actionsExtra 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
emptyCustom empty state when no layout items exist

Date-range header

When the dateRange prop is set with enabled: true, the dashboard renders a CnDateRangePicker between the page header and the widget grid. The selected range is:

  • emitted on every change via @date-range-change,
  • optionally persisted to localStorage (when persistKey is set),
  • provided to every descendant widget through the cnDashboardDateRange injection key as a reactive Vue ref.
<CnDashboardPage
title="Dashboard"
:widgets="WIDGETS"
:layout="layout"
:date-range="{
enabled: true,
persistKey: 'myapp.dashboard.range',
default: { preset: 'last-7' },
}"
@date-range-change="onRangeChange" />

dateRange shape

FieldTypeDescription
enabledBooleanWhen true, renders the picker row. When false / omitted, no row appears.
defaultObject (opt.)Initial { from, to, preset? } when no persisted state is found.
persistKeyString (opt.)When set, the chosen range is persisted to localStorage[persistKey].
presetsArray (opt.)Override the preset list. See DEFAULT_DATE_RANGE_PRESETS.

The resolution order is: explicit default → rehydrated localStorage (when persistKey set) → last-7 preset (now − 7d → now).

cnDashboardDateRange provide / inject

CnDashboardPage always provides cnDashboardDateRange — even when the feature is off — so descendants can inject without a fallback dance:

import { inject, ref } from 'vue'

export default {
setup() {
const range = inject('cnDashboardDateRange', ref(null))
// range.value is `{ from, to, preset }` when the feature is on,
// and `null` when it's off.
return { range }
},
}

CnChartWidget consumes this injection automatically — see its bucket data-source documentation.

Built-in page-level Actions menu

The dashboard header carries the shared CnActionsMenu overflow Refresh, Documentation, and Request a feature — next to the edit toggle. This is the page-level menu, distinct from each widget's own menu (the per-widget ones emit @widget-refresh / @widget-request-feature).

  • Refresh emits @refresh and, unless suppressed via event.preventDefault(), fires the cn:page:refresh event-bus channel with { widgetId, title }.
  • Documentation renders only when documentationUrl is set, opening it in a new tab.
  • Request a feature opens CnSuggestFeatureModal with surface: "dashboard:<id>" when mounted under CnAppRoot.

Refresh and Request-a-feature are on by default; opt out with :show-refresh="false" / :show-request-feature="false". Set :page-id for a stable id/surface.

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

NameTypeRequiredDefaultDescription
titlestring''Page title
descriptionstring''Page description (shown below title)
widgetsArray<{ 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 } } } }
layoutunion[]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.
contentArray<{ type: string, ref: string }>[]Declarative content items. Each item is a widget-ref entry from the manifest's pages[].config.content[] array: { type: 'widget-ref', ref: 'openregister://widget/&lt;schemaSlug&gt;/&lt;widgetSlug&gt;' } CnDashboardPage renders each widget-ref item as a CnWidgetRefItem which resolves the widget from OR's registry at runtime and renders the resolved component. Only widget-ref entries are processed; unknown type values are skipped with a console.warn. When both content (widget-ref items) and widgets+layout (classic GridStack layout) are present, content items are rendered above the grid in a stacked list.
loadingbooleanfalseWhether the dashboard is loading
allowEditbooleanfalseWhether to show the edit toggle button
columnsnumber12Number of grid columns
cellHeightnumber80Grid cell height in pixels
gridMarginnumber12Grid margin in pixels
editLabelstring() =&gt; t('nextcloud-vue', 'Edit')Label for the edit button
doneLabelstring() =&gt; t('nextcloud-vue', 'Done')Label for the done button (when editing)
emptyLabelstring() =&gt; t('nextcloud-vue', 'No widgets configured')Label for the empty state
unavailableLabelstring() =&gt; t('nextcloud-vue', 'Widget not available')Label for unavailable widgets
surfacestring'app-dashboard'Rendering surface forwarded to integration widgets (widgets whose type === 'integration'). Drives the AD-19 surface fallback on resolveWidget(integrationId, surface).
integrationContextunionnullObject context forwarded to integration widgets: { register, schema, objectId }. Optional — most dashboards aren't object-scoped, but CnDetailPage passes one through so CnFilesCard / CnTagsCard / CnAuditTrailCard know which object's sub-resources to fetch.
dateRangeunionnullOptional date-range header descriptor. When enabled: true the dashboard renders a CnDateRangePicker between the header and the widget grid, persists the chosen range to localStorage (when persistKey is set), emits @date-range-change on every change, AND provides a reactive cnDashboardDateRange ref to every descendant widget. When the prop is null (default), false, or { enabled: false }, the header row is NOT rendered and the existing dashboard layout stays unchanged. The provide is still installed (always) but its value stays null so descendant chart widgets fall back to their dataSource.bucket.staticRange (or skip the query entirely). Shape: ts { enabled: boolean, default?: { from: string, to: string, preset?: string }, persistKey?: string, presets?: Array&lt;{ id: string, label: string, days: number|null }&gt;, } Default preset when no explicit default and no persisted state is found: last-7 (now − 7d → now).
showRefreshbooleantrueShow the built-in Refresh item in the page-level overflow Actions menu (distinct from the per-widget menus). On by default. The default handler emits @refresh and, unless suppressed, fires the cn:page:refresh event-bus channel.
showRequestFeaturebooleantrueShow the built-in Request-a-feature item in the page-level overflow Actions menu. On by default; opens the CnSuggestFeatureModal when mounted under CnAppRoot.
documentationUrlstring''Documentation link for this dashboard. When a non-empty URL is set, the page-level overflow menu renders a "Documentation" item that opens the link in a new tab. Empty (the default) hides it.
documentationLabelstring() =&gt; t('nextcloud-vue', 'Documentation')Pre-translated label for the Documentation action. Defaults to "Documentation".
pageIdstring''Stable id for this dashboard, used in the @refresh / @request-feature payloads and the surface: "dashboard:&lt;id&gt;" field on the feature-request modal. Falls back to a slugified title when unset.
specRefstring''Optional specRef forwarded to the feature-request modal.
refreshingbooleanfalseWhether a page-level refresh is in flight (drives the Refresh icon spin).
optimisticSpinMsnumber800Optimistic Refresh-icon spin duration (ms) when refreshing is unbound.
refreshLabelstring() =&gt; t('nextcloud-vue', 'Refresh')Pre-translated label for the Refresh action.
requestFeatureLabelstring() =&gt; t('nextcloud-vue', 'Request a feature')Pre-translated label for the Request-a-feature action.
actionsMenuLabelstring() =&gt; t('nextcloud-vue', 'Actions')Pre-translated aria-label / tooltip for the overflow menu trigger.

Events

NamePayloadDescription
layout-changeEmitted when the user finishes dragging/resizing a widget. Payload: the updated layout array [{ widgetId, x, y, w, h }, ...].
edit-toggleEmitted when the user toggles edit mode. Payload: true when entering edit mode, false when leaving.
date-range-changeFired whenever the dashboard's effective date range changes (initial resolve, picker change, or persisted-range restore). Payload: { from, to, preset }.
refreshundefinedUser clicked Refresh in the page-level overflow Actions menu. Payload: { widgetId, title }. Handlers may call the second arg's preventDefault() to suppress the built-in default (event-bus emit on cn:page:refresh).
request-featureundefinedUser clicked Request a feature in the page-level overflow Actions menu. Payload: { widgetId, title }. Handlers may call the second arg's preventDefault() to suppress the built-in default (auto-opening CnSuggestFeatureModal).
widget-refreshUser clicked Refresh in a widget's overflow action menu. Payload: the layout item descriptor.
widget-request-featureUser clicked Request a feature in a widget's overflow action menu. Payload: the layout item descriptor.

Slots

NameBindingsDescription
header-actionsheader-actions Inline buttons rendered in the dashboard header next to the edit toggle. Used by every existing consumer (decidesk, mydash, opencatalogi, pipelinq, procest).
actionsactions Back-compat alias for #header-actions. Prefer #header-actions in new code.
action-items
emptyempty Replaces the default empty state shown when the dashboard has no widgets. Defaults to an NcEmptyContent block.
'widget-' + item.widgetId + '-title-icon'name, item, widget
'widget-' + item.widgetId + '-actions'name, item, widget
'widget-' + item.widgetIdname, item, widgetwidget-{widgetId} Per-widget body content (e.g. #widget-my-work). Apps inject custom widget rendering here. Scope: { item, widget }.