@meshmakers/octo-meshboard 3.3.390
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# OctoMesh MeshBoard Library
|
|
2
|
+
|
|
3
|
+
A flexible, widget-based dashboard library for Angular applications in the OctoMesh platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
MeshBoard provides a grid-based dashboard system with configurable widgets that can display data from various sources including runtime entities, persistent queries, and aggregations.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @meshmakers/octo-meshboard
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Basic Integration
|
|
18
|
+
|
|
19
|
+
Add the MeshBoard route to your application:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Routes } from '@angular/router';
|
|
23
|
+
import { UnsavedChangesGuard } from '@meshmakers/shared-ui';
|
|
24
|
+
|
|
25
|
+
export const routes: Routes = [
|
|
26
|
+
{
|
|
27
|
+
path: "dashboard",
|
|
28
|
+
loadComponent: () =>
|
|
29
|
+
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
|
|
30
|
+
canDeactivate: [UnsavedChangesGuard]
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Load Specific MeshBoard by rtId
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
{
|
|
39
|
+
path: "dashboard/:rtId",
|
|
40
|
+
loadComponent: () =>
|
|
41
|
+
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
|
|
42
|
+
canDeactivate: [UnsavedChangesGuard]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Load MeshBoard by Well-Known Name
|
|
47
|
+
|
|
48
|
+
Use `meshBoardWellKnownName` in route data to load a specific MeshBoard:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
{
|
|
52
|
+
path: "cockpit",
|
|
53
|
+
loadComponent: () =>
|
|
54
|
+
import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
|
|
55
|
+
canDeactivate: [UnsavedChangesGuard],
|
|
56
|
+
data: {
|
|
57
|
+
meshBoardWellKnownName: 'cockpit',
|
|
58
|
+
breadcrumb: [{ label: "Cockpit", url: "cockpit" }]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Note:** If the MeshBoard with the specified `rtWellKnownName` does not exist, an error message will be displayed with instructions on how to create it.
|
|
64
|
+
|
|
65
|
+
### Setting the Well-Known Name
|
|
66
|
+
|
|
67
|
+
To set a Well-Known Name for a MeshBoard:
|
|
68
|
+
|
|
69
|
+
1. Open the MeshBoard Settings dialog
|
|
70
|
+
2. Enter a unique identifier in the "Well-Known Name" field (e.g., `cockpit`, `sales-dashboard`)
|
|
71
|
+
3. Save the MeshBoard
|
|
72
|
+
|
|
73
|
+
The Well-Known Name should be lowercase with hyphens, similar to URL slugs.
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
octo-meshboard/
|
|
79
|
+
├── containers/
|
|
80
|
+
│ └── meshboard-view/ # Main view component
|
|
81
|
+
├── widgets/ # Widget implementations
|
|
82
|
+
│ ├── bar-chart-widget/
|
|
83
|
+
│ ├── entity-card-widget/
|
|
84
|
+
│ ├── gauge-widget/
|
|
85
|
+
│ ├── kpi-widget/
|
|
86
|
+
│ ├── pie-chart-widget/
|
|
87
|
+
│ ├── service-health-widget/
|
|
88
|
+
│ ├── stats-grid-widget/
|
|
89
|
+
│ ├── status-indicator-widget/
|
|
90
|
+
│ └── table-widget/
|
|
91
|
+
├── services/
|
|
92
|
+
│ ├── meshboard-state.service.ts # State management
|
|
93
|
+
│ ├── meshboard-data.service.ts # Data fetching
|
|
94
|
+
│ ├── meshboard-persistence.service.ts # Backend persistence
|
|
95
|
+
│ ├── meshboard-variable.service.ts # Variable resolution
|
|
96
|
+
│ ├── widget-factory.service.ts # Widget creation
|
|
97
|
+
│ └── widget-registry.service.ts # Widget registration
|
|
98
|
+
├── dialogs/ # Configuration dialogs
|
|
99
|
+
├── models/ # TypeScript interfaces
|
|
100
|
+
└── graphQL/ # GraphQL queries/mutations
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Widget Types
|
|
104
|
+
|
|
105
|
+
### KPI Widget
|
|
106
|
+
Displays a single value with optional label, prefix, suffix, and trend indicator.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface KpiWidgetConfig {
|
|
110
|
+
type: 'kpi';
|
|
111
|
+
valueAttribute: string;
|
|
112
|
+
labelAttribute?: string;
|
|
113
|
+
prefix?: string;
|
|
114
|
+
suffix?: string;
|
|
115
|
+
icon?: string;
|
|
116
|
+
trend?: 'up' | 'down' | 'neutral';
|
|
117
|
+
// For persistent query mode
|
|
118
|
+
queryMode?: 'simpleCount' | 'aggregation' | 'groupedAggregation';
|
|
119
|
+
queryValueField?: string;
|
|
120
|
+
filters?: WidgetFilterConfig[];
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Gauge Widget
|
|
125
|
+
Displays a numeric value as arc, circular, linear, or radial gauge.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface GaugeWidgetConfig {
|
|
129
|
+
type: 'gauge';
|
|
130
|
+
gaugeType: 'arc' | 'circular' | 'linear' | 'radial';
|
|
131
|
+
valueAttribute: string;
|
|
132
|
+
min?: number;
|
|
133
|
+
max?: number;
|
|
134
|
+
ranges?: GaugeRange[];
|
|
135
|
+
showLabel?: boolean;
|
|
136
|
+
prefix?: string;
|
|
137
|
+
suffix?: string;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Table Widget
|
|
142
|
+
Displays data in a configurable table with sorting and filtering.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface TableWidgetConfig {
|
|
146
|
+
type: 'table';
|
|
147
|
+
columns: TableColumn[];
|
|
148
|
+
sorting?: TableSortConfig[];
|
|
149
|
+
filters?: WidgetFilterConfig[];
|
|
150
|
+
pageSize?: number;
|
|
151
|
+
sortable?: boolean;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Pie Chart Widget
|
|
156
|
+
Displays data as pie or donut chart.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface PieChartWidgetConfig {
|
|
160
|
+
type: 'pieChart';
|
|
161
|
+
chartType: 'pie' | 'donut';
|
|
162
|
+
categoryField: string;
|
|
163
|
+
valueField: string;
|
|
164
|
+
showLabels?: boolean;
|
|
165
|
+
showLegend?: boolean;
|
|
166
|
+
legendPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
167
|
+
filters?: WidgetFilterConfig[];
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Bar Chart Widget
|
|
172
|
+
Displays data as column, bar, or stacked charts.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
interface BarChartWidgetConfig {
|
|
176
|
+
type: 'barChart';
|
|
177
|
+
chartType: 'column' | 'bar' | 'stackedColumn' | 'stackedBar' | 'stackedColumn100' | 'stackedBar100';
|
|
178
|
+
categoryField: string;
|
|
179
|
+
series: BarChartSeries[];
|
|
180
|
+
// Dynamic series mode
|
|
181
|
+
seriesGroupField?: string;
|
|
182
|
+
valueField?: string;
|
|
183
|
+
showLegend?: boolean;
|
|
184
|
+
legendPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
185
|
+
filters?: WidgetFilterConfig[];
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Stats Grid Widget
|
|
190
|
+
Displays multiple KPIs in a grid layout.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
interface StatsGridWidgetConfig {
|
|
194
|
+
type: 'statsGrid';
|
|
195
|
+
stats: StatItem[];
|
|
196
|
+
columns?: number;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Service Health Widget
|
|
201
|
+
Displays service health status with pulse animation.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
interface ServiceHealthWidgetConfig {
|
|
205
|
+
type: 'serviceHealth';
|
|
206
|
+
navigateOnClick?: boolean;
|
|
207
|
+
detailRoute?: string;
|
|
208
|
+
showPulse?: boolean;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Status Indicator Widget
|
|
213
|
+
Displays boolean status (e.g., ENABLED/DISABLED).
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
interface StatusIndicatorWidgetConfig {
|
|
217
|
+
type: 'statusIndicator';
|
|
218
|
+
trueLabel?: string;
|
|
219
|
+
falseLabel?: string;
|
|
220
|
+
trueColor?: string;
|
|
221
|
+
falseColor?: string;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Data Sources
|
|
226
|
+
|
|
227
|
+
### Runtime Entity Data Source
|
|
228
|
+
Fetches a single entity by CK type or rtId.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
interface RuntimeEntityDataSource {
|
|
232
|
+
type: 'runtimeEntity';
|
|
233
|
+
ckTypeId?: string;
|
|
234
|
+
rtId?: string;
|
|
235
|
+
attributePaths?: string[];
|
|
236
|
+
includeAssociations?: boolean;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Persistent Query Data Source
|
|
241
|
+
Executes a saved query by its rtId.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
interface PersistentQueryDataSource {
|
|
245
|
+
type: 'persistentQuery';
|
|
246
|
+
queryRtId: string;
|
|
247
|
+
queryName?: string;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Aggregation Data Source
|
|
252
|
+
Performs aggregation queries (count, sum, avg, min, max).
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
interface AggregationDataSource {
|
|
256
|
+
type: 'aggregation';
|
|
257
|
+
queries: AggregationQuery[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface AggregationQuery {
|
|
261
|
+
id: string;
|
|
262
|
+
ckTypeId: string;
|
|
263
|
+
aggregation: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
264
|
+
attribute?: string;
|
|
265
|
+
filters?: WidgetFilterConfig[];
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Construction Kit Query Data Source
|
|
270
|
+
Queries Construction Kit metadata (models, types, attributes).
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
interface ConstructionKitQueryDataSource {
|
|
274
|
+
type: 'constructionKitQuery';
|
|
275
|
+
queryTarget: 'models' | 'types' | 'attributes' | 'associationRoles' | 'enums' | 'records';
|
|
276
|
+
groupBy?: string;
|
|
277
|
+
valueField?: string;
|
|
278
|
+
categoryField?: string;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Service Call Data Source
|
|
283
|
+
Calls services for status/health information.
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
interface ServiceCallDataSource {
|
|
287
|
+
type: 'serviceCall';
|
|
288
|
+
callType: 'modelAvailable' | 'healthCheck';
|
|
289
|
+
modelName?: string;
|
|
290
|
+
serviceType?: 'identity' | 'asset-repository' | 'bot' | 'communication-controller' | 'mesh-adapter' | 'custom';
|
|
291
|
+
customEndpoint?: string;
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Variables
|
|
296
|
+
|
|
297
|
+
MeshBoard supports variables that can be used in filter values. Variables use the syntax `${variableName}` or `$variableName`.
|
|
298
|
+
|
|
299
|
+
### Defining Variables
|
|
300
|
+
|
|
301
|
+
Variables are defined at the MeshBoard level:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
interface MeshBoardVariable {
|
|
305
|
+
name: string; // Variable name (without $)
|
|
306
|
+
label?: string; // Display label
|
|
307
|
+
description?: string;
|
|
308
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'datetime';
|
|
309
|
+
source: 'static' | 'timeFilter';
|
|
310
|
+
value: string;
|
|
311
|
+
defaultValue?: string;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Using Variables in Filters
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const filter: WidgetFilterConfig = {
|
|
319
|
+
attributePath: 'createdDate',
|
|
320
|
+
operator: 'gte',
|
|
321
|
+
comparisonValue: '${timeRangeFrom}'
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Time Filter Variables
|
|
326
|
+
|
|
327
|
+
When the time filter is enabled, two variables are automatically available:
|
|
328
|
+
- `${timeRangeFrom}` - Start of selected time range (ISO string)
|
|
329
|
+
- `${timeRangeTo}` - End of selected time range (ISO string)
|
|
330
|
+
|
|
331
|
+
## Time Filter
|
|
332
|
+
|
|
333
|
+
Enable a global time filter for the MeshBoard:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
interface MeshBoardTimeFilterConfig {
|
|
337
|
+
enabled: boolean;
|
|
338
|
+
pickerConfig?: {
|
|
339
|
+
availableTypes?: ('year' | 'quarter' | 'month' | 'relative' | 'custom')[];
|
|
340
|
+
minYear?: number;
|
|
341
|
+
maxYear?: number;
|
|
342
|
+
defaultRelativeValue?: number;
|
|
343
|
+
defaultRelativeUnit?: 'hours' | 'days' | 'weeks' | 'months';
|
|
344
|
+
showTime?: boolean;
|
|
345
|
+
};
|
|
346
|
+
selection?: TimeRangeSelection;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Services
|
|
351
|
+
|
|
352
|
+
### MeshBoardStateService
|
|
353
|
+
|
|
354
|
+
Central state management for MeshBoard.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// Load initial MeshBoard
|
|
358
|
+
await stateService.loadInitialMeshBoard();
|
|
359
|
+
|
|
360
|
+
// Switch to specific MeshBoard
|
|
361
|
+
await stateService.switchToMeshBoard(rtId);
|
|
362
|
+
|
|
363
|
+
// Switch by well-known name
|
|
364
|
+
await stateService.switchToMeshBoardByWellKnownName('cockpit');
|
|
365
|
+
|
|
366
|
+
// Save current MeshBoard
|
|
367
|
+
await stateService.saveMeshBoard();
|
|
368
|
+
|
|
369
|
+
// Access state
|
|
370
|
+
const config = stateService.meshBoardConfig();
|
|
371
|
+
const isLoading = stateService.isLoading();
|
|
372
|
+
const availableBoards = stateService.availableMeshBoards();
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### MeshBoardVariableService
|
|
376
|
+
|
|
377
|
+
Resolves variables in filter values.
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Resolve variables in a string
|
|
381
|
+
const resolved = variableService.resolveVariables('${timeRangeFrom}', variables);
|
|
382
|
+
|
|
383
|
+
// Convert filters to DTO with resolved variables
|
|
384
|
+
const dtoFilters = variableService.convertToFieldFilterDto(filters, variables);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### WidgetRegistryService
|
|
388
|
+
|
|
389
|
+
Registry for widget types and their configurations.
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// Get widget component
|
|
393
|
+
const component = registry.getWidgetComponent('kpi');
|
|
394
|
+
|
|
395
|
+
// Get default size
|
|
396
|
+
const size = registry.getDefaultSize('table');
|
|
397
|
+
|
|
398
|
+
// Get registration
|
|
399
|
+
const registration = registry.getRegistration('barChart');
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Creating Custom Widgets
|
|
403
|
+
|
|
404
|
+
### 1. Create Widget Component
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
@Component({
|
|
408
|
+
selector: 'my-custom-widget',
|
|
409
|
+
standalone: true,
|
|
410
|
+
template: `...`
|
|
411
|
+
})
|
|
412
|
+
export class MyCustomWidgetComponent implements OnInit {
|
|
413
|
+
@Input() config!: MyCustomWidgetConfig;
|
|
414
|
+
|
|
415
|
+
private readonly dataService = inject(MeshBoardDataService);
|
|
416
|
+
private readonly stateService = inject(MeshBoardStateService);
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### 2. Create Config Dialog Component
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
@Component({
|
|
424
|
+
selector: 'my-custom-config-dialog',
|
|
425
|
+
standalone: true,
|
|
426
|
+
template: `...`
|
|
427
|
+
})
|
|
428
|
+
export class MyCustomConfigDialogComponent {
|
|
429
|
+
@Input() initialTitle = '';
|
|
430
|
+
@Input() initialDataSource?: DataSource;
|
|
431
|
+
|
|
432
|
+
@Output() save = new EventEmitter<MyCustomConfigResult>();
|
|
433
|
+
@Output() cancelled = new EventEmitter<void>();
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 3. Register Widget
|
|
438
|
+
|
|
439
|
+
In your app initialization:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const registry = inject(WidgetRegistryService);
|
|
443
|
+
|
|
444
|
+
registry.registerWidget({
|
|
445
|
+
type: 'myCustom',
|
|
446
|
+
component: MyCustomWidgetComponent,
|
|
447
|
+
configDialogComponent: MyCustomConfigDialogComponent,
|
|
448
|
+
defaultSize: { colSpan: 2, rowSpan: 2 },
|
|
449
|
+
getInitialConfig: (widget) => ({
|
|
450
|
+
initialTitle: widget.title,
|
|
451
|
+
initialDataSource: widget.dataSource
|
|
452
|
+
}),
|
|
453
|
+
applyConfigResult: (widget, result) => ({
|
|
454
|
+
...widget,
|
|
455
|
+
title: result.title,
|
|
456
|
+
dataSource: result.dataSource,
|
|
457
|
+
customOption: result.customOption
|
|
458
|
+
})
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Persistence
|
|
463
|
+
|
|
464
|
+
MeshBoards are persisted using the `System.UI/Dashboard` and `System.UI/DashboardWidget` Construction Kit types.
|
|
465
|
+
|
|
466
|
+
### Required CK Model
|
|
467
|
+
|
|
468
|
+
The MeshBoard feature requires the `System.UI` CK model version >= 1.0.1.
|
|
469
|
+
|
|
470
|
+
### Backend Structure
|
|
471
|
+
|
|
472
|
+
- **Dashboard**: Contains grid configuration (columns, rowHeight, gap) and metadata
|
|
473
|
+
- **DashboardWidget**: Contains widget type, position, and serialized config
|
|
474
|
+
|
|
475
|
+
## Build
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
npm run build:octo-meshboard
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Dependencies
|
|
482
|
+
|
|
483
|
+
- `@angular/core` >= 17
|
|
484
|
+
- `@progress/kendo-angular-layout` (TileLayout)
|
|
485
|
+
- `@progress/kendo-angular-charts` (Charts)
|
|
486
|
+
- `@progress/kendo-angular-gauges` (Gauges)
|
|
487
|
+
- `@progress/kendo-angular-grid` (Table)
|
|
488
|
+
- `@meshmakers/octo-services` (GraphQL services)
|
|
489
|
+
- `@meshmakers/octo-ui` (UI components)
|
|
490
|
+
- `@meshmakers/shared-ui` (Shared components)
|