@meshmakers/octo-meshboard 3.4.1450 → 3.4.1490

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
  ```
@@ -201,6 +294,15 @@ interface PieChartWidgetConfig {
201
294
  }
202
295
  ```
203
296
 
297
+ Display rules (`utils/chart-categories.ts`): enum-style categories read as words (`RESOLVE_FAILED` →
298
+ "Resolve failed"); well-known state categories get the theme status colours (`--theme-status-*`,
299
+ then `--kendo-color-*`; e.g. Resolve failed / Error → error, Available / Online → success,
300
+ Pending → warning); a `left` / `right` legend moves below the chart while the widget is narrower
301
+ than 420 px; the 30 px plot margin is only reserved while labels are shown; legend text is 12 px.
302
+ The status table (`STATE_STATUS_BY_KEY`, `categoryStatus`, `humanizeCategory`, `statusColor`) is
303
+ exported for hosts so enum chips elsewhere read the same; the colours are resolved again on every
304
+ theme switch (`observeThemeChanges`: `<html data-theme|class|style>`, OS colour scheme).
305
+
204
306
  ### Bar Chart Widget
205
307
  Displays data as column, bar, or stacked charts.
206
308
 
@@ -268,7 +370,8 @@ interface MarkdownWidgetConfig {
268
370
  }
269
371
  ```
270
372
 
271
- Requires `provideMarkdown()` from `ngx-markdown` in the application providers.
373
+ `ngx-markdown`/`marked` are loaded lazily on first render (`LazyMarkdownComponent`), so they stay out of the
374
+ initial bundle. No root `provideMarkdown()` is needed; if the application provides one, its configuration is used.
272
375
 
273
376
  ### Entity Card Widget
274
377
  Displays a single runtime entity in a UML-style card.
@@ -292,6 +395,93 @@ interface EntityWithAssociationsWidgetConfig {
292
395
  }
293
396
  ```
294
397
 
398
+ ### Cockpit Widgets (AB#5558)
399
+
400
+ Platform health widgets that used to be hard-wired on the Refinery Studio's Home cockpit. They
401
+ have **no configurable data source** (persisted as `dataSourceType: 'static'`); they read fixed
402
+ platform data and run every check only for viewers with the roles / CK models it needs.
403
+ Registered separately — not by `provideMeshBoard()` — because they need host services:
404
+
405
+ ```typescript
406
+ providers: [
407
+ provideMeshBoard(),
408
+ provideCockpitWidgets(), // registers the 5 types + built-in checks
409
+ provideCockpitWidgetHost({
410
+ access: () => { const auth = inject(AuthorizeService); return { isInRole: r => auth.isInRole(r) }; },
411
+ links: () => myLinkResolver, // CockpitLinkTarget -> app URL (or null)
412
+ explain: () => myAssistantBridge, // optional "✦ Explain"
413
+ recents: () => myRecentItemsSource, // optional, feeds "Recent items"
414
+ messages: () => myCockpitTexts // optional translations (object or signal), AB#5622
415
+ }),
416
+ // optional host checks
417
+ { provide: COCKPIT_ATTENTION_PROVIDERS, useClass: MyCheck, multi: true }
418
+ ]
419
+ ```
420
+
421
+ | Type | Label | Shows | Gate (per check / KPI) | Persisted `config` |
422
+ |------|-------|-------|------------------------|--------------------|
423
+ | `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, pools not registered, 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 |
424
+ | `adapterStatus` | Adapter Status | Adapters online / expected to run (shared rule `utils/adapter-online.ts`) | CommunicationManagement + `System.Communication` | `{ "showDetail"?: boolean }` |
425
+ | `ckModelState` | CK Model State | CK models available / all; ResolveFailed = error, importing = warning | AdminPanelManagement | `{ "showDetail"?: boolean }` |
426
+ | `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) |
427
+ | `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 }` |
428
+
429
+ - **Role abstraction.** The library never imports the host's auth: `COCKPIT_VIEWER_ACCESS`
430
+ (`isInRole(role)`) answers role checks, `CkModelService.isModelAvailable` the CK models.
431
+ Without the token every gated check fails closed (hidden). A KPI the viewer may not see sends no
432
+ request. **Builders** (`CockpitViewerAccess.isBuilder()`, host-defined) see *why* ("Needs the
433
+ CommunicationManagement role…" / "No health checks are available for your role"); **other
434
+ viewers** see a neutral "Not available" without role details, and the widget reports
435
+ `setWidgetHiddenForViewer(id, true)` so the board collapses it outside edit mode (empty rows
436
+ close up, `collapseEmptyRows`). An attention list never claims "All clear" without visible checks.
437
+ - **Errors** never show raw messages: tiles say "The figure could not be loaded." (details in the console).
438
+ - **Links.** Findings and KPI tiles carry semantic `CockpitLinkTarget`s (`adapter`, `adapters`,
439
+ `pool`, `pools`, `dataFlows`, `ckModels`, `tenantSettings`, `secretsReEntry`); the host's
440
+ `COCKPIT_LINK_RESOLVER` maps them to URLs, `null` drops the chip. Host providers may also link
441
+ straight to their own pages (AB#5622): `{ kind: 'route', path, queryParams? }` — `path` is a URL
442
+ path or router commands (`['/', tenantId, 'documents']`), navigated with `routerLink` +
443
+ `queryParams`; relative paths resolve against the route that renders the board. The resolver
444
+ sees route targets too and may return a rewritten path (e.g. with the tenant/language prefix);
445
+ `null` keeps `path`. Route targets stay in the app: a `path` with a URL scheme or a
446
+ protocol-relative start (`//host`) is dropped. `CockpitContextService.resolveLinkTarget()` returns `{ path, queryParams }`
447
+ for custom renderings; `resolveLink()` a URL string with the query appended.
448
+ - **Counts.** `AttentionFinding.count?: number` (AB#5622) renders a badge next to the title
449
+ (formatted in `numberLocale`); without it nothing changes. Of the built-in checks only "Failed
450
+ pipeline executions" sets it — the others name the count in their titles.
451
+ - **Texts (i18n, AB#5622).** Every widget text (severity chips, empty / loading / unavailable
452
+ states, KPI labels, statuses and details, recent-items wording and relative times) comes from
453
+ `CockpitWidgetMessages` with English defaults (`DEFAULT_COCKPIT_WIDGET_MESSAGES`). Provide a
454
+ `Partial<CockpitWidgetMessages>` — or a `Signal` of one to follow a runtime language switch —
455
+ on `COCKPIT_WIDGET_MESSAGES` (or `provideCockpitWidgetHost({ messages })`); a widget used on its
456
+ own also takes a `messages` input. `{count}`-style placeholders; missing members keep English;
457
+ `numberLocale` (default `en-US`) formats the figures. `CockpitKpiService.kpi(kind, messages?)`
458
+ builds KPI texts the same way. Not covered: the config dialogs and the findings of the built-in
459
+ OctoMesh checks except "Failed pipeline executions" (`attentionFailedExecutions*`, optional
460
+ members); a host's own providers bring their own translated titles and texts. Nothing of
461
+ this is persisted — the board JSON is unchanged.
462
+ - **Adding a check.** Implement `AttentionProvider` (`id` — persisted, never rename — `label`,
463
+ `description`, `isVisible` via `CockpitContextService.allows(roles, models)`, `load` = exactly one
464
+ query emitting findings once) and register it on `COCKPIT_ATTENTION_PROVIDERS`.
465
+ - **Failed pipeline executions** (`pipeline-executions-failed`, AB#5622). Same request and
466
+ counting as the "Pipeline executions 24 h" KPI. Warning from `minFailed` failures (default 10),
467
+ error from `errorFailed` failures (default 1,000) or a failure share of `errorRatio` (default
468
+ 0.2); `null` disables an error rule. Override with `{ provide: COCKPIT_FAILED_EXECUTIONS_OPTIONS,
469
+ useValue: { minFailed: 50 } }`. Links to `dataFlows` (the list has no "failed only" filter).
470
+ - **Data.** Lean documents with explicit fields in `graphQL/cockpit*.graphql`; the adapter states
471
+ and the data flow executions are shared per tenant for 10 s between the KPI and the attention
472
+ list (`CockpitAdapterStatesService`, `CockpitDataFlowExecutionsService`).
473
+ - **Recent items source.** `CockpitRecentItemsSource` = `items(limit)` (most recent first, only
474
+ entries the viewer may still open, each with a real `href`), `open(item)` for plain left clicks
475
+ (modified clicks stay with the browser), optional `revision` signal (re-read on change),
476
+ `openPalette()` + `paletteShortcut`. The Refinery Studio feeds it from the same history as the
477
+ empty Cmd+K palette. `recentItems` is not one of `COCKPIT_WIDGET_TYPES` (the health widgets).
478
+ - **Seeded board.** octo-platform-services' `System.UI.TenantCockpit` blueprint (≥ 1.1.0) seeds the
479
+ four health widgets on every tenant's `cockpit` board, since 1.3.0 also "Recently opened"
480
+ (`recentItems`, 3 columns) next to the CK-model pie (3 columns); `cockpit-widget-registrations.spec.ts` pins the
481
+ seed's encoding against `toPersistedConfig`.
482
+ - **Theming.** Neutral defaults via `--mm-cockpit-*` custom properties (text, text-muted, surface,
483
+ border, border-strong, success, warning, error, info, neutral, accent, ai) falling back to Kendo colours.
484
+
295
485
  ### Process Widget
296
486
  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
487