@meshmakers/octo-meshboard 3.4.1480 → 3.5.0

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 CHANGED
@@ -72,6 +72,76 @@ To set a Well-Known Name for a MeshBoard:
72
72
 
73
73
  The Well-Known Name should be lowercase with hyphens, similar to URL slugs.
74
74
 
75
+ ### Open in edit mode (`?edit=1`)
76
+
77
+ A `?edit=1` query parameter opens the board in edit mode once it has loaded (e.g. the "Edit"
78
+ action of a host's board list: `/ui/meshboards/<rtId>?edit=1`, constant
79
+ `MESHBOARD_EDIT_QUERY_PARAM`). It is ignored on routes with `meshBoardReadonly: true`. After
80
+ entering edit mode the view removes the parameter from the URL (`replaceUrl`), so a reload or a
81
+ copied link does not re-enter edit mode. Hosts creating boards use `newMeshBoardConfig(name)`
82
+ for the layout defaults (the same `MeshBoardStateService.createNewMeshBoard` uses).
83
+
84
+ ### Header mode (`headerMode` / `meshBoardHeaderMode`)
85
+
86
+ How much of its header the view shows is decided by the **host page**, not by the board: the
87
+ same board appears with its name in a board designer list and without it on a page that
88
+ already names it (e.g. a Home tab under a greeting).
89
+
90
+ | Mode | Shows |
91
+ |------|-------|
92
+ | `full` (default) | Board name, description and the controls |
93
+ | `compact` | Only the controls (time filter, entity selectors, refresh; on editable boards also manager, settings, edit, add widget, save) in a slim row aligned right, without its own bar background |
94
+ | `none` | No header row — for hosts that offer their own controls |
95
+
96
+ Hints (unselected entity selectors, variable resolution errors) and the "MeshBoard Not Found" /
97
+ "MeshBoard Not Available" help are shown in every mode.
98
+
99
+ Bind the input when embedding the view in a template, or set the route data key when a route
100
+ loads the view directly (the bound input wins; unknown values fall back to `full`):
101
+
102
+ ```html
103
+ <mm-meshboard-view headerMode="compact"></mm-meshboard-view>
104
+ ```
105
+
106
+ ```typescript
107
+ {
108
+ path: "boards/:rtId",
109
+ loadComponent: () =>
110
+ import('@meshmakers/octo-meshboard').then(m => m.MeshBoardViewComponent),
111
+ data: { meshBoardReadonly: true, meshBoardHeaderMode: 'compact' }
112
+ }
113
+ ```
114
+
115
+ There is deliberately no per-board setting: a board stored with "no header" would lose its
116
+ name in the designer, where the name is the only orientation. Exported helpers:
117
+ `MeshBoardHeaderMode`, `MESHBOARD_HEADER_MODE_ROUTE_DATA`, `resolveMeshBoardHeaderMode`.
118
+
119
+ ### Chrome (`chrome` / `meshBoardChrome`)
120
+
121
+ Whether the view draws its own **outer frame** is also decided by the host page:
122
+
123
+ | Chrome | Draws |
124
+ |--------|-------|
125
+ | `framed` (default) | The view background, the header bar (background and bottom border) and the padding around the widget grid — for a page that is only the board |
126
+ | `plain` | None of these: the widgets sit directly on the host surface and line up with the host page's gutter. For hosts that already provide the surface and the gutter (e.g. a Home tab), so there is no frame inside the page and no double gutter |
127
+
128
+ The widget frames (tile border, background, shadow, header) and the gap between the widgets are
129
+ the same in both. Like the header mode, bind the input or set the route data key (the bound
130
+ input wins; unknown values fall back to `framed`). Plain usually goes together with a compact
131
+ header:
132
+
133
+ ```html
134
+ <mm-meshboard-view headerMode="compact" chrome="plain"></mm-meshboard-view>
135
+ ```
136
+
137
+ ```typescript
138
+ data: { meshBoardReadonly: true, meshBoardHeaderMode: 'compact', meshBoardChrome: 'plain' }
139
+ ```
140
+
141
+ With `plain` the host is responsible for the gutter: a route that loads the view directly into
142
+ an unpadded outlet should wrap it in a padded page instead. Exported helpers: `MeshBoardChrome`,
143
+ `MESHBOARD_CHROME_ROUTE_DATA`, `resolveMeshBoardChrome`.
144
+
75
145
  ### URL Sync (`meshBoardSyncUrl`)
76
146
 
77
147
  After the initial load and after every post-init board switch (e.g. via the
@@ -102,6 +172,29 @@ routes) never rewrite the URL. Appending an rtId to a route without an
102
172
  `:rtId` variant would fall through to the app's `'**'` wildcard route (bouncing
103
173
  the user back to home) or fail with a `NavigationError` (AB#4457).
104
174
 
175
+ ### Pinning MeshBoards to the host navigation
176
+
177
+ The MeshBoard manager dialog offers a pin toggle per board. A pinned board is
178
+ meant to appear in the host application's end-user navigation (the Refinery
179
+ Studio Home mode lists pinned boards next to its Cockpit).
180
+ Pinning does not change who may *open* a board — the host's routes decide that.
181
+
182
+ The flag is stored with the other encoded board settings in the description
183
+ blob (`navigation: { pinned: true, order?: number }`), so no CK change is
184
+ needed. Host apps read it from a board's raw description:
185
+
186
+ ```typescript
187
+ import { readMeshBoardNavigation, compareMeshBoardNavigation } from '@meshmakers/octo-meshboard';
188
+
189
+ const pinned = boards
190
+ .map(b => ({ ...b, navigation: readMeshBoardNavigation(b.description) }))
191
+ .filter(b => b.navigation?.pinned)
192
+ .sort(compareMeshBoardNavigation);
193
+ ```
194
+
195
+ `MeshBoardStateService.setMeshBoardPinned(rtId, pinned)` toggles the flag and
196
+ keeps every other encoded setting (variables, time filter, auto-refresh, …).
197
+
105
198
  ## Architecture
106
199
 
107
200
  ```
@@ -185,6 +278,29 @@ interface TableWidgetConfig {
185
278
  }
186
279
  ```
187
280
 
281
+ **Texts (AB#5622).** The list inside the table (`mm-list-view`) takes its texts from
282
+ `MESHBOARD_MESSAGES` — "No records available.", pager texts, toolbar labels — merged over the
283
+ English defaults; the host sets them once (fixed object or a signal for a runtime language switch):
284
+
285
+ ```typescript
286
+ providers: [
287
+ provideMeshBoard({ messages: { tableList: { noRecords: 'Keine Einträge vorhanden.', pagerItemsPerPage: 'Einträge pro Seite' } } }),
288
+ // or, when the widgets are registered lazily (no provideMeshBoard()):
289
+ provideMeshBoardMessages(appMeshBoardMessages),
290
+ ]
291
+ ```
292
+
293
+ `tableNotConfigured` translates the placeholder of an unconfigured table.
294
+
295
+ **Enum labels (AB#5622).** `MESHBOARD_LABEL_RESOLVER` (same pattern as the entity forms'
296
+ `ENTITY_FORM_LABEL_RESOLVER`) translates enum values: `(request) => string | null | undefined`
297
+ with `request = { kind: 'tableCell' | 'chartCategory', ckTypeId?, attribute, value, valueType?,
298
+ defaultText }`; `null` / `undefined` / `''` keep `defaultText` (today's text), a throwing resolver
299
+ is logged once and ignored. Tables offer string cells of text columns (persistent-query columns
300
+ only when their value type is `ENUM`); pie / donut charts offer every category — the resolved text
301
+ is the slice name in data labels, legend and tooltips, the status colour still follows the raw
302
+ value. A resolver that reads a signal re-labels the charts when it changes.
303
+
188
304
  ### Pie Chart Widget
189
305
  Displays data as pie or donut chart.
190
306
 
@@ -198,9 +314,34 @@ interface PieChartWidgetConfig {
198
314
  showLegend?: boolean;
199
315
  legendPosition?: 'top' | 'bottom' | 'left' | 'right';
200
316
  filters?: WidgetFilterConfig[];
317
+ categoryColors?: Record<string, string>; // AB#5622
318
+ labelPosition?: 'inside' | 'outside' | 'none'; // AB#5622
319
+ hideLabelsBelowPercent?: number; // AB#5622
201
320
  }
202
321
  ```
203
322
 
323
+ Colours and labels (AB#5622): every slice gets an explicit colour — a `categoryColors` entry
324
+ (key = raw category value, case-insensitive, or the displayed label; value = hex / CSS colour,
325
+ custom property `--brand-paid` / `var(--brand-paid, #2fb37a)`, or a status name `success` /
326
+ `warning` / `error` / `info`), else the status colour of a well-known state, else the theme
327
+ palette colour of its position (`chartSeriesColors()`: `--theme-chart-1…12`, then
328
+ `--kendo-chart-series-1…12`, then `--kendo-color-series-a…f`, then a fixed palette; unresolved or
329
+ invalid tokens are skipped, so no slice falls back to black in dark mode). The palette is part of
330
+ the theme signature, so a live theme switch re-colours the chart without a redraw loop.
331
+ `labelPosition` (labels mode only) maps to Kendo `outsideEnd` (theme-coloured connector lines) /
332
+ `insideEnd` / hidden; `hideLabelsBelowPercent` drops the labels of small slices so neighbours do
333
+ not overlap. All three are optional, written to the board JSON only when set, and edited in the
334
+ config dialog (Label Position, Hide labels below, Category Colours).
335
+
336
+ Display rules (`utils/chart-categories.ts`): enum-style categories read as words (`RESOLVE_FAILED` →
337
+ "Resolve failed"); well-known state categories get the theme status colours (`--theme-status-*`,
338
+ then `--kendo-color-*`; e.g. Resolve failed / Error → error, Available / Online → success,
339
+ Pending → warning); a `left` / `right` legend moves below the chart while the widget is narrower
340
+ than 420 px; the 30 px plot margin is only reserved while labels are shown; legend text is 12 px.
341
+ The status table (`STATE_STATUS_BY_KEY`, `categoryStatus`, `humanizeCategory`, `statusColor`) is
342
+ exported for hosts so enum chips elsewhere read the same; the colours are resolved again on every
343
+ theme switch (`observeThemeChanges`: `<html data-theme|class|style>`, OS colour scheme).
344
+
204
345
  ### Bar Chart Widget
205
346
  Displays data as column, bar, or stacked charts.
206
347
 
@@ -268,7 +409,8 @@ interface MarkdownWidgetConfig {
268
409
  }
269
410
  ```
270
411
 
271
- Requires `provideMarkdown()` from `ngx-markdown` in the application providers.
412
+ `ngx-markdown`/`marked` are loaded lazily on first render (`LazyMarkdownComponent`), so they stay out of the
413
+ initial bundle. No root `provideMarkdown()` is needed; if the application provides one, its configuration is used.
272
414
 
273
415
  ### Entity Card Widget
274
416
  Displays a single runtime entity in a UML-style card.
@@ -292,6 +434,107 @@ interface EntityWithAssociationsWidgetConfig {
292
434
  }
293
435
  ```
294
436
 
437
+ ### Cockpit Widgets (AB#5558)
438
+
439
+ Platform health widgets that used to be hard-wired on the Refinery Studio's Home cockpit. They
440
+ have **no configurable data source** (persisted as `dataSourceType: 'static'`); they read fixed
441
+ platform data and run every check only for viewers with the roles / CK models it needs.
442
+ Registered separately — not by `provideMeshBoard()` — because they need host services:
443
+
444
+ ```typescript
445
+ providers: [
446
+ provideMeshBoard(),
447
+ provideCockpitWidgets(), // registers the 5 types + built-in checks
448
+ provideCockpitWidgetHost({
449
+ access: () => { const auth = inject(AuthorizeService); return { isInRole: r => auth.isInRole(r) }; },
450
+ links: () => myLinkResolver, // CockpitLinkTarget -> app URL (or null)
451
+ explain: () => myAssistantBridge, // optional "✦ Explain"
452
+ recents: () => myRecentItemsSource, // optional, feeds "Recent items"
453
+ messages: () => myCockpitTexts // optional translations (object or signal), AB#5622
454
+ }),
455
+ // optional host checks
456
+ { provide: COCKPIT_ATTENTION_PROVIDERS, useClass: MyCheck, multi: true }
457
+ ]
458
+ ```
459
+
460
+ | Type | Label | Shows | Gate (per check / KPI) | Persisted `config` |
461
+ |------|-------|-------|------------------------|--------------------|
462
+ | `attentionList` | Attention List | Findings, errors first: CK models in ResolveFailed, adapters in error / offline > 10 min, failed pipeline executions in 24 h above a threshold, deployment sites not registered (provider id `pools-unregistered`, kept stable for persisted `providerIds`), features enabled but not installed, plus host checks (Refinery Studio: secrets needing re-entry) | each provider: AdminPanelManagement / CommunicationManagement + `System.Communication` / TenantManagement | `{ "providerIds"?: string[], "maxItems"?: number, "showExplain"?: boolean }` — no `providerIds` = all checks, including ones added later |
463
+ | `adapterStatus` | Adapter Status | Adapters online / expected to run (shared rule `utils/adapter-online.ts`) | CommunicationManagement + `System.Communication` | `{ "showDetail"?: boolean }` |
464
+ | `ckModelState` | CK Model State | CK models available / all; ResolveFailed = error, importing = warning | AdminPanelManagement | `{ "showDetail"?: boolean }` |
465
+ | `recentItems` | Recent Items | The viewer's recently opened pages, entities and boards (most recent first, glyph, kind, relative time as `<time>`), real links; optional "⌘K shows the same list" | none — per user, from the host's `COCKPIT_RECENT_ITEMS`; without it "Not available" + collapsed | `{ "maxItems"?: number }` (default 8, 1–20) |
466
+ | `pipelineExecutions` | Pipeline Executions 24 h | Executions of all data flows, failed count, hourly sparkline (same counting as the Studio's Data Flows list) | CommunicationManagement + `System.Communication` | `{ "showDetail"?: boolean, "showSparkline"?: boolean }` |
467
+ | `tenantCount` | Tenants | Child tenants of the tenant (on the system tenant: all tenants), first ids; links `tenants` | TenantManagement | `{ "showDetail"?: boolean }` |
468
+ | `blueprintUpdates` | Blueprint Updates | Installed blueprints with a newer catalog version (warning); service-managed `System.*` ones only in the detail; links `blueprints` | AdminPanelManagement | `{ "showDetail"?: boolean }` |
469
+ | `servicesHealth` | Services Healthy | Identity, Asset Repository, Bot, Communication Controller (when installed) healthy / all; unhealthy or silent = error, degraded = warning; links `serviceHealth` of the first problem | none | `{ "showDetail"?: boolean }` |
470
+ | `versionInfo` | Version | The host's versions (`COCKPIT_VERSION_SOURCE` / `provideCockpitWidgetHost({ versions })`), first = value; no link | none — without the source "Not available" + collapsed | `{ "showDetail"?: boolean }` |
471
+
472
+ - **Role abstraction.** The library never imports the host's auth: `COCKPIT_VIEWER_ACCESS`
473
+ (`isInRole(role)`) answers role checks, `CkModelService.isModelAvailable` the CK models.
474
+ Without the token every gated check fails closed (hidden). A KPI the viewer may not see sends no
475
+ request. **Builders** (`CockpitViewerAccess.isBuilder()`, host-defined) see *why* ("Needs the
476
+ CommunicationManagement role…" / "No health checks are available for your role"); **other
477
+ viewers** see a neutral "Not available" without role details, and the widget reports
478
+ `setWidgetHiddenForViewer(id, true)` so the board collapses it outside edit mode (empty rows
479
+ close up, `collapseEmptyRows`). An attention list never claims "All clear" without visible checks.
480
+ - **Errors** never show raw messages: tiles say "The figure could not be loaded." (details in the console).
481
+ - **Links.** Findings and KPI tiles carry semantic `CockpitLinkTarget`s (`adapter`, `adapters`,
482
+ `deployment-site`, `deployment-sites`, `dataFlows`, `ckModels`, `tenantSettings`, `secretsReEntry`;
483
+ `pool` / `pools` are the deprecated pre-System.Communication-4.x aliases); the host's
484
+ `COCKPIT_LINK_RESOLVER` maps them to URLs, `null` drops the chip. When the resolver answers `null`
485
+ for a deployment-site kind, the context asks once more with the legacy alias
486
+ (`legacyCockpitLinkTarget`), so hosts that only map `pool` / `pools` keep their links (AB#5842). Host providers may also link
487
+ straight to their own pages (AB#5622): `{ kind: 'route', path, queryParams? }` — `path` is a URL
488
+ path or router commands (`['/', tenantId, 'documents']`), navigated with `routerLink` +
489
+ `queryParams`; relative paths resolve against the route that renders the board. The resolver
490
+ sees route targets too and may return a rewritten path (e.g. with the tenant/language prefix);
491
+ `null` keeps `path`. Route targets stay in the app: a `path` with a URL scheme or a
492
+ protocol-relative start (`//host`) is dropped. `CockpitContextService.resolveLinkTarget()` returns `{ path, queryParams }`
493
+ for custom renderings; `resolveLink()` a URL string with the query appended.
494
+ - **Counts.** `AttentionFinding.count?: number` (AB#5622) renders a badge next to the title
495
+ (formatted in `numberLocale`); without it nothing changes. Of the built-in checks only "Failed
496
+ pipeline executions" sets it — the others name the count in their titles.
497
+ - **Texts (i18n, AB#5622).** Every widget text (severity chips, empty / loading / unavailable
498
+ states, KPI labels, statuses and details, recent-items wording and relative times) comes from
499
+ `CockpitWidgetMessages` with English defaults (`DEFAULT_COCKPIT_WIDGET_MESSAGES`). Provide a
500
+ `Partial<CockpitWidgetMessages>` — or a `Signal` of one to follow a runtime language switch —
501
+ on `COCKPIT_WIDGET_MESSAGES` (or `provideCockpitWidgetHost({ messages })`); a widget used on its
502
+ own also takes a `messages` input. `{count}`-style placeholders; missing members keep English;
503
+ `numberLocale` (default `en-US`) formats the figures. `CockpitKpiService.kpi(kind, messages?)`
504
+ builds KPI texts the same way. Not covered: the config dialogs and the findings of the built-in
505
+ OctoMesh checks except "Failed pipeline executions" (`attentionFailedExecutions*`, optional
506
+ members); a host's own providers bring their own translated titles and texts. Nothing of
507
+ this is persisted — the board JSON is unchanged.
508
+ - **Adding a check.** Implement `AttentionProvider` (`id` — persisted, never rename — `label`,
509
+ `description`, `isVisible` via `CockpitContextService.allows(roles, models)`, `load` = exactly one
510
+ query emitting findings once) and register it on `COCKPIT_ATTENTION_PROVIDERS`.
511
+ - **Failed pipeline executions** (`pipeline-executions-failed`, AB#5622). Same request and
512
+ counting as the "Pipeline executions 24 h" KPI. Warning from `minFailed` failures (default 10),
513
+ error from `errorFailed` failures (default 1,000) or a failure share of `errorRatio` (default
514
+ 0.2); `null` disables an error rule. Override with `{ provide: COCKPIT_FAILED_EXECUTIONS_OPTIONS,
515
+ useValue: { minFailed: 50 } }`. Links to `dataFlows` (the list has no "failed only" filter).
516
+ - **System cockpit checks** (AB#5558). `services-unhealthy` (one finding per platform service
517
+ that is not Healthy, links `serviceHealth`) and `blueprint-updates` (info, count badge, links
518
+ `blueprints`; AdminPanelManagement) are visible on the system tenant only
519
+ (`CockpitContextService.isSystemTenant`, from `systemTenantId` of the platform configuration,
520
+ `octosystem` without it) — tenant cockpits are unchanged. Texts: `attentionService*`,
521
+ `attentionBlueprintUpdates*`, `kpiTenants*`, `kpiBlueprints*`, `kpiServices*`, `kpiVersion*`
522
+ (optional members). Seeded by `System.UI.SystemCockpit` ≥ 1.1.0.
523
+ - **Data.** Lean documents with explicit fields in `graphQL/cockpit*.graphql`; the adapter states
524
+ and the data flow executions are shared per tenant for 10 s between the KPI and the attention
525
+ list (`CockpitAdapterStatesService`, `CockpitDataFlowExecutionsService`).
526
+ - **Recent items source.** `CockpitRecentItemsSource` = `items(limit)` (most recent first, only
527
+ entries the viewer may still open, each with a real `href`), `open(item)` for plain left clicks
528
+ (modified clicks stay with the browser), optional `revision` signal (re-read on change),
529
+ `openPalette()` + `paletteShortcut`. The Refinery Studio feeds it from the same history as the
530
+ empty Cmd+K palette. `recentItems` is not one of `COCKPIT_WIDGET_TYPES` (the health widgets).
531
+ - **Seeded board.** octo-platform-services' `System.UI.TenantCockpit` blueprint (≥ 1.1.0) seeds the
532
+ four health widgets on every tenant's `cockpit` board, since 1.3.0 also "Recently opened"
533
+ (`recentItems`, 3 columns) next to the CK-model pie (3 columns); `cockpit-widget-registrations.spec.ts` pins the
534
+ seed's encoding against `toPersistedConfig`.
535
+ - **Theming.** Neutral defaults via `--mm-cockpit-*` custom properties (text, text-muted, surface,
536
+ border, border-strong, success, warning, error, info, neutral, accent, ai) falling back to Kendo colours.
537
+
295
538
  ### Process Widget
296
539
  Provides HMI-style (Human-Machine Interface) process visualization with tanks, pipes, valves, pumps, and other process elements. Includes a visual drag-and-drop designer.
297
540