@meshmakers/octo-meshboard
v3.4.520
Published
A flexible, widget-based dashboard library for Angular applications in the OctoMesh platform.
Readme
OctoMesh MeshBoard Library
A flexible, widget-based dashboard library for Angular applications in the OctoMesh platform.
Overview
MeshBoard provides a grid-based dashboard system with configurable widgets that can display data from various sources including runtime entities, persistent queries, and aggregations.
Installation
npm install @meshmakers/octo-meshboardQuick Start
Basic Integration
Add the MeshBoard route to your application:
import { Routes } from '@angular/router';
import { UnsavedChangesGuard } from '@meshmakers/shared-ui';
export const routes: Routes = [
{
path: "dashboard",
loadComponent: () =>
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
canDeactivate: [UnsavedChangesGuard]
}
];Load Specific MeshBoard by rtId
{
path: "dashboard/:rtId",
loadComponent: () =>
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
canDeactivate: [UnsavedChangesGuard]
}Load MeshBoard by Well-Known Name
Use meshBoardWellKnownName in route data to load a specific MeshBoard:
{
path: "cockpit",
loadComponent: () =>
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
canDeactivate: [UnsavedChangesGuard],
data: {
meshBoardWellKnownName: 'cockpit',
breadcrumb: [{ label: "Cockpit", url: "cockpit" }]
}
}Note: If the MeshBoard with the specified rtWellKnownName does not exist, an error message will be displayed with instructions on how to create it.
Setting the Well-Known Name
To set a Well-Known Name for a MeshBoard:
- Open the MeshBoard Settings dialog
- Enter a unique identifier in the "Well-Known Name" field (e.g.,
cockpit,sales-dashboard) - Save the MeshBoard
The Well-Known Name should be lowercase with hyphens, similar to URL slugs.
Architecture
octo-meshboard/
├── containers/
│ └── meshboard-view/ # Main view component
├── widgets/ # Widget implementations
│ ├── bar-chart-widget/ # Bar/Column/Stacked charts
│ ├── entity-associations-widget/ # Entity with relationships
│ ├── entity-card-widget/ # Single entity display (UML-style)
│ ├── gauge-widget/ # Arc, Circular, Linear, Radial gauges
│ ├── kpi-widget/ # Single numeric value with trend
│ ├── markdown-widget/ # Static markdown content with themed prose
│ ├── pie-chart-widget/ # Pie/Donut charts
│ ├── process-widget/ # Process diagram / HMI editor
│ ├── service-health-widget/ # Backend service health monitoring
│ ├── stats-grid-widget/ # Multiple KPIs in grid layout
│ ├── status-indicator-widget/ # Traffic light status
│ └── table-widget/ # Data grid with pagination
├── services/
│ ├── meshboard-state.service.ts # State management
│ ├── meshboard-data.service.ts # Data fetching
│ ├── meshboard-persistence.service.ts # Backend persistence
│ ├── meshboard-variable.service.ts # Variable resolution
│ ├── widget-factory.service.ts # Widget creation
│ └── widget-registry.service.ts # Widget registration
├── dialogs/ # Configuration dialogs
├── models/ # TypeScript interfaces
└── graphQL/ # GraphQL queries/mutationsWidget Types
KPI Widget
Displays a single value with optional label, prefix, suffix, and trend indicator.
interface KpiWidgetConfig {
type: 'kpi';
valueAttribute: string;
labelAttribute?: string;
prefix?: string;
suffix?: string;
icon?: string;
trend?: 'up' | 'down' | 'neutral';
// For persistent query mode
queryMode?: 'simpleCount' | 'aggregation' | 'groupedAggregation';
queryValueField?: string;
filters?: WidgetFilterConfig[];
}Gauge Widget
Displays a numeric value as arc, circular, linear, or radial gauge.
interface GaugeWidgetConfig {
type: 'gauge';
gaugeType: 'arc' | 'circular' | 'linear' | 'radial';
valueAttribute: string;
min?: number;
max?: number;
ranges?: GaugeRange[];
showLabel?: boolean;
prefix?: string;
suffix?: string;
}Table Widget
Displays data in a configurable table with sorting and filtering.
interface TableWidgetConfig {
type: 'table';
columns: TableColumn[];
sorting?: TableSortConfig[];
filters?: WidgetFilterConfig[];
pageSize?: number;
sortable?: boolean;
}Pie Chart Widget
Displays data as pie or donut chart.
interface PieChartWidgetConfig {
type: 'pieChart';
chartType: 'pie' | 'donut';
categoryField: string;
valueField: string;
showLabels?: boolean;
showLegend?: boolean;
legendPosition?: 'top' | 'bottom' | 'left' | 'right';
filters?: WidgetFilterConfig[];
}Bar Chart Widget
Displays data as column, bar, or stacked charts.
interface BarChartWidgetConfig {
type: 'barChart';
chartType: 'column' | 'bar' | 'stackedColumn' | 'stackedBar' | 'stackedColumn100' | 'stackedBar100';
categoryField: string;
series: BarChartSeries[];
// Dynamic series mode
seriesGroupField?: string;
valueField?: string;
showLegend?: boolean;
legendPosition?: 'top' | 'bottom' | 'left' | 'right';
filters?: WidgetFilterConfig[];
}Stats Grid Widget
Displays multiple KPIs in a grid layout.
interface StatsGridWidgetConfig {
type: 'statsGrid';
stats: StatItem[];
columns?: number;
}Service Health Widget
Displays service health status with pulse animation.
interface ServiceHealthWidgetConfig {
type: 'serviceHealth';
navigateOnClick?: boolean;
detailRoute?: string;
showPulse?: boolean;
}Status Indicator Widget
Displays boolean status (e.g., ENABLED/DISABLED).
interface StatusIndicatorWidgetConfig {
type: 'statusIndicator';
trueLabel?: string;
falseLabel?: string;
trueColor?: string;
falseColor?: string;
}Markdown Widget
Displays static markdown content with themed prose styling. Supports variable interpolation.
interface MarkdownWidgetConfig {
type: 'markdown';
content: string;
resolveVariables?: boolean;
padding?: string;
textAlign?: 'left' | 'center' | 'right';
}Requires provideMarkdown() from ngx-markdown in the application providers.
Entity Card Widget
Displays a single runtime entity in a UML-style card.
interface EntityCardWidgetConfig {
type: 'entityCard';
attributePaths?: string[];
showCkType?: boolean;
}Entity With Associations Widget
Displays a runtime entity together with its associated entities.
interface EntityWithAssociationsWidgetConfig {
type: 'entityWithAssociations';
attributePaths?: string[];
includeAssociations?: boolean;
}Process Widget
Provides HMI-style (Human-Machine Interface) process visualization with tanks, pipes, valves, pumps, and other process elements. Includes a visual drag-and-drop designer.
See Process Widget Documentation for detailed documentation.
Data Sources
Runtime Entity Data Source
Fetches a single entity by CK type or rtId.
interface RuntimeEntityDataSource {
type: 'runtimeEntity';
ckTypeId?: string;
rtId?: string;
attributePaths?: string[];
includeAssociations?: boolean;
}Persistent Query Data Source
Executes a saved query by its rtId.
interface PersistentQueryDataSource {
type: 'persistentQuery';
queryRtId: string;
queryName?: string;
queryFamily?: 'runtime' | 'streamData';
/**
* Stream-data opt-out. When `true`, the active MeshBoard time filter is NOT
* bound to this widget's `streamDataArgs.from/.to`, so the saved query's own
* time range wins. Default auto-binds the time filter. SD-only (ignored for
* runtime queries). Exposed in the config dialog as the
* "Ignore MeshBoard time filter" toggle, shown only for stream-data queries.
*/
ignoreTimeFilter?: boolean;
}Aggregation Data Source
Performs aggregation queries (count, sum, avg, min, max).
interface AggregationDataSource {
type: 'aggregation';
queries: AggregationQuery[];
}
interface AggregationQuery {
id: string;
ckTypeId: string;
aggregation: 'count' | 'sum' | 'avg' | 'min' | 'max';
attribute?: string;
filters?: WidgetFilterConfig[];
}Construction Kit Query Data Source
Queries Construction Kit metadata (models, types, attributes).
interface ConstructionKitQueryDataSource {
type: 'constructionKitQuery';
queryTarget: 'models' | 'types' | 'attributes' | 'associationRoles' | 'enums' | 'records';
groupBy?: string;
valueField?: string;
categoryField?: string;
}Service Call Data Source
Calls services for status/health information.
interface ServiceCallDataSource {
type: 'serviceCall';
callType: 'modelAvailable' | 'healthCheck';
modelName?: string;
serviceType?: 'identity' | 'asset-repository' | 'bot' | 'communication-controller' | 'mesh-adapter' | 'custom';
customEndpoint?: string;
}Variables
MeshBoard supports variables that can be used in filter values. Variables use the syntax ${variableName} or $variableName.
Defining Variables
Variables are defined at the MeshBoard level:
interface MeshBoardVariable {
name: string; // Variable name (without $)
label?: string; // Display label
description?: string;
type: 'string' | 'number' | 'boolean' | 'date' | 'datetime';
source: 'static' | 'timeFilter';
value: string;
defaultValue?: string;
}Using Variables in Filters
const filter: WidgetFilterConfig = {
attributePath: 'createdDate',
operator: 'gte',
comparisonValue: '${timeRangeFrom}'
};Time Filter Variables
When the time filter is enabled, two variables are automatically available:
${timeRangeFrom}- Start of selected time range (ISO string)${timeRangeTo}- End of selected time range (ISO string)
Time Filter
Enable a global time filter for the MeshBoard:
interface MeshBoardTimeFilterConfig {
enabled: boolean;
pickerConfig?: {
availableTypes?: ('year' | 'quarter' | 'month' | 'relative' | 'custom')[];
minYear?: number;
maxYear?: number;
defaultRelativeValue?: number;
defaultRelativeUnit?: 'hours' | 'days' | 'weeks' | 'months';
showTime?: boolean;
};
selection?: TimeRangeSelection;
}Stream-data widgets: automatic time-range binding
For widgets backed by a stream-data persistent query, the active time filter
is auto-bound to the query's streamDataArgs.from/.to — you do not need to add
a fieldFilter on timestamp. This pushes the range down to the backend
(CrateDB DATE_BIN / downsampling bucket sizing) instead of post-filtering rows.
The binding is resolved centrally by
MeshBoardStateService.resolveStreamDataTimeArgs(ignoreTimeFilter); every
stream-data widget calls it.
Per-widget opt-out: the config dialog of each stream-data widget shows an
"Ignore MeshBoard time filter" toggle (only when the selected query is a
stream-data query). Enabling it sets ignoreTimeFilter: true on the data source
so the saved query's intrinsic time range wins. Runtime queries ignore the time
filter binding entirely and never show the toggle.
Services
MeshBoardStateService
Central state management for MeshBoard.
// Load initial MeshBoard
await stateService.loadInitialMeshBoard();
// Switch to specific MeshBoard
await stateService.switchToMeshBoard(rtId);
// Switch by well-known name
await stateService.switchToMeshBoardByWellKnownName('cockpit');
// Save current MeshBoard
await stateService.saveMeshBoard();
// Access state
const config = stateService.meshBoardConfig();
const isLoading = stateService.isLoading();
const availableBoards = stateService.availableMeshBoards();MeshBoardVariableService
Resolves variables in filter values.
// Resolve variables in a string
const resolved = variableService.resolveVariables('${timeRangeFrom}', variables);
// Convert filters to DTO with resolved variables
const dtoFilters = variableService.convertToFieldFilterDto(filters, variables);WidgetRegistryService
Registry for widget types and their configurations.
// Get widget component
const component = registry.getWidgetComponent('kpi');
// Get default size
const size = registry.getDefaultSize('table');
// Get registration
const registration = registry.getRegistration('barChart');Creating Custom Widgets
1. Create Widget Component
@Component({
selector: 'my-custom-widget',
standalone: true,
template: `...`
})
export class MyCustomWidgetComponent implements OnInit {
@Input() config!: MyCustomWidgetConfig;
private readonly dataService = inject(MeshBoardDataService);
private readonly stateService = inject(MeshBoardStateService);
}2. Create Config Dialog Component
@Component({
selector: 'my-custom-config-dialog',
standalone: true,
template: `...`
})
export class MyCustomConfigDialogComponent {
@Input() initialTitle = '';
@Input() initialDataSource?: DataSource;
@Output() save = new EventEmitter<MyCustomConfigResult>();
@Output() cancelled = new EventEmitter<void>();
}3. Register Widget
In your app initialization:
const registry = inject(WidgetRegistryService);
registry.registerWidget({
type: 'myCustom',
component: MyCustomWidgetComponent,
configDialogComponent: MyCustomConfigDialogComponent,
defaultSize: { colSpan: 2, rowSpan: 2 },
getInitialConfig: (widget) => ({
initialTitle: widget.title,
initialDataSource: widget.dataSource
}),
applyConfigResult: (widget, result) => ({
...widget,
title: result.title,
dataSource: result.dataSource,
customOption: result.customOption
})
});Persistence
MeshBoards are persisted using the System.UI/Dashboard and System.UI/DashboardWidget Construction Kit types.
Required CK Model
The MeshBoard feature requires the System.UI CK model version >= 1.0.1.
Backend Structure
- Dashboard: Contains grid configuration (columns, rowHeight, gap) and metadata
- DashboardWidget: Contains widget type, position, and serialized config
Styling
All widget components use CSS custom properties with neutral defaults. Host applications override these to apply their theme.
| Widget | CSS Variable Prefix | Example |
|--------|-------------------|---------|
| Markdown Widget | --mm-prose-* | --mm-prose-text, --mm-prose-heading |
| Stats Grid Widget | --mm-stat-{variant}-* | --mm-stat-mint-bg, --mm-stat-cyan-text |
| Process Designer | --designer-* | --designer-canvas-color, --designer-grid-color |
See the main README Styling Guidelines for details on the CSS variable pattern.
Build
npm run build:octo-meshboardDependencies
@angular/core^21@progress/kendo-angular-charts(Charts)@progress/kendo-angular-gauges(Gauges)@progress/kendo-angular-grid(Table)@progress/kendo-angular-dialog(Dialogs)@progress/kendo-angular-buttons(Buttons)@progress/kendo-angular-inputs(Inputs)ngx-markdown(Markdown rendering)@meshmakers/octo-services(GraphQL services)@meshmakers/octo-ui(UI components)@meshmakers/octo-process-diagrams(Process diagram library)@meshmakers/shared-ui(Shared components)
