@meshmakers/octo-meshboard 3.4.1490 → 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.
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { FieldFilterOperatorsDto, CkTypeSelectorService, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, TENANT_ID_PROVIDER, QueryModeDto, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, SeriesResolutionSignalDto, HealthService, AssetRepoService, JobManagementService, SystemCommunicationDeploymentStateDto, SystemCommunicationConfigurationStateDto,
|
|
1
|
+
import { FieldFilterOperatorsDto, CkTypeSelectorService, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, TENANT_ID_PROVIDER, QueryModeDto, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, SeriesResolutionSignalDto, HealthService, AssetRepoService, JobManagementService, CONFIGURATION_SERVICE, SystemCommunicationDeploymentStateDto, SystemCommunicationConfigurationStateDto, HealthStatus } from '@meshmakers/octo-services';
|
|
2
2
|
export { RuntimeEntityDialogDataSource, RuntimeEntitySelectDataSource, TENANT_ID_PROVIDER } from '@meshmakers/octo-services';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild, ChangeDetectionStrategy, Component, Injector, EnvironmentInjector, ApplicationRef, signal, computed, effect, untracked, Directive, NgZone, DestroyRef, ElementRef, input, createEnvironmentInjector, ViewContainerRef, makeEnvironmentProviders, provideAppInitializer,
|
|
4
|
+
import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild, ChangeDetectionStrategy, Component, Injector, EnvironmentInjector, ApplicationRef, signal, computed, effect, untracked, Directive, InjectionToken, isSignal, NgZone, DestroyRef, ElementRef, input, createEnvironmentInjector, ViewContainerRef, makeEnvironmentProviders, provideAppInitializer, output, viewChild } from '@angular/core';
|
|
5
5
|
import * as i1$1 from '@angular/forms';
|
|
6
6
|
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
7
7
|
import { firstValueFrom, from, map, Observable, of, switchMap as switchMap$1, catchError, interval, filter, combineLatest, defer, EMPTY } from 'rxjs';
|
|
@@ -25,7 +25,7 @@ import * as i1$2 from '@progress/kendo-angular-indicators';
|
|
|
25
25
|
import { LoaderModule } from '@progress/kendo-angular-indicators';
|
|
26
26
|
import * as i1$4 from '@progress/kendo-angular-icons';
|
|
27
27
|
import { SVGIconModule } from '@progress/kendo-angular-icons';
|
|
28
|
-
import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, circleIcon, questionCircleIcon, minusCircleIcon, warningTriangleIcon, exclamationCircleIcon, xCircleIcon, checkCircleIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, infoCircleIcon,
|
|
28
|
+
import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, circleIcon, questionCircleIcon, minusCircleIcon, warningTriangleIcon, exclamationCircleIcon, xCircleIcon, checkCircleIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, plusIcon, trashIcon, chartPieIcon, infoCircleIcon, pencilIcon, chartLineIcon, clockArrowRotateIcon, dataIcon, connectorIcon, chartAreaRangeIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, checkIcon, xIcon, uploadIcon, pinIcon, unpinIcon, gearIcon, saveIcon, arrowRotateCwIcon, undoIcon } from '@progress/kendo-svg-icons';
|
|
29
29
|
import { ExpressionEvaluatorService, GetProcessDiagramDtoGQL, GetProcessDiagramsDtoGQL, CreateProcessDiagramDtoGQL, UpdateProcessDiagramDtoGQL, SymbolLibraryService, renderAnimations, getFlowParticlesAnimation, renderFlowParticles } from '@meshmakers/octo-process-diagrams';
|
|
30
30
|
import { NotificationService } from '@progress/kendo-angular-notification';
|
|
31
31
|
import * as i1$6 from '@progress/kendo-angular-gauges';
|
|
@@ -48,7 +48,10 @@ import { BreadCrumbService } from '@meshmakers/shared-services';
|
|
|
48
48
|
* Defines the interfaces for MeshBoard widgets and their data sources
|
|
49
49
|
*/
|
|
50
50
|
/** The cockpit widget types (e.g. to tell whether a board already shows them). */
|
|
51
|
-
const COCKPIT_WIDGET_TYPES = [
|
|
51
|
+
const COCKPIT_WIDGET_TYPES = [
|
|
52
|
+
'attentionList', 'adapterStatus', 'ckModelState', 'pipelineExecutions',
|
|
53
|
+
'tenantCount', 'blueprintUpdates', 'servicesHealth', 'versionInfo'
|
|
54
|
+
];
|
|
52
55
|
/** True for the cockpit widget types. */
|
|
53
56
|
function isCockpitWidgetType(type) {
|
|
54
57
|
return COCKPIT_WIDGET_TYPES.includes(type ?? '');
|
|
@@ -255,6 +258,72 @@ function chartThemeColorsFrom(style) {
|
|
|
255
258
|
function chartThemeColors(doc = typeof document !== 'undefined' ? document : null) {
|
|
256
259
|
return chartThemeColorsFrom(computedRootStyle(doc));
|
|
257
260
|
}
|
|
261
|
+
/** Fixed series colours, the last resort when the host theme defines no chart palette (or no CSS). */
|
|
262
|
+
const FALLBACK_SERIES_COLORS = Object.freeze(['#3b9eff', '#2fb37a', '#e0a43a', '#c861d6', '#64ceb9', '#da9162', '#6c4da8', '#8a94a0']);
|
|
263
|
+
const SERIES_TOKEN_PREFIXES = ['--theme-chart-', '--kendo-chart-series-'];
|
|
264
|
+
const KENDO_SERIES_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
|
265
|
+
const MAX_SERIES_TOKENS = 12;
|
|
266
|
+
/** A resolved custom property value usable as an SVG fill (non-empty, no unresolved `var()`, a valid colour where checkable). */
|
|
267
|
+
function isUsableColor(value) {
|
|
268
|
+
if (!value || value.includes('var(')) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return typeof CSS === 'undefined' || typeof CSS.supports !== 'function' || CSS.supports('color', value);
|
|
272
|
+
}
|
|
273
|
+
function chartSeriesColorsFrom(style) {
|
|
274
|
+
for (const prefix of SERIES_TOKEN_PREFIXES) {
|
|
275
|
+
const colors = [];
|
|
276
|
+
for (let i = 1; i <= MAX_SERIES_TOKENS; i++) {
|
|
277
|
+
const value = style?.getPropertyValue(`${prefix}${i}`).trim();
|
|
278
|
+
if (isUsableColor(value)) {
|
|
279
|
+
colors.push(value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (colors.length) {
|
|
283
|
+
return colors;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const kendo = KENDO_SERIES_LETTERS.map(letter => style?.getPropertyValue(`--kendo-color-series-${letter}`).trim()).filter(isUsableColor);
|
|
287
|
+
return kendo.length ? kendo : [...FALLBACK_SERIES_COLORS];
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The categorical chart palette of the current theme (AB#5622): the host's `--theme-chart-1…12`
|
|
291
|
+
* (Studio tokens), else the Kendo theme's `--kendo-chart-series-1…12`, else
|
|
292
|
+
* `--kendo-color-series-a…f`, else {@link FALLBACK_SERIES_COLORS}. Values that are empty, still
|
|
293
|
+
* contain `var()` or are no valid colour are skipped — Kendo resolves its series colours once per
|
|
294
|
+
* page load, and a token it could not resolve rendered a slice black in dark mode. The charts set
|
|
295
|
+
* these colours explicitly on every data item, so the palette also follows a live theme switch.
|
|
296
|
+
*/
|
|
297
|
+
function chartSeriesColors(doc = typeof document !== 'undefined' ? document : null) {
|
|
298
|
+
return chartSeriesColorsFrom(computedRootStyle(doc));
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Resolves a configured colour (per-category colour of a chart, AB#5622): a status name
|
|
302
|
+
* (`success`, `warning`, `error`, `info` → theme status colour), a custom property (`--brand-paid`
|
|
303
|
+
* or `var(--brand-paid, #2fb37a)`) read from the document root, or any CSS colour (`#2fb37a`,
|
|
304
|
+
* `rgb(…)`) used as it is. Returns `undefined` when it cannot be resolved (the default colour applies).
|
|
305
|
+
*/
|
|
306
|
+
function resolveChartColor(token, doc = typeof document !== 'undefined' ? document : null) {
|
|
307
|
+
// Board JSON is not type-checked: a non-string value (number, object) is no colour.
|
|
308
|
+
const text = typeof token === 'string' ? token.trim() : '';
|
|
309
|
+
if (!text) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
if (text === 'success' || text === 'warning' || text === 'error' || text === 'info') {
|
|
313
|
+
return statusColor(text, doc);
|
|
314
|
+
}
|
|
315
|
+
const variable = /^var\(\s*(--[\w-]+)\s*(?:,\s*(.+))?\)$/.exec(text);
|
|
316
|
+
const name = text.startsWith('--') ? text : variable?.[1];
|
|
317
|
+
if (name) {
|
|
318
|
+
const value = computedRootStyle(doc)?.getPropertyValue(name).trim();
|
|
319
|
+
if (isUsableColor(value)) {
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
const fallback = variable?.[2]?.trim();
|
|
323
|
+
return isUsableColor(fallback) ? fallback : undefined;
|
|
324
|
+
}
|
|
325
|
+
return isUsableColor(text) ? text : undefined;
|
|
326
|
+
}
|
|
258
327
|
/**
|
|
259
328
|
* What the charts' resolved colours depend on: the `data-theme` attribute, the OS colour scheme,
|
|
260
329
|
* the resolved status colours and the chart text / line colours. `class` / `style` of `<html>` are deliberately not part of it —
|
|
@@ -265,7 +334,7 @@ function themeSignature(doc = typeof document !== 'undefined' ? document : null)
|
|
|
265
334
|
// One style computation per signature (it runs on every <html> mutation).
|
|
266
335
|
const style = computedRootStyle(doc);
|
|
267
336
|
const text = chartThemeColorsFrom(style);
|
|
268
|
-
const colors = [...['success', 'warning', 'error', 'info'].map(status => statusColorFrom(status, style)), text.text, text.muted, text.grid].join(',');
|
|
337
|
+
const colors = [...['success', 'warning', 'error', 'info'].map(status => statusColorFrom(status, style)), text.text, text.muted, text.grid, ...chartSeriesColorsFrom(style)].join(',');
|
|
269
338
|
return `${doc?.documentElement?.getAttribute('data-theme') ?? ''}|${dark ? 'dark' : 'light'}|${colors}`;
|
|
270
339
|
}
|
|
271
340
|
/**
|
|
@@ -10150,6 +10219,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
10150
10219
|
type: Input
|
|
10151
10220
|
}] } });
|
|
10152
10221
|
|
|
10222
|
+
/** Default texts of {@link MeshBoardMessages} (the list texts default inside `mm-list-view`). */
|
|
10223
|
+
const DEFAULT_MESHBOARD_MESSAGES = {
|
|
10224
|
+
tableNotConfigured: 'Table not configured',
|
|
10225
|
+
};
|
|
10226
|
+
/**
|
|
10227
|
+
* Optional: the host's translations of the MeshBoard widget texts (English without it). Set it
|
|
10228
|
+
* once app-wide, either directly or via `provideMeshBoard({ messages })` /
|
|
10229
|
+
* `provideMeshBoardMessages(messages)`.
|
|
10230
|
+
*
|
|
10231
|
+
* ```ts
|
|
10232
|
+
* providers: [provideMeshBoardMessages({ tableList: { noRecords: 'Keine Einträge vorhanden.', pagerItemsPerPage: 'Einträge pro Seite' } })]
|
|
10233
|
+
* ```
|
|
10234
|
+
*/
|
|
10235
|
+
const MESHBOARD_MESSAGES = new InjectionToken('MESHBOARD_MESSAGES');
|
|
10236
|
+
/** Reads a {@link MeshBoardMessagesSource} (fixed or signal) as the current host layer. */
|
|
10237
|
+
function readMeshBoardMessagesSource(source) {
|
|
10238
|
+
if (!source) {
|
|
10239
|
+
return null;
|
|
10240
|
+
}
|
|
10241
|
+
return isSignal(source) ? source() ?? null : source;
|
|
10242
|
+
}
|
|
10243
|
+
/** The resolved MeshBoard texts: defaults with the host layer on top (missing / null members keep the default). */
|
|
10244
|
+
function resolveMeshBoardMessages(messages) {
|
|
10245
|
+
return {
|
|
10246
|
+
...DEFAULT_MESHBOARD_MESSAGES,
|
|
10247
|
+
...(messages?.tableList ? { tableList: messages.tableList } : {}),
|
|
10248
|
+
...(messages?.tableNotConfigured ? { tableNotConfigured: messages.tableNotConfigured } : {}),
|
|
10249
|
+
};
|
|
10250
|
+
}
|
|
10251
|
+
/** The resolved MeshBoard messages of {@link MESHBOARD_MESSAGES} as a signal. Call in an injection context. */
|
|
10252
|
+
function injectMeshBoardMessages() {
|
|
10253
|
+
const source = inject(MESHBOARD_MESSAGES, { optional: true });
|
|
10254
|
+
return computed(() => resolveMeshBoardMessages(readMeshBoardMessagesSource(source)));
|
|
10255
|
+
}
|
|
10256
|
+
|
|
10257
|
+
/** App-wide {@link MeshBoardLabelResolver}. Not provided = today's texts. */
|
|
10258
|
+
const MESHBOARD_LABEL_RESOLVER = new InjectionToken('MESHBOARD_LABEL_RESOLVER');
|
|
10259
|
+
const failedResolvers = new WeakSet();
|
|
10260
|
+
/** Resolves one label; the default text when there is no resolver, no translation, or the resolver throws. */
|
|
10261
|
+
function resolveMeshBoardLabel(resolver, request) {
|
|
10262
|
+
if (!resolver) {
|
|
10263
|
+
return request.defaultText;
|
|
10264
|
+
}
|
|
10265
|
+
let text;
|
|
10266
|
+
try {
|
|
10267
|
+
text = resolver(request);
|
|
10268
|
+
}
|
|
10269
|
+
catch (error) {
|
|
10270
|
+
if (!failedResolvers.has(resolver)) {
|
|
10271
|
+
failedResolvers.add(resolver);
|
|
10272
|
+
console.warn('octo-meshboard: the label resolver threw; showing the default texts', error);
|
|
10273
|
+
}
|
|
10274
|
+
return request.defaultText;
|
|
10275
|
+
}
|
|
10276
|
+
return typeof text === 'string' && text !== '' ? text : request.defaultText;
|
|
10277
|
+
}
|
|
10278
|
+
|
|
10153
10279
|
const ICON_MAP = {
|
|
10154
10280
|
'check-circle': checkCircleIcon,
|
|
10155
10281
|
'x-circle': xCircleIcon,
|
|
@@ -10169,6 +10295,13 @@ class TableWidgetComponent {
|
|
|
10169
10295
|
config;
|
|
10170
10296
|
dataSource;
|
|
10171
10297
|
stateService = inject(MeshBoardStateService);
|
|
10298
|
+
/** Host texts (`MESHBOARD_MESSAGES`, AB#5622): list texts (empty state, pager, toolbar) and the placeholder. */
|
|
10299
|
+
texts = injectMeshBoardMessages();
|
|
10300
|
+
/** Host translation of enum cell values (`MESHBOARD_LABEL_RESOLVER`, AB#5622); absent = raw text. */
|
|
10301
|
+
labelResolver = inject(MESHBOARD_LABEL_RESOLVER, { optional: true });
|
|
10302
|
+
/** CK value type per derived column field (persistent queries), for the label resolver. */
|
|
10303
|
+
_queryColumnValueTypes = signal(new Map(), /* @ts-ignore */
|
|
10304
|
+
...(ngDevMode ? [{ debugName: "_queryColumnValueTypes" }] : /* istanbul ignore next */ []));
|
|
10172
10305
|
// Widget state signals
|
|
10173
10306
|
_isLoading = signal(false, /* @ts-ignore */
|
|
10174
10307
|
...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -10188,6 +10321,7 @@ class TableWidgetComponent {
|
|
|
10188
10321
|
*/
|
|
10189
10322
|
onQueryColumnsLoaded(columns) {
|
|
10190
10323
|
if (columns.length > 0) {
|
|
10324
|
+
this._queryColumnValueTypes.set(new Map(columns.map(col => [col.attributePath, col.attributeValueType])));
|
|
10191
10325
|
this._queryColumnsForView.set(columns.map(col => ({
|
|
10192
10326
|
field: col.attributePath,
|
|
10193
10327
|
displayName: this.formatColumnTitle(col.attributePath),
|
|
@@ -10209,12 +10343,41 @@ class TableWidgetComponent {
|
|
|
10209
10343
|
// explicitly typed ('date', 'numeric', 'iso8601', …) keep their own
|
|
10210
10344
|
// rendering, and any value that is not an ISO-8601 date-time passes through
|
|
10211
10345
|
// unchanged. Derived stream-data columns are all 'text', so they're covered.
|
|
10346
|
+
//
|
|
10347
|
+
// With a host label resolver (AB#5622) string cells of those columns are offered to it as
|
|
10348
|
+
// well (enum values such as `PAID` → "Bezahlt"); columns whose query value type is known and
|
|
10349
|
+
// not an enum are skipped. Without a resolver the formatter is unchanged.
|
|
10350
|
+
const resolver = this.labelResolver;
|
|
10351
|
+
const dataSource = this.config?.dataSource;
|
|
10352
|
+
const ckTypeId = dataSource?.type === 'runtimeEntity' ? dataSource.ckTypeId || undefined : undefined;
|
|
10353
|
+
const valueTypes = this._queryColumnValueTypes();
|
|
10212
10354
|
const withDateFormatter = (column) => {
|
|
10213
10355
|
const isPlainText = !column.dataType || column.dataType === 'text';
|
|
10214
10356
|
if (!isPlainText || column.formatter) {
|
|
10215
10357
|
return column;
|
|
10216
10358
|
}
|
|
10217
|
-
|
|
10359
|
+
const valueType = valueTypes.get(column.field) || undefined;
|
|
10360
|
+
const translate = !!resolver && (!valueType || /ENUM/i.test(valueType));
|
|
10361
|
+
if (!translate) {
|
|
10362
|
+
return { ...column, formatter: (value) => formatTableCellValue(value, mode) };
|
|
10363
|
+
}
|
|
10364
|
+
return {
|
|
10365
|
+
...column,
|
|
10366
|
+
formatter: (value) => {
|
|
10367
|
+
const text = formatTableCellValue(value, mode);
|
|
10368
|
+
if (typeof value !== 'string' || value === '') {
|
|
10369
|
+
return text;
|
|
10370
|
+
}
|
|
10371
|
+
return resolveMeshBoardLabel(resolver, {
|
|
10372
|
+
kind: 'tableCell',
|
|
10373
|
+
...(ckTypeId ? { ckTypeId } : {}),
|
|
10374
|
+
attribute: column.field,
|
|
10375
|
+
value,
|
|
10376
|
+
...(valueType ? { valueType } : {}),
|
|
10377
|
+
defaultText: text
|
|
10378
|
+
});
|
|
10379
|
+
}
|
|
10380
|
+
};
|
|
10218
10381
|
};
|
|
10219
10382
|
// If explicit columns are configured, use them (works for both data source types)
|
|
10220
10383
|
if (this.config?.columns && this.config.columns.length > 0) {
|
|
@@ -10281,6 +10444,7 @@ class TableWidgetComponent {
|
|
|
10281
10444
|
// Clear cached query columns when config changes (for persistent queries)
|
|
10282
10445
|
if (this.config?.dataSource?.type === 'persistentQuery') {
|
|
10283
10446
|
this._queryColumnsForView.set([]);
|
|
10447
|
+
this._queryColumnValueTypes.set(new Map());
|
|
10284
10448
|
}
|
|
10285
10449
|
// Defer refresh to next microtask to allow Angular to propagate config to directive
|
|
10286
10450
|
Promise.resolve().then(() => this.refresh());
|
|
@@ -10310,15 +10474,16 @@ class TableWidgetComponent {
|
|
|
10310
10474
|
[showRowCheckBoxes]="false"
|
|
10311
10475
|
[showRowSelectAllCheckBox]="false"
|
|
10312
10476
|
[rowIsClickable]="false"
|
|
10477
|
+
[messages]="texts().tableList"
|
|
10313
10478
|
(queryColumnsLoaded)="onQueryColumnsLoaded($event)">
|
|
10314
10479
|
</mm-list-view>
|
|
10315
10480
|
} @else {
|
|
10316
10481
|
<div class="no-config-overlay">
|
|
10317
|
-
<span>
|
|
10482
|
+
<span>{{ texts().tableNotConfigured }}</span>
|
|
10318
10483
|
</div>
|
|
10319
10484
|
}
|
|
10320
10485
|
</div>
|
|
10321
|
-
`, isInline: true, styles: [".table-widget{height:100%;width:100%;display:flex;flex-direction:column}.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d);font-style:italic}:host ::ng-deep mm-list-view{height:100%}:host ::ng-deep .k-grid{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "autoPageSize", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "rowActions", "maxInlineRowActions", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "persistListState", "listStateKey", "resizable", "actionsColumnWidth", "hideCheckboxesBelow", "cardModeBelow", "collapseCommandsBelow", "hasExternalFilters", "rowClass", "messages", "selectable", "columns", "rowLabelField"], outputs: ["rowClicked", "rowAction", "resetFilters"] }, { kind: "directive", type: TableWidgetDataSourceDirective, selector: "[mmTableWidgetDataSource]", inputs: ["config"], outputs: ["queryColumnsLoaded"], exportAs: ["mmTableWidgetDataSource"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
10486
|
+
`, isInline: true, styles: [".table-widget{height:100%;width:100%;display:flex;flex-direction:column}.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d);font-style:italic}:host ::ng-deep mm-list-view{height:100%}:host ::ng-deep .k-grid{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "autoPageSize", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "rowActions", "maxInlineRowActions", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "persistListState", "listStateKey", "resizable", "actionsColumnWidth", "hideCheckboxesBelow", "cardModeBelow", "collapseCommandsBelow", "hasExternalFilters", "emptyState", "emptyTemplate", "rowClass", "messages", "selectable", "columns", "rowLabelField"], outputs: ["rowClicked", "rowAction", "resetFilters"] }, { kind: "directive", type: TableWidgetDataSourceDirective, selector: "[mmTableWidgetDataSource]", inputs: ["config"], outputs: ["queryColumnsLoaded"], exportAs: ["mmTableWidgetDataSource"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
10322
10487
|
}
|
|
10323
10488
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TableWidgetComponent, decorators: [{
|
|
10324
10489
|
type: Component,
|
|
@@ -10342,11 +10507,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
10342
10507
|
[showRowCheckBoxes]="false"
|
|
10343
10508
|
[showRowSelectAllCheckBox]="false"
|
|
10344
10509
|
[rowIsClickable]="false"
|
|
10510
|
+
[messages]="texts().tableList"
|
|
10345
10511
|
(queryColumnsLoaded)="onQueryColumnsLoaded($event)">
|
|
10346
10512
|
</mm-list-view>
|
|
10347
10513
|
} @else {
|
|
10348
10514
|
<div class="no-config-overlay">
|
|
10349
|
-
<span>
|
|
10515
|
+
<span>{{ texts().tableNotConfigured }}</span>
|
|
10350
10516
|
</div>
|
|
10351
10517
|
}
|
|
10352
10518
|
</div>
|
|
@@ -13128,6 +13294,8 @@ function injectChartTheme() {
|
|
|
13128
13294
|
return theme.asReadonly();
|
|
13129
13295
|
}
|
|
13130
13296
|
|
|
13297
|
+
/** Kendo label position per configured {@link PieChartWidgetConfig.labelPosition}. */
|
|
13298
|
+
const KENDO_LABEL_POSITION = { inside: 'insideEnd', outside: 'outsideEnd' };
|
|
13131
13299
|
const PLOT_AREA_WITH_LABELS = Object.freeze({ background: 'transparent', margin: Object.freeze({ top: 30, right: 30, bottom: 30, left: 30 }) });
|
|
13132
13300
|
const PLOT_AREA_WITHOUT_LABELS = Object.freeze({ background: 'transparent', margin: Object.freeze({ top: 4, right: 4, bottom: 4, left: 4 }) });
|
|
13133
13301
|
class PieChartWidgetComponent {
|
|
@@ -13147,6 +13315,8 @@ class PieChartWidgetComponent {
|
|
|
13147
13315
|
dataService = inject(MeshBoardDataService);
|
|
13148
13316
|
stateService = inject(MeshBoardStateService);
|
|
13149
13317
|
variableService = inject(MeshBoardVariableService);
|
|
13318
|
+
/** Host translation of category names (`MESHBOARD_LABEL_RESOLVER`, AB#5622): slices, legend and tooltips. */
|
|
13319
|
+
labelResolver = inject(MESHBOARD_LABEL_RESOLVER, { optional: true });
|
|
13150
13320
|
config;
|
|
13151
13321
|
// Widget state signals
|
|
13152
13322
|
_isLoading = signal(false, /* @ts-ignore */
|
|
@@ -13165,7 +13335,8 @@ class PieChartWidgetComponent {
|
|
|
13165
13335
|
/** Keeps its reference while categories, values and colours are unchanged (AB#5568). */
|
|
13166
13336
|
chartData = computed(() => {
|
|
13167
13337
|
this.chartTheme();
|
|
13168
|
-
|
|
13338
|
+
const palette = chartSeriesColors();
|
|
13339
|
+
return this._rawData().map((item, index) => this.toItem(item.category, item.value, index, palette));
|
|
13169
13340
|
}, { ...(ngDevMode ? { debugName: "chartData" } : /* istanbul ignore next */ {}), equal: sameChartItems });
|
|
13170
13341
|
error = this._error.asReadonly();
|
|
13171
13342
|
data = computed(() => this.chartData(), /* @ts-ignore */
|
|
@@ -13195,7 +13366,8 @@ class PieChartWidgetComponent {
|
|
|
13195
13366
|
* chart on every hover (AB#5568).
|
|
13196
13367
|
*/
|
|
13197
13368
|
plotArea() {
|
|
13198
|
-
|
|
13369
|
+
const outsideLabels = this.config?.showLabels === true && this.config?.labelPosition !== 'inside' && this.config?.labelPosition !== 'none';
|
|
13370
|
+
return outsideLabels ? PLOT_AREA_WITH_LABELS : PLOT_AREA_WITHOUT_LABELS;
|
|
13199
13371
|
}
|
|
13200
13372
|
/** Margin of the plot area (see `plotArea`). */
|
|
13201
13373
|
plotAreaMargin() {
|
|
@@ -13224,10 +13396,42 @@ class PieChartWidgetComponent {
|
|
|
13224
13396
|
ngOnDestroy() {
|
|
13225
13397
|
this.resizeObserver?.disconnect();
|
|
13226
13398
|
}
|
|
13227
|
-
/**
|
|
13228
|
-
|
|
13399
|
+
/**
|
|
13400
|
+
* Human label and status colour for a raw category value (exposed for tests). The label is the
|
|
13401
|
+
* host resolver's text (AB#5622), else the humanized category; the colour still follows the raw value.
|
|
13402
|
+
*/
|
|
13403
|
+
toItem(rawCategory, value, index = 0, palette = chartSeriesColors()) {
|
|
13404
|
+
const category = this.categoryLabel(rawCategory);
|
|
13229
13405
|
const status = categoryStatus(rawCategory);
|
|
13230
|
-
|
|
13406
|
+
const color = this.configuredColor(rawCategory, category)
|
|
13407
|
+
?? (status ? statusColor(status) : undefined)
|
|
13408
|
+
?? (palette.length ? palette[index % palette.length] : undefined);
|
|
13409
|
+
return { category, value, ...(color ? { color } : {}) };
|
|
13410
|
+
}
|
|
13411
|
+
/** The `categoryColors` entry of a category (raw value, case-insensitive, then the label), resolved. */
|
|
13412
|
+
configuredColor(rawCategory, label) {
|
|
13413
|
+
const colors = this.config?.categoryColors;
|
|
13414
|
+
if (!colors) {
|
|
13415
|
+
return undefined;
|
|
13416
|
+
}
|
|
13417
|
+
const lower = rawCategory.toLowerCase();
|
|
13418
|
+
// Own keys only: `in` would also match inherited members (`constructor`, `toString`, ...).
|
|
13419
|
+
const own = (k) => Object.prototype.hasOwnProperty.call(colors, k);
|
|
13420
|
+
const key = own(rawCategory) ? rawCategory
|
|
13421
|
+
: Object.keys(colors).find(k => k.toLowerCase() === lower) ?? (own(label) ? label : undefined);
|
|
13422
|
+
return key !== undefined ? resolveChartColor(colors[key]) : undefined;
|
|
13423
|
+
}
|
|
13424
|
+
/** Display name of a raw category: the host label resolver, else `humanizeCategory`. */
|
|
13425
|
+
categoryLabel(rawCategory) {
|
|
13426
|
+
const defaultText = humanizeCategory(rawCategory);
|
|
13427
|
+
if (!this.labelResolver) {
|
|
13428
|
+
return defaultText;
|
|
13429
|
+
}
|
|
13430
|
+
const dataSource = this.config?.dataSource;
|
|
13431
|
+
const attribute = dataSource?.type === 'constructionKitQuery'
|
|
13432
|
+
? dataSource.groupBy ?? this.config?.categoryField ?? ''
|
|
13433
|
+
: this.config?.categoryField ?? '';
|
|
13434
|
+
return resolveMeshBoardLabel(this.labelResolver, { kind: 'chartCategory', attribute, value: rawCategory, defaultText });
|
|
13231
13435
|
}
|
|
13232
13436
|
_labelSettings = signal({
|
|
13233
13437
|
visible: false,
|
|
@@ -13237,10 +13441,24 @@ class PieChartWidgetComponent {
|
|
|
13237
13441
|
/** Series label options incl. the theme text colour (new object only when either changes). */
|
|
13238
13442
|
labelSettings = computed(() => ({ ...this._labelSettings(), color: this.chartTheme().text }), /* @ts-ignore */
|
|
13239
13443
|
...(ngDevMode ? [{ debugName: "labelSettings" }] : /* istanbul ignore next */ []));
|
|
13444
|
+
/** Connector lines of outside labels: theme coloured when `labelPosition: 'outside'` is set, else the chart default (AB#5622). */
|
|
13445
|
+
_explicitOutsideLabels = signal(false, /* @ts-ignore */
|
|
13446
|
+
...(ngDevMode ? [{ debugName: "_explicitOutsideLabels" }] : /* istanbul ignore next */ []));
|
|
13447
|
+
connectors = computed(() => this._explicitOutsideLabels()
|
|
13448
|
+
? { color: this.chartTheme().muted, width: 1, padding: 4 }
|
|
13449
|
+
: undefined, /* @ts-ignore */
|
|
13450
|
+
...(ngDevMode ? [{ debugName: "connectors" }] : /* istanbul ignore next */ []));
|
|
13240
13451
|
updateLabelSettings() {
|
|
13452
|
+
const position = this.config?.labelPosition;
|
|
13453
|
+
const minPercent = Math.max(0, Number(this.config?.hideLabelsBelowPercent) || 0);
|
|
13454
|
+
this._explicitOutsideLabels.set(this.config?.showLabels === true && position === 'outside');
|
|
13241
13455
|
this._labelSettings.set({
|
|
13242
|
-
visible: this.config?.showLabels === true,
|
|
13456
|
+
visible: this.config?.showLabels === true && position !== 'none',
|
|
13457
|
+
...(position === 'inside' || position === 'outside' ? { position: KENDO_LABEL_POSITION[position] } : {}),
|
|
13243
13458
|
content: (e) => {
|
|
13459
|
+
if (minPercent > 0 && typeof e.percentage === 'number' && e.percentage * 100 < minPercent) {
|
|
13460
|
+
return '';
|
|
13461
|
+
}
|
|
13244
13462
|
const maxLen = 20;
|
|
13245
13463
|
const name = e.category.length > maxLen ? e.category.substring(0, maxLen) + '...' : e.category;
|
|
13246
13464
|
return `${name}: ${this.formatValue(e.value)}`;
|
|
@@ -13398,14 +13616,27 @@ class PieChartWidgetComponent {
|
|
|
13398
13616
|
<kendo-chart class="chart-container" [plotArea]="plotArea()">
|
|
13399
13617
|
<kendo-chart-area [background]="'transparent'"></kendo-chart-area>
|
|
13400
13618
|
<kendo-chart-series>
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13619
|
+
<!-- Connector lines only bound when outside labels are configured (AB#5622); otherwise the Kendo default stays untouched. -->
|
|
13620
|
+
@if (connectors(); as labelConnectors) {
|
|
13621
|
+
<kendo-chart-series-item
|
|
13622
|
+
[type]="config.chartType"
|
|
13623
|
+
[data]="chartData()"
|
|
13624
|
+
field="value"
|
|
13625
|
+
categoryField="category"
|
|
13626
|
+
colorField="color"
|
|
13627
|
+
[labels]="labelSettings()"
|
|
13628
|
+
[connectors]="labelConnectors">
|
|
13629
|
+
</kendo-chart-series-item>
|
|
13630
|
+
} @else {
|
|
13631
|
+
<kendo-chart-series-item
|
|
13632
|
+
[type]="config.chartType"
|
|
13633
|
+
[data]="chartData()"
|
|
13634
|
+
field="value"
|
|
13635
|
+
categoryField="category"
|
|
13636
|
+
colorField="color"
|
|
13637
|
+
[labels]="labelSettings()">
|
|
13638
|
+
</kendo-chart-series-item>
|
|
13639
|
+
}
|
|
13409
13640
|
</kendo-chart-series>
|
|
13410
13641
|
<kendo-chart-legend
|
|
13411
13642
|
[visible]="config.showLegend !== false"
|
|
@@ -13446,14 +13677,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
13446
13677
|
<kendo-chart class="chart-container" [plotArea]="plotArea()">
|
|
13447
13678
|
<kendo-chart-area [background]="'transparent'"></kendo-chart-area>
|
|
13448
13679
|
<kendo-chart-series>
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13680
|
+
<!-- Connector lines only bound when outside labels are configured (AB#5622); otherwise the Kendo default stays untouched. -->
|
|
13681
|
+
@if (connectors(); as labelConnectors) {
|
|
13682
|
+
<kendo-chart-series-item
|
|
13683
|
+
[type]="config.chartType"
|
|
13684
|
+
[data]="chartData()"
|
|
13685
|
+
field="value"
|
|
13686
|
+
categoryField="category"
|
|
13687
|
+
colorField="color"
|
|
13688
|
+
[labels]="labelSettings()"
|
|
13689
|
+
[connectors]="labelConnectors">
|
|
13690
|
+
</kendo-chart-series-item>
|
|
13691
|
+
} @else {
|
|
13692
|
+
<kendo-chart-series-item
|
|
13693
|
+
[type]="config.chartType"
|
|
13694
|
+
[data]="chartData()"
|
|
13695
|
+
field="value"
|
|
13696
|
+
categoryField="category"
|
|
13697
|
+
colorField="color"
|
|
13698
|
+
[labels]="labelSettings()">
|
|
13699
|
+
</kendo-chart-series-item>
|
|
13700
|
+
}
|
|
13457
13701
|
</kendo-chart-series>
|
|
13458
13702
|
<kendo-chart-legend
|
|
13459
13703
|
[visible]="config.showLegend !== false"
|
|
@@ -13500,6 +13744,20 @@ class PieChartConfigDialogComponent {
|
|
|
13500
13744
|
initialCkQueryTarget;
|
|
13501
13745
|
initialCkGroupBy;
|
|
13502
13746
|
initialFilters;
|
|
13747
|
+
initialCategoryColors;
|
|
13748
|
+
initialLabelPosition;
|
|
13749
|
+
initialHideLabelsBelowPercent;
|
|
13750
|
+
addIcon = plusIcon;
|
|
13751
|
+
removeIcon = trashIcon;
|
|
13752
|
+
/** Rows of the category colour editor (AB#5622). */
|
|
13753
|
+
categoryColorRows = [];
|
|
13754
|
+
nextCategoryColorId = 1;
|
|
13755
|
+
labelPositions = [
|
|
13756
|
+
{ value: undefined, label: 'Default' },
|
|
13757
|
+
{ value: 'outside', label: 'Outside (with connector lines)' },
|
|
13758
|
+
{ value: 'inside', label: 'Inside' },
|
|
13759
|
+
{ value: 'none', label: 'None' }
|
|
13760
|
+
];
|
|
13503
13761
|
searchIcon = searchIcon;
|
|
13504
13762
|
chartPieIcon = chartPieIcon;
|
|
13505
13763
|
// State
|
|
@@ -13546,7 +13804,9 @@ class PieChartConfigDialogComponent {
|
|
|
13546
13804
|
displayMode: 'legend',
|
|
13547
13805
|
legendPosition: 'right',
|
|
13548
13806
|
ckQueryTarget: 'models',
|
|
13549
|
-
ckGroupBy: 'modelState'
|
|
13807
|
+
ckGroupBy: 'modelState',
|
|
13808
|
+
labelPosition: undefined,
|
|
13809
|
+
hideLabelsBelowPercent: 0
|
|
13550
13810
|
};
|
|
13551
13811
|
get isValid() {
|
|
13552
13812
|
if (this.form.dataSourceType === 'persistentQuery') {
|
|
@@ -13584,6 +13844,9 @@ class PieChartConfigDialogComponent {
|
|
|
13584
13844
|
this.form.legendPosition = this.initialLegendPosition ?? 'right';
|
|
13585
13845
|
this.form.ckQueryTarget = this.initialCkQueryTarget ?? 'models';
|
|
13586
13846
|
this.form.ckGroupBy = this.initialCkGroupBy ?? 'modelState';
|
|
13847
|
+
this.form.labelPosition = this.initialLabelPosition;
|
|
13848
|
+
this.form.hideLabelsBelowPercent = this.initialHideLabelsBelowPercent ?? 0;
|
|
13849
|
+
this.categoryColorRows = Object.entries(this.initialCategoryColors ?? {}).map(([category, color]) => ({ id: this.nextCategoryColorId++, category, color }));
|
|
13587
13850
|
this.ignoreTimeFilter = this.initialIgnoreTimeFilter ?? false;
|
|
13588
13851
|
this.entitySelectorId = this.initialEntitySelectorId;
|
|
13589
13852
|
// Initialize filters
|
|
@@ -13717,6 +13980,23 @@ class PieChartConfigDialogComponent {
|
|
|
13717
13980
|
aggregationType: c.aggregationType ?? null
|
|
13718
13981
|
}));
|
|
13719
13982
|
}
|
|
13983
|
+
addCategoryColor() {
|
|
13984
|
+
this.categoryColorRows = [...this.categoryColorRows, { id: this.nextCategoryColorId++, category: '', color: '' }];
|
|
13985
|
+
}
|
|
13986
|
+
removeCategoryColor(id) {
|
|
13987
|
+
this.categoryColorRows = this.categoryColorRows.filter(row => row.id !== id);
|
|
13988
|
+
}
|
|
13989
|
+
/** Value of the native colour picker: the row's hex colour, else black (the picker needs a #rrggbb value). */
|
|
13990
|
+
swatchValue(color) {
|
|
13991
|
+
return /^#[0-9a-f]{6}$/i.test(color.trim()) ? color.trim() : '#000000';
|
|
13992
|
+
}
|
|
13993
|
+
/** The edited category colours; rows without category or colour are dropped; none = undefined. */
|
|
13994
|
+
categoryColorsResult() {
|
|
13995
|
+
const entries = this.categoryColorRows
|
|
13996
|
+
.map(row => [row.category.trim(), row.color.trim()])
|
|
13997
|
+
.filter(([category, color]) => category !== '' && color !== '');
|
|
13998
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
13999
|
+
}
|
|
13720
14000
|
onFiltersChange(updatedFilters) {
|
|
13721
14001
|
this.filters = updatedFilters;
|
|
13722
14002
|
}
|
|
@@ -13741,6 +14021,18 @@ class PieChartConfigDialogComponent {
|
|
|
13741
14021
|
legendPosition: this.form.legendPosition,
|
|
13742
14022
|
filters: filtersDto
|
|
13743
14023
|
};
|
|
14024
|
+
const categoryColors = this.categoryColorsResult();
|
|
14025
|
+
if (categoryColors) {
|
|
14026
|
+
result.categoryColors = categoryColors;
|
|
14027
|
+
}
|
|
14028
|
+
if (this.form.displayMode === 'labels') {
|
|
14029
|
+
if (this.form.labelPosition) {
|
|
14030
|
+
result.labelPosition = this.form.labelPosition;
|
|
14031
|
+
}
|
|
14032
|
+
if (this.form.hideLabelsBelowPercent && this.form.hideLabelsBelowPercent > 0) {
|
|
14033
|
+
result.hideLabelsBelowPercent = this.form.hideLabelsBelowPercent;
|
|
14034
|
+
}
|
|
14035
|
+
}
|
|
13744
14036
|
if (this.form.dataSourceType === 'persistentQuery') {
|
|
13745
14037
|
if (!this.selectedPersistentQuery)
|
|
13746
14038
|
return;
|
|
@@ -13760,7 +14052,7 @@ class PieChartConfigDialogComponent {
|
|
|
13760
14052
|
this.windowRef.close();
|
|
13761
14053
|
}
|
|
13762
14054
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PieChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13763
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: PieChartConfigDialogComponent, isStandalone: true, selector: "mm-pie-chart-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialIgnoreTimeFilter: "initialIgnoreTimeFilter", initialEntitySelectorId: "initialEntitySelectorId", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialValueField: "initialValueField", initialShowLabels: "initialShowLabels", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialCkQueryTarget: "initialCkQueryTarget", initialCkGroupBy: "initialCkGroupBy", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
14055
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: PieChartConfigDialogComponent, isStandalone: true, selector: "mm-pie-chart-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialIgnoreTimeFilter: "initialIgnoreTimeFilter", initialEntitySelectorId: "initialEntitySelectorId", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialValueField: "initialValueField", initialShowLabels: "initialShowLabels", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialCkQueryTarget: "initialCkQueryTarget", initialCkGroupBy: "initialCkGroupBy", initialFilters: "initialFilters", initialCategoryColors: "initialCategoryColors", initialLabelPosition: "initialLabelPosition", initialHideLabelsBelowPercent: "initialHideLabelsBelowPercent" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
13764
14056
|
<div class="config-container">
|
|
13765
14057
|
|
|
13766
14058
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -13956,6 +14248,48 @@ class PieChartConfigDialogComponent {
|
|
|
13956
14248
|
</kendo-dropdownlist>
|
|
13957
14249
|
</div>
|
|
13958
14250
|
}
|
|
14251
|
+
|
|
14252
|
+
@if (form.displayMode === 'labels') {
|
|
14253
|
+
<div class="form-field">
|
|
14254
|
+
<label for="pie-label-position">Label Position</label>
|
|
14255
|
+
<kendo-dropdownlist
|
|
14256
|
+
id="pie-label-position"
|
|
14257
|
+
data-pie-label-position
|
|
14258
|
+
[data]="labelPositions"
|
|
14259
|
+
[textField]="'label'"
|
|
14260
|
+
[valueField]="'value'"
|
|
14261
|
+
[valuePrimitive]="true"
|
|
14262
|
+
[(ngModel)]="form.labelPosition">
|
|
14263
|
+
</kendo-dropdownlist>
|
|
14264
|
+
</div>
|
|
14265
|
+
<div class="form-field">
|
|
14266
|
+
<label for="pie-hide-labels-below">Hide labels of slices below (%)</label>
|
|
14267
|
+
<kendo-numerictextbox
|
|
14268
|
+
id="pie-hide-labels-below"
|
|
14269
|
+
data-pie-hide-labels-below
|
|
14270
|
+
[min]="0"
|
|
14271
|
+
[max]="50"
|
|
14272
|
+
[decimals]="1"
|
|
14273
|
+
[format]="'n1'"
|
|
14274
|
+
[(ngModel)]="form.hideLabelsBelowPercent">
|
|
14275
|
+
</kendo-numerictextbox>
|
|
14276
|
+
<span class="section-hint">Small neighbouring slices no longer overlap; 0 labels every slice.</span>
|
|
14277
|
+
</div>
|
|
14278
|
+
}
|
|
14279
|
+
|
|
14280
|
+
<div class="form-field" data-pie-category-colors>
|
|
14281
|
+
<label>Category Colours</label>
|
|
14282
|
+
<span class="section-hint">Raw category value or label → hex colour, CSS variable (--brand-paid) or status (success, warning, error, info). Others use the theme palette.</span>
|
|
14283
|
+
@for (entry of categoryColorRows; track entry.id) {
|
|
14284
|
+
<div class="category-color-row">
|
|
14285
|
+
<kendo-textbox [(ngModel)]="entry.category" placeholder="Category (e.g. PAID)" [attr.aria-label]="'Category'"></kendo-textbox>
|
|
14286
|
+
<kendo-textbox [(ngModel)]="entry.color" placeholder="#2fb37a or --token" [attr.aria-label]="'Colour'"></kendo-textbox>
|
|
14287
|
+
<input type="color" class="category-color-swatch" [value]="swatchValue(entry.color)" (input)="entry.color = $any($event.target).value" [attr.aria-label]="'Pick colour'" />
|
|
14288
|
+
<button kendoButton fillMode="flat" type="button" [svgIcon]="removeIcon" (click)="removeCategoryColor(entry.id)" [attr.aria-label]="'Remove category colour'" title="Remove"></button>
|
|
14289
|
+
</div>
|
|
14290
|
+
}
|
|
14291
|
+
<button kendoButton fillMode="flat" type="button" [svgIcon]="addIcon" (click)="addCategoryColor()" data-pie-add-category-color>Add category colour</button>
|
|
14292
|
+
</div>
|
|
13959
14293
|
</div>
|
|
13960
14294
|
</div>
|
|
13961
14295
|
|
|
@@ -13970,7 +14304,7 @@ class PieChartConfigDialogComponent {
|
|
|
13970
14304
|
</button>
|
|
13971
14305
|
</div>
|
|
13972
14306
|
</div>
|
|
13973
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio]:not([ngNoCva])[formControlName],input[type=radio]:not([ngNoCva])[formControl],input[type=radio]:not([ngNoCva])[ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: SdTimeFilterToggleComponent, selector: "mm-sd-time-filter-toggle", inputs: ["family", "ignoreTimeFilter"], outputs: ["ignoreTimeFilterChange"] }, { kind: "component", type: EntitySelectorScopePickerComponent, selector: "mm-entity-selector-scope-picker", inputs: ["family", "entitySelectorId", "selectors"], outputs: ["entitySelectorIdChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
14307
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.category-color-row{display:flex;gap:8px;align-items:center;margin-bottom:6px}.category-color-row kendo-textbox{flex:1;min-width:0}.category-color-swatch{width:32px;height:28px;padding:0;border:none;background:none;cursor:pointer}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio]:not([ngNoCva])[formControlName],input[type=radio]:not([ngNoCva])[formControl],input[type=radio]:not([ngNoCva])[ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: SdTimeFilterToggleComponent, selector: "mm-sd-time-filter-toggle", inputs: ["family", "ignoreTimeFilter"], outputs: ["ignoreTimeFilterChange"] }, { kind: "component", type: EntitySelectorScopePickerComponent, selector: "mm-entity-selector-scope-picker", inputs: ["family", "entitySelectorId", "selectors"], outputs: ["entitySelectorIdChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
13974
14308
|
}
|
|
13975
14309
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PieChartConfigDialogComponent, decorators: [{
|
|
13976
14310
|
type: Component,
|
|
@@ -14182,6 +14516,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
14182
14516
|
</kendo-dropdownlist>
|
|
14183
14517
|
</div>
|
|
14184
14518
|
}
|
|
14519
|
+
|
|
14520
|
+
@if (form.displayMode === 'labels') {
|
|
14521
|
+
<div class="form-field">
|
|
14522
|
+
<label for="pie-label-position">Label Position</label>
|
|
14523
|
+
<kendo-dropdownlist
|
|
14524
|
+
id="pie-label-position"
|
|
14525
|
+
data-pie-label-position
|
|
14526
|
+
[data]="labelPositions"
|
|
14527
|
+
[textField]="'label'"
|
|
14528
|
+
[valueField]="'value'"
|
|
14529
|
+
[valuePrimitive]="true"
|
|
14530
|
+
[(ngModel)]="form.labelPosition">
|
|
14531
|
+
</kendo-dropdownlist>
|
|
14532
|
+
</div>
|
|
14533
|
+
<div class="form-field">
|
|
14534
|
+
<label for="pie-hide-labels-below">Hide labels of slices below (%)</label>
|
|
14535
|
+
<kendo-numerictextbox
|
|
14536
|
+
id="pie-hide-labels-below"
|
|
14537
|
+
data-pie-hide-labels-below
|
|
14538
|
+
[min]="0"
|
|
14539
|
+
[max]="50"
|
|
14540
|
+
[decimals]="1"
|
|
14541
|
+
[format]="'n1'"
|
|
14542
|
+
[(ngModel)]="form.hideLabelsBelowPercent">
|
|
14543
|
+
</kendo-numerictextbox>
|
|
14544
|
+
<span class="section-hint">Small neighbouring slices no longer overlap; 0 labels every slice.</span>
|
|
14545
|
+
</div>
|
|
14546
|
+
}
|
|
14547
|
+
|
|
14548
|
+
<div class="form-field" data-pie-category-colors>
|
|
14549
|
+
<label>Category Colours</label>
|
|
14550
|
+
<span class="section-hint">Raw category value or label → hex colour, CSS variable (--brand-paid) or status (success, warning, error, info). Others use the theme palette.</span>
|
|
14551
|
+
@for (entry of categoryColorRows; track entry.id) {
|
|
14552
|
+
<div class="category-color-row">
|
|
14553
|
+
<kendo-textbox [(ngModel)]="entry.category" placeholder="Category (e.g. PAID)" [attr.aria-label]="'Category'"></kendo-textbox>
|
|
14554
|
+
<kendo-textbox [(ngModel)]="entry.color" placeholder="#2fb37a or --token" [attr.aria-label]="'Colour'"></kendo-textbox>
|
|
14555
|
+
<input type="color" class="category-color-swatch" [value]="swatchValue(entry.color)" (input)="entry.color = $any($event.target).value" [attr.aria-label]="'Pick colour'" />
|
|
14556
|
+
<button kendoButton fillMode="flat" type="button" [svgIcon]="removeIcon" (click)="removeCategoryColor(entry.id)" [attr.aria-label]="'Remove category colour'" title="Remove"></button>
|
|
14557
|
+
</div>
|
|
14558
|
+
}
|
|
14559
|
+
<button kendoButton fillMode="flat" type="button" [svgIcon]="addIcon" (click)="addCategoryColor()" data-pie-add-category-color>Add category colour</button>
|
|
14560
|
+
</div>
|
|
14185
14561
|
</div>
|
|
14186
14562
|
</div>
|
|
14187
14563
|
|
|
@@ -14196,7 +14572,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
14196
14572
|
</button>
|
|
14197
14573
|
</div>
|
|
14198
14574
|
</div>
|
|
14199
|
-
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"] }]
|
|
14575
|
+
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.category-color-row{display:flex;gap:8px;align-items:center;margin-bottom:6px}.category-color-row kendo-textbox{flex:1;min-width:0}.category-color-swatch{width:32px;height:28px;padding:0;border:none;background:none;cursor:pointer}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"] }]
|
|
14200
14576
|
}], propDecorators: { querySelector: [{
|
|
14201
14577
|
type: ViewChild,
|
|
14202
14578
|
args: ['querySelector']
|
|
@@ -14230,6 +14606,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
14230
14606
|
type: Input
|
|
14231
14607
|
}], initialFilters: [{
|
|
14232
14608
|
type: Input
|
|
14609
|
+
}], initialCategoryColors: [{
|
|
14610
|
+
type: Input
|
|
14611
|
+
}], initialLabelPosition: [{
|
|
14612
|
+
type: Input
|
|
14613
|
+
}], initialHideLabelsBelowPercent: [{
|
|
14614
|
+
type: Input
|
|
14233
14615
|
}] } });
|
|
14234
14616
|
|
|
14235
14617
|
const CHART_TYPE_MAPPING = {
|
|
@@ -30194,7 +30576,10 @@ function registerDefaultWidgets(registry) {
|
|
|
30194
30576
|
initialShowLabels: pieWidget.showLabels,
|
|
30195
30577
|
initialShowLegend: pieWidget.showLegend,
|
|
30196
30578
|
initialLegendPosition: pieWidget.legendPosition,
|
|
30197
|
-
initialFilters: pieWidget.filters
|
|
30579
|
+
initialFilters: pieWidget.filters,
|
|
30580
|
+
initialCategoryColors: pieWidget.categoryColors,
|
|
30581
|
+
initialLabelPosition: pieWidget.labelPosition,
|
|
30582
|
+
initialHideLabelsBelowPercent: pieWidget.hideLabelsBelowPercent
|
|
30198
30583
|
};
|
|
30199
30584
|
},
|
|
30200
30585
|
applyConfigResult: (widget, result) => {
|
|
@@ -30233,7 +30618,10 @@ function registerDefaultWidgets(registry) {
|
|
|
30233
30618
|
showLabels: result.showLabels,
|
|
30234
30619
|
showLegend: result.showLegend,
|
|
30235
30620
|
legendPosition: result.legendPosition,
|
|
30236
|
-
filters
|
|
30621
|
+
filters,
|
|
30622
|
+
categoryColors: result.categoryColors,
|
|
30623
|
+
labelPosition: result.labelPosition,
|
|
30624
|
+
hideLabelsBelowPercent: result.hideLabelsBelowPercent
|
|
30237
30625
|
};
|
|
30238
30626
|
},
|
|
30239
30627
|
// SOLID: Factory function
|
|
@@ -30266,7 +30654,8 @@ function registerDefaultWidgets(registry) {
|
|
|
30266
30654
|
legendPosition: widget.legendPosition,
|
|
30267
30655
|
ckQueryTarget: ckDataSource.queryTarget,
|
|
30268
30656
|
ckGroupBy: ckDataSource.groupBy,
|
|
30269
|
-
filters: widget.filters
|
|
30657
|
+
filters: widget.filters,
|
|
30658
|
+
...pieChartPresentationConfig(widget)
|
|
30270
30659
|
}
|
|
30271
30660
|
};
|
|
30272
30661
|
}
|
|
@@ -30292,7 +30681,8 @@ function registerDefaultWidgets(registry) {
|
|
|
30292
30681
|
...(dataSource.entitySelectorId && {
|
|
30293
30682
|
entitySelectorId: dataSource.entitySelectorId
|
|
30294
30683
|
}),
|
|
30295
|
-
filters: widget.filters
|
|
30684
|
+
filters: widget.filters,
|
|
30685
|
+
...pieChartPresentationConfig(widget)
|
|
30296
30686
|
}
|
|
30297
30687
|
};
|
|
30298
30688
|
},
|
|
@@ -30316,7 +30706,8 @@ function registerDefaultWidgets(registry) {
|
|
|
30316
30706
|
showLabels: config['showLabels'] ?? false,
|
|
30317
30707
|
showLegend: config['showLegend'] ?? true,
|
|
30318
30708
|
legendPosition: config['legendPosition'] ?? 'right',
|
|
30319
|
-
filters: config['filters']
|
|
30709
|
+
filters: config['filters'],
|
|
30710
|
+
...readPieChartPresentationConfig(config)
|
|
30320
30711
|
};
|
|
30321
30712
|
}
|
|
30322
30713
|
// Default: persistentQuery
|
|
@@ -30332,7 +30723,8 @@ function registerDefaultWidgets(registry) {
|
|
|
30332
30723
|
showLabels: config['showLabels'] ?? false,
|
|
30333
30724
|
showLegend: config['showLegend'] ?? true,
|
|
30334
30725
|
legendPosition: config['legendPosition'] ?? 'right',
|
|
30335
|
-
filters: config['filters']
|
|
30726
|
+
filters: config['filters'],
|
|
30727
|
+
...readPieChartPresentationConfig(config)
|
|
30336
30728
|
};
|
|
30337
30729
|
}
|
|
30338
30730
|
});
|
|
@@ -31499,6 +31891,32 @@ function provideDefaultWidgets() {
|
|
|
31499
31891
|
})
|
|
31500
31892
|
]);
|
|
31501
31893
|
}
|
|
31894
|
+
/**
|
|
31895
|
+
* Optional presentation settings of a pie chart for the board JSON (AB#5622): written only when
|
|
31896
|
+
* set, so boards without them persist exactly as before.
|
|
31897
|
+
*/
|
|
31898
|
+
function pieChartPresentationConfig(widget) {
|
|
31899
|
+
return {
|
|
31900
|
+
...(widget.categoryColors && Object.keys(widget.categoryColors).length ? { categoryColors: widget.categoryColors } : {}),
|
|
31901
|
+
...(widget.labelPosition ? { labelPosition: widget.labelPosition } : {}),
|
|
31902
|
+
...(widget.hideLabelsBelowPercent ? { hideLabelsBelowPercent: widget.hideLabelsBelowPercent } : {})
|
|
31903
|
+
};
|
|
31904
|
+
}
|
|
31905
|
+
/** The string-valued entries of a JSON object (non-string colour values are dropped). */
|
|
31906
|
+
function stringEntries(record) {
|
|
31907
|
+
return Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === 'string'));
|
|
31908
|
+
}
|
|
31909
|
+
/** Reads {@link pieChartPresentationConfig} back; unknown or missing values are left out. */
|
|
31910
|
+
function readPieChartPresentationConfig(config) {
|
|
31911
|
+
const colors = config['categoryColors'];
|
|
31912
|
+
const position = config['labelPosition'];
|
|
31913
|
+
const minPercent = config['hideLabelsBelowPercent'];
|
|
31914
|
+
return {
|
|
31915
|
+
...(colors && typeof colors === 'object' && !Array.isArray(colors) ? { categoryColors: stringEntries(colors) } : {}),
|
|
31916
|
+
...(position === 'inside' || position === 'outside' || position === 'none' ? { labelPosition: position } : {}),
|
|
31917
|
+
...(typeof minPercent === 'number' && minPercent > 0 ? { hideLabelsBelowPercent: minPercent } : {})
|
|
31918
|
+
};
|
|
31919
|
+
}
|
|
31502
31920
|
/**
|
|
31503
31921
|
* Injection token for MeshBoard options
|
|
31504
31922
|
*/
|
|
@@ -31530,6 +31948,9 @@ function provideMeshBoard(options) {
|
|
|
31530
31948
|
const providers = [
|
|
31531
31949
|
{ provide: MESHBOARD_OPTIONS, useValue: opts }
|
|
31532
31950
|
];
|
|
31951
|
+
if (opts.messages) {
|
|
31952
|
+
providers.push({ provide: MESHBOARD_MESSAGES, useValue: opts.messages });
|
|
31953
|
+
}
|
|
31533
31954
|
// Include default widgets unless explicitly disabled
|
|
31534
31955
|
if (opts.includeDefaultWidgets !== false) {
|
|
31535
31956
|
providers.push(provideAppInitializer(() => {
|
|
@@ -31539,6 +31960,13 @@ function provideMeshBoard(options) {
|
|
|
31539
31960
|
}
|
|
31540
31961
|
return makeEnvironmentProviders(providers);
|
|
31541
31962
|
}
|
|
31963
|
+
/**
|
|
31964
|
+
* Provides the host translations of the MeshBoard widget texts (`MESHBOARD_MESSAGES`, AB#5622)
|
|
31965
|
+
* without `provideMeshBoard()` — for apps that register the widgets lazily.
|
|
31966
|
+
*/
|
|
31967
|
+
function provideMeshBoardMessages(messages) {
|
|
31968
|
+
return makeEnvironmentProviders([{ provide: MESHBOARD_MESSAGES, useValue: messages }]);
|
|
31969
|
+
}
|
|
31542
31970
|
/**
|
|
31543
31971
|
* Provider function for registering custom widgets.
|
|
31544
31972
|
* Use this to add your own widget types to MeshBoard.
|
|
@@ -32040,15 +32468,21 @@ function rowSpanForContent(contentHeight, rowHeight, gap, minRows) {
|
|
|
32040
32468
|
const rows = Math.ceil((contentHeight + TILE_CHROME_HEIGHT + Math.max(0, gap)) / (rowHeight + Math.max(0, gap)));
|
|
32041
32469
|
return Math.max(minRows, Math.min(MAX_CONTENT_ROWS, rows));
|
|
32042
32470
|
}
|
|
32471
|
+
/**
|
|
32472
|
+
* Widget types whose tile shrinks to its content on the native (desktop) tier (AB#5622): the
|
|
32473
|
+
* cockpit "Needs attention" list, which otherwise leaves a large empty tile with few findings.
|
|
32474
|
+
*/
|
|
32475
|
+
const DESKTOP_CONTENT_FIT_TYPES = new Set(['attentionList']);
|
|
32043
32476
|
function placeWidgetsForTier(widgets, tier, configuredColumns, contentSizing) {
|
|
32044
32477
|
if (tier === 'none') {
|
|
32045
|
-
|
|
32478
|
+
const placements = widgets.map(widget => ({
|
|
32046
32479
|
widget,
|
|
32047
32480
|
col: widget.col,
|
|
32048
32481
|
row: widget.row,
|
|
32049
32482
|
colSpan: widget.colSpan,
|
|
32050
32483
|
rowSpan: widget.rowSpan
|
|
32051
32484
|
}));
|
|
32485
|
+
return contentSizing ? fitDesktopTilesToContent(placements, contentSizing) : placements;
|
|
32052
32486
|
}
|
|
32053
32487
|
const columns = columnsForTier(tier, configuredColumns);
|
|
32054
32488
|
const placements = [...widgets]
|
|
@@ -32071,6 +32505,53 @@ function placeWidgetsForTier(widgets, tier, configuredColumns, contentSizing) {
|
|
|
32071
32505
|
}
|
|
32072
32506
|
return placements;
|
|
32073
32507
|
}
|
|
32508
|
+
/**
|
|
32509
|
+
* Native tier (AB#5622): {@link DESKTOP_CONTENT_FIT_TYPES} tiles take only the rows their content
|
|
32510
|
+
* needs — between 1 and the configured `rowSpan`, never more, so neighbours never overlap. Rows
|
|
32511
|
+
* that only the shrunk tiles occupied are then removed and the widgets below move up; rows that
|
|
32512
|
+
* were already empty in the stored layout stay as designed. Presentation only: the configs (and
|
|
32513
|
+
* thus the persisted row spans) are untouched. Mutates and returns `placements`.
|
|
32514
|
+
*/
|
|
32515
|
+
function fitDesktopTilesToContent(placements, sizing) {
|
|
32516
|
+
const occupiedBefore = occupiedRows(placements);
|
|
32517
|
+
let changed = false;
|
|
32518
|
+
for (const placement of placements) {
|
|
32519
|
+
const height = sizing.heights.get(placement.widget.id);
|
|
32520
|
+
if (height === undefined || !DESKTOP_CONTENT_FIT_TYPES.has(placement.widget.type)) {
|
|
32521
|
+
continue;
|
|
32522
|
+
}
|
|
32523
|
+
const rows = Math.min(placement.widget.rowSpan, rowSpanForContent(height, sizing.rowHeight, sizing.gap, 1));
|
|
32524
|
+
if (rows < placement.rowSpan) {
|
|
32525
|
+
placement.rowSpan = rows;
|
|
32526
|
+
changed = true;
|
|
32527
|
+
}
|
|
32528
|
+
}
|
|
32529
|
+
if (!changed) {
|
|
32530
|
+
return placements;
|
|
32531
|
+
}
|
|
32532
|
+
const occupiedAfter = occupiedRows(placements);
|
|
32533
|
+
const freed = [...occupiedBefore].filter(row => !occupiedAfter.has(row));
|
|
32534
|
+
if (freed.length > 0) {
|
|
32535
|
+
for (const placement of placements) {
|
|
32536
|
+
const row = placement.row ?? 1;
|
|
32537
|
+
const shift = freed.filter(freedRow => freedRow < row).length;
|
|
32538
|
+
if (shift > 0) {
|
|
32539
|
+
placement.row = row - shift;
|
|
32540
|
+
}
|
|
32541
|
+
}
|
|
32542
|
+
}
|
|
32543
|
+
return placements;
|
|
32544
|
+
}
|
|
32545
|
+
function occupiedRows(placements) {
|
|
32546
|
+
const rows = new Set();
|
|
32547
|
+
for (const placement of placements) {
|
|
32548
|
+
const start = placement.row ?? 1;
|
|
32549
|
+
for (let row = start; row < start + Math.max(1, placement.rowSpan); row++) {
|
|
32550
|
+
rows.add(row);
|
|
32551
|
+
}
|
|
32552
|
+
}
|
|
32553
|
+
return rows;
|
|
32554
|
+
}
|
|
32074
32555
|
/**
|
|
32075
32556
|
* Keeps widgets that share a row side by side on the reduced grid (AB#5558): two span-3 widgets
|
|
32076
32557
|
* of a 6-column board both round to 2 of 3 columns and would wrap onto two half-empty rows. When
|
|
@@ -32710,6 +33191,14 @@ class AddWidgetDialogComponent {
|
|
|
32710
33191
|
return dataIcon;
|
|
32711
33192
|
case 'pipelineExecutions':
|
|
32712
33193
|
return chartAreaRangeIcon;
|
|
33194
|
+
case 'tenantCount':
|
|
33195
|
+
return dataIcon;
|
|
33196
|
+
case 'blueprintUpdates':
|
|
33197
|
+
return dataIcon;
|
|
33198
|
+
case 'servicesHealth':
|
|
33199
|
+
return connectorIcon;
|
|
33200
|
+
case 'versionInfo':
|
|
33201
|
+
return dataIcon;
|
|
32713
33202
|
case 'recentItems':
|
|
32714
33203
|
return clockArrowRotateIcon;
|
|
32715
33204
|
default:
|
|
@@ -32759,6 +33248,14 @@ class AddWidgetDialogComponent {
|
|
|
32759
33248
|
return 'Construction Kit models available, ResolveFailed as error';
|
|
32760
33249
|
case 'pipelineExecutions':
|
|
32761
33250
|
return 'Pipeline executions in the last 24 hours with failures and an hourly sparkline';
|
|
33251
|
+
case 'tenantCount':
|
|
33252
|
+
return 'Child tenants of this tenant (system cockpit)';
|
|
33253
|
+
case 'blueprintUpdates':
|
|
33254
|
+
return 'Installed blueprints with a newer catalog version';
|
|
33255
|
+
case 'servicesHealth':
|
|
33256
|
+
return 'Platform services whose health check reports Healthy';
|
|
33257
|
+
case 'versionInfo':
|
|
33258
|
+
return 'Versions the host application knows';
|
|
32762
33259
|
case 'recentItems':
|
|
32763
33260
|
return 'Pages, entities and boards the viewer opened recently — personal, from the host application';
|
|
32764
33261
|
default:
|
|
@@ -33730,8 +34227,10 @@ class MeshBoardViewComponent {
|
|
|
33730
34227
|
displayGridWidgets = computed(() => {
|
|
33731
34228
|
const tier = this.compactTier();
|
|
33732
34229
|
const config = this.config();
|
|
33733
|
-
// Phone tier
|
|
33734
|
-
|
|
34230
|
+
// Phone tier (AB#5558): content-sized widgets grow their tile to fit. Native tier outside edit
|
|
34231
|
+
// mode (AB#5622): the attention list shrinks to its content, at most its stored rows — edit
|
|
34232
|
+
// mode keeps the stored spans so drag/resize never persists a content-fitted size.
|
|
34233
|
+
const contentSizing = tier === 'phone' || (tier === 'none' && !this.isEditMode())
|
|
33735
34234
|
? { heights: this.stateService.widgetContentHeights(), rowHeight: config.rowHeight, gap: config.gap }
|
|
33736
34235
|
: undefined;
|
|
33737
34236
|
return placeWidgetsForTier(this.gridWidgets(), tier, config.columns, contentSizing);
|
|
@@ -35010,6 +35509,36 @@ const DEFAULT_COCKPIT_WIDGET_MESSAGES = {
|
|
|
35010
35509
|
kpiCkModelsResolveFailed: '{count} ResolveFailed',
|
|
35011
35510
|
kpiCkModelsImportingStatus: 'Importing',
|
|
35012
35511
|
kpiCkModelsAllAvailable: 'All available',
|
|
35512
|
+
kpiNeedsTenantManagement: 'Needs the TenantManagement role.',
|
|
35513
|
+
kpiTenantsLabel: 'Tenants',
|
|
35514
|
+
kpiTenantsRegistered: 'Registered',
|
|
35515
|
+
kpiTenantsNone: 'No child tenants',
|
|
35516
|
+
kpiBlueprintsLabel: 'Blueprint updates',
|
|
35517
|
+
kpiBlueprintsUpToDate: 'Up to date',
|
|
35518
|
+
kpiBlueprintsAvailable: '{count} available',
|
|
35519
|
+
kpiBlueprintsNone: 'No blueprints installed',
|
|
35520
|
+
kpiBlueprintsInstalled: '{count} installed',
|
|
35521
|
+
kpiBlueprintsServiceManaged: '{count} service-managed pending',
|
|
35522
|
+
kpiBlueprintsTruncated: 'based on the first {read} of {total} catalog entries',
|
|
35523
|
+
kpiBlueprintsScanning: 'Scanning catalogs…',
|
|
35524
|
+
kpiServicesLabel: 'Services healthy',
|
|
35525
|
+
kpiServicesAllHealthy: 'All healthy',
|
|
35526
|
+
kpiServicesUnhealthy: '{count} unhealthy',
|
|
35527
|
+
kpiServicesDegraded: '{count} degraded',
|
|
35528
|
+
serviceStateDegraded: 'degraded',
|
|
35529
|
+
serviceStateUnhealthy: 'unhealthy',
|
|
35530
|
+
serviceStateUnknown: 'not reachable',
|
|
35531
|
+
kpiVersionLabel: 'Version',
|
|
35532
|
+
kpiVersionNone: 'No version information',
|
|
35533
|
+
attentionServiceTitle: '{name} service is {state}',
|
|
35534
|
+
attentionServiceText: 'Its health check reports a problem. The health details list the failing checks.',
|
|
35535
|
+
attentionServiceUnreachableText: 'Its health endpoint does not answer. Features that use the service may fail.',
|
|
35536
|
+
attentionServiceLink: 'Open health details',
|
|
35537
|
+
attentionServiceExplain: 'Why is the {name} service {state}?',
|
|
35538
|
+
attentionBlueprintUpdatesTitle: 'Blueprint updates available',
|
|
35539
|
+
attentionBlueprintUpdatesText: '{list}. Updates are applied on the Blueprints page.',
|
|
35540
|
+
attentionBlueprintUpdatesServiceManaged: 'Service-managed: {list} — applied by the owning service when it starts.',
|
|
35541
|
+
attentionBlueprintUpdatesLink: 'Open blueprints',
|
|
35013
35542
|
recentLoadFailed: 'Recently opened items could not be read.',
|
|
35014
35543
|
recentListLabel: 'Recently opened',
|
|
35015
35544
|
recentEmpty: 'Nothing opened yet. Pages, entities and boards you open appear here.',
|
|
@@ -35081,12 +35610,29 @@ const COCKPIT_ROLES = {
|
|
|
35081
35610
|
* never run, so a board embedded in an app without this token shows no builder findings.
|
|
35082
35611
|
*/
|
|
35083
35612
|
const COCKPIT_VIEWER_ACCESS = new InjectionToken('COCKPIT_VIEWER_ACCESS');
|
|
35613
|
+
/**
|
|
35614
|
+
* The deprecated pre-4.x alias of a deployment-site target (`deployment-site` → `pool`,
|
|
35615
|
+
* `deployment-sites` → `pools`), or `null` for every other target. Used as the fallback when a
|
|
35616
|
+
* host's resolver has no page for the new kind yet (AB#5842).
|
|
35617
|
+
*/
|
|
35618
|
+
function legacyCockpitLinkTarget(target) {
|
|
35619
|
+
switch (target.kind) {
|
|
35620
|
+
case 'deployment-site':
|
|
35621
|
+
return { kind: 'pool', rtId: target.rtId };
|
|
35622
|
+
case 'deployment-sites':
|
|
35623
|
+
return { kind: 'pools' };
|
|
35624
|
+
default:
|
|
35625
|
+
return null;
|
|
35626
|
+
}
|
|
35627
|
+
}
|
|
35084
35628
|
/** The host's link resolver; without it findings and KPI tiles carry no links. */
|
|
35085
35629
|
const COCKPIT_LINK_RESOLVER = new InjectionToken('COCKPIT_LINK_RESOLVER');
|
|
35086
35630
|
/** Optional: without it the attention list shows no "✦ Explain" buttons. */
|
|
35087
35631
|
const COCKPIT_EXPLAIN_HANDLER = new InjectionToken('COCKPIT_EXPLAIN_HANDLER');
|
|
35088
35632
|
/** The host's recent items; without it the "Recent items" widget says "Not available" and collapses. */
|
|
35089
35633
|
const COCKPIT_RECENT_ITEMS = new InjectionToken('COCKPIT_RECENT_ITEMS');
|
|
35634
|
+
/** The host's versions; without it the "Version" tile says "Not available" and collapses. */
|
|
35635
|
+
const COCKPIT_VERSION_SOURCE = new InjectionToken('COCKPIT_VERSION_SOURCE');
|
|
35090
35636
|
/**
|
|
35091
35637
|
* Provides the host services of the cockpit widgets. The factories run in an injection context,
|
|
35092
35638
|
* so they may `inject()` the application's services:
|
|
@@ -35112,6 +35658,9 @@ function provideCockpitWidgetHost(host) {
|
|
|
35112
35658
|
if (host.recents) {
|
|
35113
35659
|
providers.push({ provide: COCKPIT_RECENT_ITEMS, useFactory: host.recents });
|
|
35114
35660
|
}
|
|
35661
|
+
if (host.versions) {
|
|
35662
|
+
providers.push({ provide: COCKPIT_VERSION_SOURCE, useFactory: host.versions });
|
|
35663
|
+
}
|
|
35115
35664
|
if (host.messages) {
|
|
35116
35665
|
providers.push({ provide: COCKPIT_WIDGET_MESSAGES, useFactory: host.messages });
|
|
35117
35666
|
}
|
|
@@ -35129,6 +35678,7 @@ class CockpitContextService {
|
|
|
35129
35678
|
links = inject(COCKPIT_LINK_RESOLVER, { optional: true });
|
|
35130
35679
|
explainHandler = inject(COCKPIT_EXPLAIN_HANDLER, { optional: true });
|
|
35131
35680
|
ckModelService = inject(CkModelService);
|
|
35681
|
+
configuration = inject(CONFIGURATION_SERVICE, { optional: true });
|
|
35132
35682
|
/** The current tenant, or `null` when the host provides none. */
|
|
35133
35683
|
async tenantId() {
|
|
35134
35684
|
try {
|
|
@@ -35157,6 +35707,21 @@ class CockpitContextService {
|
|
|
35157
35707
|
return false;
|
|
35158
35708
|
}
|
|
35159
35709
|
}
|
|
35710
|
+
/**
|
|
35711
|
+
* Whether `tenantId` is the installation's system tenant (`systemTenantId` of the platform
|
|
35712
|
+
* configuration, `octosystem` when the host has none). Checks about the whole installation
|
|
35713
|
+
* (service health, AB#5558) run only there.
|
|
35714
|
+
*/
|
|
35715
|
+
isSystemTenant(tenantId) {
|
|
35716
|
+
let systemTenantId;
|
|
35717
|
+
try {
|
|
35718
|
+
systemTenantId = this.configuration?.config?.systemTenantId;
|
|
35719
|
+
}
|
|
35720
|
+
catch {
|
|
35721
|
+
systemTenantId = undefined;
|
|
35722
|
+
}
|
|
35723
|
+
return tenantId.toLowerCase() === (systemTenantId || 'octosystem').toLowerCase();
|
|
35724
|
+
}
|
|
35160
35725
|
/** Whether the viewer is a builder (`CockpitViewerAccess.isBuilder`); fails closed. */
|
|
35161
35726
|
async isBuilder() {
|
|
35162
35727
|
try {
|
|
@@ -35187,12 +35752,11 @@ class CockpitContextService {
|
|
|
35187
35752
|
* keep their path unless the resolver rewrites it, and always keep their query parameters.
|
|
35188
35753
|
*/
|
|
35189
35754
|
resolveLinkTarget(target, tenantId) {
|
|
35190
|
-
let url;
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
url = null;
|
|
35755
|
+
let url = this.resolveWithHost(target, tenantId);
|
|
35756
|
+
if (url === null) {
|
|
35757
|
+
// Hosts that predate System.Communication 4.x only map `pool` / `pools` (AB#5842).
|
|
35758
|
+
const legacy = legacyCockpitLinkTarget(target);
|
|
35759
|
+
url = legacy ? this.resolveWithHost(legacy, tenantId) : null;
|
|
35196
35760
|
}
|
|
35197
35761
|
if (target.kind !== 'route') {
|
|
35198
35762
|
return url ? { path: url } : null;
|
|
@@ -35207,6 +35771,14 @@ class CockpitContextService {
|
|
|
35207
35771
|
}
|
|
35208
35772
|
return target.queryParams && Object.keys(target.queryParams).length > 0 ? { path, queryParams: { ...target.queryParams } } : { path };
|
|
35209
35773
|
}
|
|
35774
|
+
resolveWithHost(target, tenantId) {
|
|
35775
|
+
try {
|
|
35776
|
+
return this.links?.resolve(target, tenantId) ?? null;
|
|
35777
|
+
}
|
|
35778
|
+
catch {
|
|
35779
|
+
return null;
|
|
35780
|
+
}
|
|
35781
|
+
}
|
|
35210
35782
|
/** Whether "✦ Explain" buttons are shown. */
|
|
35211
35783
|
get explainEnabled() {
|
|
35212
35784
|
return !!this.explainHandler?.enabled;
|
|
@@ -35402,7 +35974,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
35402
35974
|
|
|
35403
35975
|
/** Names of models in ResolveFailed fetched with the counts. */
|
|
35404
35976
|
const RESOLVE_FAILED_NAMES_FETCHED = 10;
|
|
35405
|
-
const SHARE_MS$
|
|
35977
|
+
const SHARE_MS$4 = 10_000;
|
|
35406
35978
|
/**
|
|
35407
35979
|
* CK model counts per state (`cockpitCkModelStates`, one request with aliased counts), shared for
|
|
35408
35980
|
* a few seconds per tenant between the "CK model state" KPI and the ResolveFailed provider of the
|
|
@@ -35413,7 +35985,7 @@ class CockpitCkModelStatesService {
|
|
|
35413
35985
|
shared = new Map();
|
|
35414
35986
|
counts(tenantId, now = Date.now()) {
|
|
35415
35987
|
const cached = this.shared.get(tenantId);
|
|
35416
|
-
if (cached && now - cached.at <= SHARE_MS$
|
|
35988
|
+
if (cached && now - cached.at <= SHARE_MS$4) {
|
|
35417
35989
|
return cached.counts;
|
|
35418
35990
|
}
|
|
35419
35991
|
const counts = this.gql.fetch({ variables: { first: RESOLVE_FAILED_NAMES_FETCHED }, fetchPolicy: 'network-only' }).pipe(map$1(result => {
|
|
@@ -35550,7 +36122,7 @@ function summarizeAdapterOnline(adapters) {
|
|
|
35550
36122
|
/** Upper bound of adapters read; beyond it the KPI and findings say "≥" / "first N". */
|
|
35551
36123
|
const COCKPIT_ADAPTER_LIMIT = 500;
|
|
35552
36124
|
/** Answers younger than this are shared between the "Adapter status" KPI and the attention list. */
|
|
35553
|
-
const SHARE_MS$
|
|
36125
|
+
const SHARE_MS$3 = 10_000;
|
|
35554
36126
|
/** A running adapter offline for longer than this is reported (ui-concept §5.5). */
|
|
35555
36127
|
const ADAPTER_OFFLINE_GRACE_MS = 10 * 60 * 1000;
|
|
35556
36128
|
/**
|
|
@@ -35564,7 +36136,7 @@ class CockpitAdapterStatesService {
|
|
|
35564
36136
|
shared = new Map();
|
|
35565
36137
|
states(tenantId, now = Date.now()) {
|
|
35566
36138
|
const cached = this.shared.get(tenantId);
|
|
35567
|
-
if (cached && now - cached.at <= SHARE_MS$
|
|
36139
|
+
if (cached && now - cached.at <= SHARE_MS$3) {
|
|
35568
36140
|
return cached.states;
|
|
35569
36141
|
}
|
|
35570
36142
|
const states = this.gql.fetch({ variables: { first: COCKPIT_ADAPTER_LIMIT }, fetchPolicy: 'network-only' }).pipe(map$1(result => {
|
|
@@ -35670,7 +36242,7 @@ function explainTarget(states, what) {
|
|
|
35670
36242
|
const CockpitUnregisteredPoolsDocumentDto = gql `
|
|
35671
36243
|
query cockpitUnregisteredPools($first: Int) {
|
|
35672
36244
|
runtime {
|
|
35673
|
-
|
|
36245
|
+
systemCommunicationDeploymentSite(
|
|
35674
36246
|
first: $first
|
|
35675
36247
|
fieldFilter: [{attributePath: "communicationState", operator: EQUALS, comparisonValue: "UNREGISTERED"}]
|
|
35676
36248
|
sortOrder: [{attributePath: "name", sortOrder: ASCENDING}]
|
|
@@ -35701,45 +36273,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
35701
36273
|
|
|
35702
36274
|
const NAMES_FETCHED = 10;
|
|
35703
36275
|
/**
|
|
35704
|
-
*
|
|
35705
|
-
*
|
|
36276
|
+
* Deployment sites (`System.Communication/DeploymentSite`, called "pools" before
|
|
36277
|
+
* System.Communication 4.x) no Communication Operator has registered for: the adapters and
|
|
36278
|
+
* applications they host are not rolled out. Needs what the deployment sites page needs
|
|
36279
|
+
* (CommunicationManagement + System.Communication).
|
|
36280
|
+
*
|
|
36281
|
+
* The class name and the provider id `pools-unregistered` keep the pre-4.x wording on purpose:
|
|
36282
|
+
* the id is persisted in attention-list widget configs (`providerIds`) and must never change
|
|
36283
|
+
* (AB#5842).
|
|
35706
36284
|
*/
|
|
35707
36285
|
class UnregisteredPoolsAttentionProvider {
|
|
35708
36286
|
context = inject(CockpitContextService);
|
|
35709
|
-
|
|
36287
|
+
sitesGql = inject(CockpitUnregisteredPoolsDtoGQL);
|
|
36288
|
+
/** Persisted in widget configs — stable since AB#5558, deliberately not renamed in AB#5842. */
|
|
35710
36289
|
id = 'pools-unregistered';
|
|
35711
|
-
label = '
|
|
35712
|
-
description = '
|
|
36290
|
+
label = 'Deployment sites not registered';
|
|
36291
|
+
description = 'Deployment sites without a connected Communication Operator. Viewers with CommunicationManagement.';
|
|
35713
36292
|
isVisible() {
|
|
35714
36293
|
return this.context.allows([COCKPIT_ROLES.CommunicationManagement], ['System.Communication']);
|
|
35715
36294
|
}
|
|
35716
36295
|
load() {
|
|
35717
|
-
return this.
|
|
35718
|
-
const connection = result.data?.runtime?.
|
|
35719
|
-
const
|
|
35720
|
-
const count = connection?.totalCount ??
|
|
36296
|
+
return this.sitesGql.fetch({ variables: { first: NAMES_FETCHED }, fetchPolicy: 'network-only' }).pipe(map$1(result => {
|
|
36297
|
+
const connection = result.data?.runtime?.systemCommunicationDeploymentSite;
|
|
36298
|
+
const sites = (connection?.items ?? []).filter((site) => !!site);
|
|
36299
|
+
const count = connection?.totalCount ?? sites.length;
|
|
35721
36300
|
if (count === 0) {
|
|
35722
36301
|
return [];
|
|
35723
36302
|
}
|
|
35724
|
-
const single = count === 1 &&
|
|
36303
|
+
const single = count === 1 && sites.length === 1 ? sites[0] : null;
|
|
35725
36304
|
if (single) {
|
|
35726
36305
|
const name = single.name ?? String(single.rtId);
|
|
35727
36306
|
return [{
|
|
35728
36307
|
id: this.id,
|
|
35729
36308
|
severity: 'warning',
|
|
35730
|
-
title: `
|
|
35731
|
-
text: 'No operator is connected. Adapters and applications
|
|
35732
|
-
links: [
|
|
35733
|
-
|
|
36309
|
+
title: `Deployment site "${name}" is not registered`,
|
|
36310
|
+
text: 'No operator is connected. Adapters and applications on this deployment site are not rolled out.',
|
|
36311
|
+
links: [
|
|
36312
|
+
{ label: 'Open deployment site', target: { kind: 'deployment-site', rtId: String(single.rtId) } },
|
|
36313
|
+
{ label: 'All deployment sites', target: { kind: 'deployment-sites' } }
|
|
36314
|
+
],
|
|
36315
|
+
explain: {
|
|
36316
|
+
label: name,
|
|
36317
|
+
rtId: String(single.rtId),
|
|
36318
|
+
ckTypeId: 'System.Communication/DeploymentSite',
|
|
36319
|
+
prompt: `Why is the deployment site "${name}" not registered?`
|
|
36320
|
+
}
|
|
35734
36321
|
}];
|
|
35735
36322
|
}
|
|
35736
36323
|
return [{
|
|
35737
36324
|
id: this.id,
|
|
35738
36325
|
severity: 'warning',
|
|
35739
|
-
title: `${plural(count, '
|
|
35740
|
-
text: `${nameList(
|
|
35741
|
-
links: [{ label: 'Open
|
|
35742
|
-
explain: { label: `${count}
|
|
36326
|
+
title: `${plural(count, 'deployment site')} not registered`,
|
|
36327
|
+
text: `${nameList(sites.map(site => site.name ?? String(site.rtId)), 3, count)}: no operator is connected, their adapters and applications are not rolled out.`,
|
|
36328
|
+
links: [{ label: 'Open deployment sites', target: { kind: 'deployment-sites' } }],
|
|
36329
|
+
explain: { label: `${count} deployment sites not registered`, prompt: `Why are ${count} deployment sites not registered?` }
|
|
35743
36330
|
}];
|
|
35744
36331
|
}));
|
|
35745
36332
|
}
|
|
@@ -35948,7 +36535,7 @@ function statisticsCoverage(stat) {
|
|
|
35948
36535
|
/** Upper bound of data flows read for the executions KPI; beyond it the tile says "≥". */
|
|
35949
36536
|
const COCKPIT_DATA_FLOW_LIMIT = 500;
|
|
35950
36537
|
/** Answers younger than this are shared between the "Pipeline executions 24 h" KPI and the attention list. */
|
|
35951
|
-
const SHARE_MS = 10_000;
|
|
36538
|
+
const SHARE_MS$2 = 10_000;
|
|
35952
36539
|
/**
|
|
35953
36540
|
* The data flows behind the "Pipeline executions 24 h" KPI and the failed-executions provider of
|
|
35954
36541
|
* the attention list (AB#5622). Both ask within the same board load, so one request per tenant
|
|
@@ -35960,7 +36547,7 @@ class CockpitDataFlowExecutionsService {
|
|
|
35960
36547
|
shared = new Map();
|
|
35961
36548
|
executions(tenantId, now = Date.now()) {
|
|
35962
36549
|
const cached = this.shared.get(tenantId);
|
|
35963
|
-
if (cached && now - cached.at <= SHARE_MS) {
|
|
36550
|
+
if (cached && now - cached.at <= SHARE_MS$2) {
|
|
35964
36551
|
return cached.flows;
|
|
35965
36552
|
}
|
|
35966
36553
|
const entry = { at: now, flows: EMPTY };
|
|
@@ -36185,6 +36772,145 @@ function ckModelKpi(counts, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
|
36185
36772
|
link: { kind: 'ckModels' }
|
|
36186
36773
|
};
|
|
36187
36774
|
}
|
|
36775
|
+
/**
|
|
36776
|
+
* "Tenants" (system cockpit, AB#5558): child tenants of the current tenant — on the system tenant
|
|
36777
|
+
* every tenant of the installation. The tenant list has no state, so the tile counts only.
|
|
36778
|
+
*/
|
|
36779
|
+
function tenantCountKpi(tenants, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
36780
|
+
const m = withDefaults(messages);
|
|
36781
|
+
const n = (value) => formatCount(value, m.numberLocale);
|
|
36782
|
+
let detail;
|
|
36783
|
+
if (tenants.firstIds.length > 0) {
|
|
36784
|
+
const rest = tenants.total - tenants.firstIds.length;
|
|
36785
|
+
const shown = tenants.firstIds.join(', ');
|
|
36786
|
+
detail = rest > 0 ? formatCockpitMessage(m.kpiCkModelsMoreNames, { names: shown, count: n(rest) }) : shown;
|
|
36787
|
+
}
|
|
36788
|
+
return {
|
|
36789
|
+
id: 'tenant-count',
|
|
36790
|
+
label: m.kpiTenantsLabel,
|
|
36791
|
+
value: n(tenants.total),
|
|
36792
|
+
detail,
|
|
36793
|
+
status: 'neutral',
|
|
36794
|
+
statusLabel: tenants.total === 0 ? m.kpiTenantsNone : m.kpiTenantsRegistered,
|
|
36795
|
+
link: { kind: 'tenants' }
|
|
36796
|
+
};
|
|
36797
|
+
}
|
|
36798
|
+
/**
|
|
36799
|
+
* "Blueprint updates" (system cockpit, AB#5558): installed blueprints with a newer catalog
|
|
36800
|
+
* version. Only blueprints the tenant installed count as updates to act on (warning);
|
|
36801
|
+
* service-managed ones (`System.*`) are applied by their service and only named in the detail.
|
|
36802
|
+
*/
|
|
36803
|
+
function blueprintUpdatesKpi(status, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
36804
|
+
const m = withDefaults(messages);
|
|
36805
|
+
const n = (value) => formatCount(value, m.numberLocale);
|
|
36806
|
+
const own = status.updates.filter(update => !update.isServiceManaged);
|
|
36807
|
+
const managed = status.updates.length - own.length;
|
|
36808
|
+
const details = [];
|
|
36809
|
+
if (own.length > 0) {
|
|
36810
|
+
details.push(own.slice(0, 2).map(update => `${update.name} ${update.installedVersion} → ${update.availableVersion}`).join(', '));
|
|
36811
|
+
}
|
|
36812
|
+
else {
|
|
36813
|
+
details.push(formatCockpitMessage(m.kpiBlueprintsInstalled, { count: n(status.installed) }));
|
|
36814
|
+
}
|
|
36815
|
+
if (managed > 0) {
|
|
36816
|
+
details.push(formatCockpitMessage(m.kpiBlueprintsServiceManaged, { count: n(managed) }));
|
|
36817
|
+
}
|
|
36818
|
+
const note = truncationText(status.catalogRead, status.catalogTotal, m.kpiBlueprintsTruncated, m.numberLocale);
|
|
36819
|
+
if (note) {
|
|
36820
|
+
details.push(note);
|
|
36821
|
+
}
|
|
36822
|
+
let kpiStatus;
|
|
36823
|
+
let statusLabel;
|
|
36824
|
+
if (status.installed === 0 && status.updates.length === 0) {
|
|
36825
|
+
kpiStatus = 'neutral';
|
|
36826
|
+
statusLabel = m.kpiBlueprintsNone;
|
|
36827
|
+
}
|
|
36828
|
+
else if (own.length > 0) {
|
|
36829
|
+
kpiStatus = 'warning';
|
|
36830
|
+
statusLabel = formatCockpitMessage(m.kpiBlueprintsAvailable, { count: n(own.length) });
|
|
36831
|
+
}
|
|
36832
|
+
else {
|
|
36833
|
+
kpiStatus = 'success';
|
|
36834
|
+
statusLabel = m.kpiBlueprintsUpToDate;
|
|
36835
|
+
}
|
|
36836
|
+
return {
|
|
36837
|
+
id: 'blueprint-updates',
|
|
36838
|
+
label: m.kpiBlueprintsLabel,
|
|
36839
|
+
value: n(own.length),
|
|
36840
|
+
detail: details.join(' · ') || undefined,
|
|
36841
|
+
status: kpiStatus,
|
|
36842
|
+
statusLabel,
|
|
36843
|
+
link: { kind: 'blueprints' }
|
|
36844
|
+
};
|
|
36845
|
+
}
|
|
36846
|
+
/** The display word of a service health state (`degraded`, `unhealthy`, `not reachable`). */
|
|
36847
|
+
function serviceStateText(state, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
36848
|
+
const m = withDefaults(messages);
|
|
36849
|
+
switch (state) {
|
|
36850
|
+
case 'degraded':
|
|
36851
|
+
return m.serviceStateDegraded;
|
|
36852
|
+
case 'unknown':
|
|
36853
|
+
return m.serviceStateUnknown;
|
|
36854
|
+
default:
|
|
36855
|
+
return m.serviceStateUnhealthy;
|
|
36856
|
+
}
|
|
36857
|
+
}
|
|
36858
|
+
/**
|
|
36859
|
+
* "Services healthy" (system cockpit, AB#5558): platform services whose health check reports
|
|
36860
|
+
* Healthy, of all checked. Unhealthy and not answering are errors, degraded a warning; the tile
|
|
36861
|
+
* opens the health details of the first service with a problem (else Identity).
|
|
36862
|
+
*/
|
|
36863
|
+
function servicesHealthKpi(services, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
36864
|
+
const m = withDefaults(messages);
|
|
36865
|
+
const n = (value) => formatCount(value, m.numberLocale);
|
|
36866
|
+
const healthy = services.filter(service => service.status === 'healthy');
|
|
36867
|
+
const failing = services.filter(service => service.status === 'unhealthy' || service.status === 'unknown');
|
|
36868
|
+
const degraded = services.filter(service => service.status === 'degraded');
|
|
36869
|
+
const problems = [...failing, ...degraded];
|
|
36870
|
+
let status;
|
|
36871
|
+
let statusLabel;
|
|
36872
|
+
if (failing.length > 0) {
|
|
36873
|
+
status = 'error';
|
|
36874
|
+
statusLabel = formatCockpitMessage(m.kpiServicesUnhealthy, { count: n(failing.length) });
|
|
36875
|
+
}
|
|
36876
|
+
else if (degraded.length > 0) {
|
|
36877
|
+
status = 'warning';
|
|
36878
|
+
statusLabel = formatCockpitMessage(m.kpiServicesDegraded, { count: n(degraded.length) });
|
|
36879
|
+
}
|
|
36880
|
+
else {
|
|
36881
|
+
status = 'success';
|
|
36882
|
+
statusLabel = m.kpiServicesAllHealthy;
|
|
36883
|
+
}
|
|
36884
|
+
return {
|
|
36885
|
+
id: 'services-health',
|
|
36886
|
+
label: m.kpiServicesLabel,
|
|
36887
|
+
value: `${n(healthy.length)} / ${n(services.length)}`,
|
|
36888
|
+
detail: problems.length > 0
|
|
36889
|
+
? problems.map(service => `${service.name}: ${serviceStateText(service.status, m)}`).join(' · ')
|
|
36890
|
+
: services.map(service => service.name).join(', ') || undefined,
|
|
36891
|
+
status,
|
|
36892
|
+
statusLabel,
|
|
36893
|
+
link: { kind: 'serviceHealth', service: problems[0]?.service ?? services[0]?.service ?? 'identity' }
|
|
36894
|
+
};
|
|
36895
|
+
}
|
|
36896
|
+
/** "Version" (system cockpit, AB#5558): the host's versions; the first is the value. No page behind it. */
|
|
36897
|
+
function versionKpi(entries, messages = DEFAULT_COCKPIT_WIDGET_MESSAGES) {
|
|
36898
|
+
const m = withDefaults(messages);
|
|
36899
|
+
const valid = entries.filter(entry => !!entry?.label && !!entry.version);
|
|
36900
|
+
const [first, ...rest] = valid;
|
|
36901
|
+
return {
|
|
36902
|
+
id: 'version-info',
|
|
36903
|
+
label: m.kpiVersionLabel,
|
|
36904
|
+
value: first?.version ?? '—',
|
|
36905
|
+
detail: rest.map(entry => `${entry.label} ${entry.version}`).join(' · ') || undefined,
|
|
36906
|
+
status: 'neutral',
|
|
36907
|
+
statusLabel: first?.label ?? m.kpiVersionNone
|
|
36908
|
+
};
|
|
36909
|
+
}
|
|
36910
|
+
/** Messages with every optional member filled (members added later are optional). */
|
|
36911
|
+
function withDefaults(messages) {
|
|
36912
|
+
return resolveCockpitWidgetMessages(messages);
|
|
36913
|
+
}
|
|
36188
36914
|
/** Scales the values to the box; the tallest value touches the top padding. */
|
|
36189
36915
|
function sparklineGeometry(values, width = 120, height = 36, padding = 3) {
|
|
36190
36916
|
if (values.length < 2) {
|
|
@@ -36349,6 +37075,325 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
36349
37075
|
type: Injectable
|
|
36350
37076
|
}] });
|
|
36351
37077
|
|
|
37078
|
+
/** Display names, in check order. */
|
|
37079
|
+
const COCKPIT_SERVICE_NAMES = {
|
|
37080
|
+
identity: 'Identity',
|
|
37081
|
+
'asset-repository': 'Asset Repository',
|
|
37082
|
+
bot: 'Bot',
|
|
37083
|
+
'communication-controller': 'Communication Controller'
|
|
37084
|
+
};
|
|
37085
|
+
const SHARE_MS$1 = 10_000;
|
|
37086
|
+
/** `HealthCheck` → status (`null` = no answer). */
|
|
37087
|
+
function toServiceHealthStatus(check) {
|
|
37088
|
+
switch (check?.status) {
|
|
37089
|
+
case HealthStatus.Healthy:
|
|
37090
|
+
return 'healthy';
|
|
37091
|
+
case HealthStatus.Degraded:
|
|
37092
|
+
return 'degraded';
|
|
37093
|
+
case HealthStatus.Unhealthy:
|
|
37094
|
+
return 'unhealthy';
|
|
37095
|
+
default:
|
|
37096
|
+
return 'unknown';
|
|
37097
|
+
}
|
|
37098
|
+
}
|
|
37099
|
+
/**
|
|
37100
|
+
* Health of the platform services (AB#5558, system cockpit): the same `/health` endpoints as the
|
|
37101
|
+
* MeshBoard's service health tiles, all at once, shared for a few seconds between the "Services"
|
|
37102
|
+
* KPI and the attention check. Optional services that are not part of the installation (empty
|
|
37103
|
+
* URL, or `/` from older platform-services) are left out.
|
|
37104
|
+
*/
|
|
37105
|
+
class CockpitServiceHealthService {
|
|
37106
|
+
health = inject(HealthService);
|
|
37107
|
+
configuration = inject(CONFIGURATION_SERVICE, { optional: true });
|
|
37108
|
+
shared = null;
|
|
37109
|
+
services(now = Date.now()) {
|
|
37110
|
+
if (this.shared && now - this.shared.at <= SHARE_MS$1) {
|
|
37111
|
+
return this.shared.result;
|
|
37112
|
+
}
|
|
37113
|
+
const result = defer(() => from(this.check())).pipe(shareReplay({ bufferSize: 1, refCount: false }));
|
|
37114
|
+
this.shared = { at: now, result };
|
|
37115
|
+
return result;
|
|
37116
|
+
}
|
|
37117
|
+
async check() {
|
|
37118
|
+
const config = this.configuration?.config;
|
|
37119
|
+
const checks = [
|
|
37120
|
+
['identity', () => this.health.getIdentityServiceAsync()],
|
|
37121
|
+
['asset-repository', () => this.health.getAssetRepoServiceHealthAsync()],
|
|
37122
|
+
['bot', () => this.health.getBotServiceAsync()]
|
|
37123
|
+
];
|
|
37124
|
+
if (!config || isConfigured(config.communicationServices)) {
|
|
37125
|
+
checks.push(['communication-controller', () => this.health.getCommunicationControllerServiceAsync()]);
|
|
37126
|
+
}
|
|
37127
|
+
return Promise.all(checks.map(async ([service, read]) => {
|
|
37128
|
+
let check;
|
|
37129
|
+
try {
|
|
37130
|
+
check = await read();
|
|
37131
|
+
}
|
|
37132
|
+
catch {
|
|
37133
|
+
check = null;
|
|
37134
|
+
}
|
|
37135
|
+
return { service, name: COCKPIT_SERVICE_NAMES[service], status: toServiceHealthStatus(check) };
|
|
37136
|
+
}));
|
|
37137
|
+
}
|
|
37138
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitServiceHealthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37139
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitServiceHealthService, providedIn: 'root' });
|
|
37140
|
+
}
|
|
37141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitServiceHealthService, decorators: [{
|
|
37142
|
+
type: Injectable,
|
|
37143
|
+
args: [{ providedIn: 'root' }]
|
|
37144
|
+
}] });
|
|
37145
|
+
function isConfigured(url) {
|
|
37146
|
+
return !!url && url !== '/';
|
|
37147
|
+
}
|
|
37148
|
+
|
|
37149
|
+
/**
|
|
37150
|
+
* Platform services whose health check is not Healthy (system cockpit, AB#5558): one finding per
|
|
37151
|
+
* service — unhealthy or not answering is an error, degraded a warning — linking to the service's
|
|
37152
|
+
* health details. The services serve the whole installation, so the check runs on the system
|
|
37153
|
+
* tenant only (tenant cockpits are unchanged). Shares its requests with the "Services healthy" KPI.
|
|
37154
|
+
*/
|
|
37155
|
+
class ServicesHealthAttentionProvider {
|
|
37156
|
+
context = inject(CockpitContextService);
|
|
37157
|
+
/** Resolved on first load: hosts and tests that never run the check need no data providers. */
|
|
37158
|
+
injector = inject(Injector);
|
|
37159
|
+
messagesSource = inject(COCKPIT_WIDGET_MESSAGES, { optional: true });
|
|
37160
|
+
id = 'services-unhealthy';
|
|
37161
|
+
label = 'Platform services with problems';
|
|
37162
|
+
description = 'Identity, Asset Repository, Bot or Communication Controller not healthy. System tenant only.';
|
|
37163
|
+
async isVisible(context) {
|
|
37164
|
+
return this.context.isSystemTenant(context.tenantId) && await this.context.allows([]);
|
|
37165
|
+
}
|
|
37166
|
+
load() {
|
|
37167
|
+
return this.injector.get(CockpitServiceHealthService).services().pipe(map$1(services => {
|
|
37168
|
+
const m = resolveCockpitWidgetMessages(readCockpitMessagesSource(this.messagesSource));
|
|
37169
|
+
return services
|
|
37170
|
+
.filter(service => service.status !== 'healthy')
|
|
37171
|
+
.map((service) => {
|
|
37172
|
+
const state = serviceStateText(service.status, m);
|
|
37173
|
+
const title = formatCockpitMessage(m.attentionServiceTitle, { name: service.name, state });
|
|
37174
|
+
return {
|
|
37175
|
+
id: `${this.id}:${service.service}`,
|
|
37176
|
+
severity: service.status === 'degraded' ? 'warning' : 'error',
|
|
37177
|
+
title,
|
|
37178
|
+
text: service.status === 'unknown' ? m.attentionServiceUnreachableText : m.attentionServiceText,
|
|
37179
|
+
links: [{ label: m.attentionServiceLink, target: { kind: 'serviceHealth', service: service.service } }],
|
|
37180
|
+
explain: { label: title, prompt: formatCockpitMessage(m.attentionServiceExplain, { name: service.name, state }) }
|
|
37181
|
+
};
|
|
37182
|
+
});
|
|
37183
|
+
}));
|
|
37184
|
+
}
|
|
37185
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ServicesHealthAttentionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37186
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ServicesHealthAttentionProvider });
|
|
37187
|
+
}
|
|
37188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ServicesHealthAttentionProvider, decorators: [{
|
|
37189
|
+
type: Injectable
|
|
37190
|
+
}] });
|
|
37191
|
+
|
|
37192
|
+
const CockpitBlueprintStatusDocumentDto = gql `
|
|
37193
|
+
query cockpitBlueprintStatus($take: Int) {
|
|
37194
|
+
blueprints {
|
|
37195
|
+
installations {
|
|
37196
|
+
blueprintId
|
|
37197
|
+
isDependency
|
|
37198
|
+
}
|
|
37199
|
+
list(skip: 0, take: $take) {
|
|
37200
|
+
totalCount
|
|
37201
|
+
items {
|
|
37202
|
+
name
|
|
37203
|
+
version
|
|
37204
|
+
}
|
|
37205
|
+
}
|
|
37206
|
+
}
|
|
37207
|
+
}
|
|
37208
|
+
`;
|
|
37209
|
+
class CockpitBlueprintStatusDtoGQL extends i1.Query {
|
|
37210
|
+
document = CockpitBlueprintStatusDocumentDto;
|
|
37211
|
+
constructor(apollo) {
|
|
37212
|
+
super(apollo);
|
|
37213
|
+
}
|
|
37214
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusDtoGQL, deps: [{ token: i1.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37215
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusDtoGQL, providedIn: 'root' });
|
|
37216
|
+
}
|
|
37217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusDtoGQL, decorators: [{
|
|
37218
|
+
type: Injectable,
|
|
37219
|
+
args: [{
|
|
37220
|
+
providedIn: 'root'
|
|
37221
|
+
}]
|
|
37222
|
+
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
37223
|
+
|
|
37224
|
+
/** Catalog entries read with the installations (one page; the catalog lists one entry per name and version). */
|
|
37225
|
+
const COCKPIT_BLUEPRINT_CATALOG_LIMIT = 500;
|
|
37226
|
+
const SHARE_MS = 10_000;
|
|
37227
|
+
/**
|
|
37228
|
+
* Blueprints whose name starts with `System.` are service-managed: the owning service applies and
|
|
37229
|
+
* rolls them forward per tenant (mirrors `BlueprintIdExtensions.IsServiceManaged`).
|
|
37230
|
+
*/
|
|
37231
|
+
const SERVICE_MANAGED_BLUEPRINT_PREFIX = 'System.';
|
|
37232
|
+
/**
|
|
37233
|
+
* `Name-Version` → parts. Splits at the last dash, like the Studio's blueprint pages (a `CkVersion`
|
|
37234
|
+
* never contains a dash).
|
|
37235
|
+
*/
|
|
37236
|
+
function splitBlueprintId(blueprintId) {
|
|
37237
|
+
const index = blueprintId.lastIndexOf('-');
|
|
37238
|
+
return index < 0 ? { name: blueprintId, version: '' } : { name: blueprintId.slice(0, index), version: blueprintId.slice(index + 1) };
|
|
37239
|
+
}
|
|
37240
|
+
/**
|
|
37241
|
+
* Compares two blueprint versions like the backend's `CkVersion` (`Major.Minor.Revision`,
|
|
37242
|
+
* numeric, missing components 0): negative when `a` is older, positive when newer. A string
|
|
37243
|
+
* comparison would rate `2.10.0` below `2.9.0` (AB#4837).
|
|
37244
|
+
*/
|
|
37245
|
+
function compareBlueprintVersions(a, b) {
|
|
37246
|
+
const left = versionComponents(a);
|
|
37247
|
+
const right = versionComponents(b);
|
|
37248
|
+
for (let i = 0; i < 3; i++) {
|
|
37249
|
+
if (left[i] !== right[i]) {
|
|
37250
|
+
return left[i] - right[i];
|
|
37251
|
+
}
|
|
37252
|
+
}
|
|
37253
|
+
return 0;
|
|
37254
|
+
}
|
|
37255
|
+
function versionComponents(version) {
|
|
37256
|
+
const parts = version.split('.');
|
|
37257
|
+
return [0, 1, 2].map(i => {
|
|
37258
|
+
const parsed = Number.parseInt(parts[i] ?? '', 10);
|
|
37259
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
37260
|
+
});
|
|
37261
|
+
}
|
|
37262
|
+
/** The status from one `cockpitBlueprintStatus` answer. */
|
|
37263
|
+
function toBlueprintStatus(data) {
|
|
37264
|
+
const installations = data?.blueprints?.installations ?? [];
|
|
37265
|
+
const catalog = data?.blueprints?.list;
|
|
37266
|
+
const highest = new Map();
|
|
37267
|
+
for (const item of catalog?.items ?? []) {
|
|
37268
|
+
if (!item?.name || !item.version) {
|
|
37269
|
+
continue;
|
|
37270
|
+
}
|
|
37271
|
+
const known = highest.get(item.name);
|
|
37272
|
+
if (!known || compareBlueprintVersions(item.version, known) > 0) {
|
|
37273
|
+
highest.set(item.name, item.version);
|
|
37274
|
+
}
|
|
37275
|
+
}
|
|
37276
|
+
const updates = [];
|
|
37277
|
+
let installed = 0;
|
|
37278
|
+
for (const installation of installations) {
|
|
37279
|
+
if (!installation?.blueprintId) {
|
|
37280
|
+
continue;
|
|
37281
|
+
}
|
|
37282
|
+
if (!installation.isDependency) {
|
|
37283
|
+
installed++;
|
|
37284
|
+
}
|
|
37285
|
+
const { name, version } = splitBlueprintId(installation.blueprintId);
|
|
37286
|
+
const available = highest.get(name);
|
|
37287
|
+
if (available && compareBlueprintVersions(available, version) > 0) {
|
|
37288
|
+
updates.push({ name, installedVersion: version, availableVersion: available, isServiceManaged: name.startsWith(SERVICE_MANAGED_BLUEPRINT_PREFIX) });
|
|
37289
|
+
}
|
|
37290
|
+
}
|
|
37291
|
+
updates.sort((a, b) => Number(a.isServiceManaged) - Number(b.isServiceManaged) || a.name.localeCompare(b.name));
|
|
37292
|
+
const read = catalog?.items?.length ?? 0;
|
|
37293
|
+
return { installed, updates, catalogRead: read, catalogTotal: Math.max(catalog?.totalCount ?? 0, read) };
|
|
37294
|
+
}
|
|
37295
|
+
/**
|
|
37296
|
+
* The tenant's blueprint status, shared for a few seconds per tenant between the "Blueprint
|
|
37297
|
+
* updates" KPI and the attention check.
|
|
37298
|
+
*/
|
|
37299
|
+
class CockpitBlueprintStatusService {
|
|
37300
|
+
gql = inject(CockpitBlueprintStatusDtoGQL);
|
|
37301
|
+
shared = new Map();
|
|
37302
|
+
status(tenantId, now = Date.now()) {
|
|
37303
|
+
const cached = this.shared.get(tenantId);
|
|
37304
|
+
if (cached && now - cached.at <= SHARE_MS) {
|
|
37305
|
+
return cached.status;
|
|
37306
|
+
}
|
|
37307
|
+
const status = this.gql.fetch({ variables: { take: COCKPIT_BLUEPRINT_CATALOG_LIMIT }, fetchPolicy: 'network-only' }).pipe(map$1(result => toBlueprintStatus(result.data)), shareReplay({ bufferSize: 1, refCount: false }));
|
|
37308
|
+
this.shared.set(tenantId, { at: now, status });
|
|
37309
|
+
return status;
|
|
37310
|
+
}
|
|
37311
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37312
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusService, providedIn: 'root' });
|
|
37313
|
+
}
|
|
37314
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitBlueprintStatusService, decorators: [{
|
|
37315
|
+
type: Injectable,
|
|
37316
|
+
args: [{ providedIn: 'root' }]
|
|
37317
|
+
}] });
|
|
37318
|
+
|
|
37319
|
+
/** "Name 1.0.0 → 1.1.0". */
|
|
37320
|
+
function describe(update) {
|
|
37321
|
+
return `${update.name} ${update.installedVersion} → ${update.availableVersion}`;
|
|
37322
|
+
}
|
|
37323
|
+
/**
|
|
37324
|
+
* Installed blueprints with a newer catalog version (system cockpit, AB#5558): one info finding
|
|
37325
|
+
* with the count as badge, linking to the installed blueprints. Service-managed blueprints
|
|
37326
|
+
* (`System.*`) are named separately — their service applies them. Runs on the system tenant only
|
|
37327
|
+
* for now (it reads the catalogs, so tenant cockpits do not pay for it); same role as the
|
|
37328
|
+
* Blueprints pages. Shares its request with the "Blueprint updates" KPI.
|
|
37329
|
+
*/
|
|
37330
|
+
class BlueprintUpdatesAttentionProvider {
|
|
37331
|
+
context = inject(CockpitContextService);
|
|
37332
|
+
/** Resolved on first load: hosts and tests that never run the check need no data providers. */
|
|
37333
|
+
injector = inject(Injector);
|
|
37334
|
+
messagesSource = inject(COCKPIT_WIDGET_MESSAGES, { optional: true });
|
|
37335
|
+
id = 'blueprint-updates';
|
|
37336
|
+
label = 'Blueprint updates available';
|
|
37337
|
+
description = 'Installed blueprints with a newer version in the catalogs. System tenant, viewers with AdminPanelManagement.';
|
|
37338
|
+
async isVisible(context) {
|
|
37339
|
+
return this.context.isSystemTenant(context.tenantId) && await this.context.allows([COCKPIT_ROLES.AdminPanelManagement]);
|
|
37340
|
+
}
|
|
37341
|
+
load(context) {
|
|
37342
|
+
return this.injector.get(CockpitBlueprintStatusService).status(context.tenantId).pipe(map$1(status => {
|
|
37343
|
+
if (status.updates.length === 0) {
|
|
37344
|
+
return [];
|
|
37345
|
+
}
|
|
37346
|
+
const m = resolveCockpitWidgetMessages(readCockpitMessagesSource(this.messagesSource));
|
|
37347
|
+
const own = status.updates.filter(update => !update.isServiceManaged);
|
|
37348
|
+
const managed = status.updates.filter(update => update.isServiceManaged);
|
|
37349
|
+
const texts = [];
|
|
37350
|
+
if (own.length > 0) {
|
|
37351
|
+
texts.push(formatCockpitMessage(m.attentionBlueprintUpdatesText, { list: nameList(own.map(describe)) }));
|
|
37352
|
+
}
|
|
37353
|
+
if (managed.length > 0) {
|
|
37354
|
+
texts.push(formatCockpitMessage(m.attentionBlueprintUpdatesServiceManaged, { list: nameList(managed.map(describe)) }));
|
|
37355
|
+
}
|
|
37356
|
+
const title = m.attentionBlueprintUpdatesTitle;
|
|
37357
|
+
return [{
|
|
37358
|
+
id: this.id,
|
|
37359
|
+
severity: 'info',
|
|
37360
|
+
title,
|
|
37361
|
+
text: texts.join(' '),
|
|
37362
|
+
count: status.updates.length,
|
|
37363
|
+
links: [{ label: m.attentionBlueprintUpdatesLink, target: { kind: 'blueprints' } }]
|
|
37364
|
+
}];
|
|
37365
|
+
}));
|
|
37366
|
+
}
|
|
37367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: BlueprintUpdatesAttentionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37368
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: BlueprintUpdatesAttentionProvider });
|
|
37369
|
+
}
|
|
37370
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: BlueprintUpdatesAttentionProvider, decorators: [{
|
|
37371
|
+
type: Injectable
|
|
37372
|
+
}] });
|
|
37373
|
+
|
|
37374
|
+
/** Tenant ids read with the count. */
|
|
37375
|
+
const COCKPIT_CHILD_TENANT_IDS = 3;
|
|
37376
|
+
/**
|
|
37377
|
+
* Child tenant count (AB#5558, system cockpit) from the asset repository's tenant list
|
|
37378
|
+
* (`GET {tenantId}/v1/tenants`, the Child Tenants page). The list only knows id and database —
|
|
37379
|
+
* there is no per-tenant state (enabled, mode, health) to group by.
|
|
37380
|
+
*/
|
|
37381
|
+
class CockpitChildTenantsService {
|
|
37382
|
+
assetRepo = inject(AssetRepoService);
|
|
37383
|
+
childTenants() {
|
|
37384
|
+
return defer(() => from(this.assetRepo.getTenants(0, COCKPIT_CHILD_TENANT_IDS))).pipe(map$1(result => {
|
|
37385
|
+
const ids = (result?.list ?? []).map(tenant => tenant?.tenantId).filter((id) => !!id);
|
|
37386
|
+
return { total: Math.max(result?.totalCount ?? 0, ids.length), firstIds: ids };
|
|
37387
|
+
}));
|
|
37388
|
+
}
|
|
37389
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitChildTenantsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
37390
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitChildTenantsService, providedIn: 'root' });
|
|
37391
|
+
}
|
|
37392
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitChildTenantsService, decorators: [{
|
|
37393
|
+
type: Injectable,
|
|
37394
|
+
args: [{ providedIn: 'root' }]
|
|
37395
|
+
}] });
|
|
37396
|
+
|
|
36352
37397
|
/** What a tile shows when its query failed (details are logged, never shown). */
|
|
36353
37398
|
const KPI_ERROR_TEXT = 'The figure could not be loaded.';
|
|
36354
37399
|
const COMMUNICATION_GATE = {
|
|
@@ -36361,7 +37406,14 @@ const COMMUNICATION_GATE = {
|
|
|
36361
37406
|
const COCKPIT_KPI_GATES = {
|
|
36362
37407
|
adapterStatus: COMMUNICATION_GATE,
|
|
36363
37408
|
pipelineExecutions: COMMUNICATION_GATE,
|
|
36364
|
-
ckModelState: { roles: [COCKPIT_ROLES.AdminPanelManagement], models: [], reason: 'Needs the AdminPanelManagement role.', reasonMessage: 'kpiNeedsAdminPanel' }
|
|
37409
|
+
ckModelState: { roles: [COCKPIT_ROLES.AdminPanelManagement], models: [], reason: 'Needs the AdminPanelManagement role.', reasonMessage: 'kpiNeedsAdminPanel' },
|
|
37410
|
+
// Same role as the Child Tenants page.
|
|
37411
|
+
tenantCount: { roles: [COCKPIT_ROLES.TenantManagement], models: [], reason: 'Needs the TenantManagement role.', reasonMessage: 'kpiNeedsTenantManagement' },
|
|
37412
|
+
// Same role as the Blueprints pages.
|
|
37413
|
+
blueprintUpdates: { roles: [COCKPIT_ROLES.AdminPanelManagement], models: [], reason: 'Needs the AdminPanelManagement role.', reasonMessage: 'kpiNeedsAdminPanel' },
|
|
37414
|
+
// The health endpoints and the health detail page are open to every signed-in user.
|
|
37415
|
+
servicesHealth: { roles: [], models: [], reason: '', reasonMessage: 'notAvailable' },
|
|
37416
|
+
versionInfo: { roles: [], models: [], reason: '', reasonMessage: 'notAvailable' }
|
|
36365
37417
|
};
|
|
36366
37418
|
/**
|
|
36367
37419
|
* The cockpit KPIs (AB#5558, from the Home KPI strip of AB#5545): each KPI has its own gate and
|
|
@@ -36372,6 +37424,9 @@ class CockpitKpiService {
|
|
|
36372
37424
|
adapterStates = inject(CockpitAdapterStatesService);
|
|
36373
37425
|
ckModelStates = inject(CockpitCkModelStatesService);
|
|
36374
37426
|
dataFlowExecutions = inject(CockpitDataFlowExecutionsService);
|
|
37427
|
+
/** The system cockpit's data services are resolved on first use, so hosts without them need no providers. */
|
|
37428
|
+
injector = inject(Injector);
|
|
37429
|
+
versions = inject(COCKPIT_VERSION_SOURCE, { optional: true });
|
|
36375
37430
|
messagesSource = inject(COCKPIT_WIDGET_MESSAGES, { optional: true });
|
|
36376
37431
|
/**
|
|
36377
37432
|
* The KPI's state. Texts (label, status, detail, reasons) come from `messages`, else from the
|
|
@@ -36380,7 +37435,7 @@ class CockpitKpiService {
|
|
|
36380
37435
|
kpi(kind, messages) {
|
|
36381
37436
|
const gate = COCKPIT_KPI_GATES[kind];
|
|
36382
37437
|
return defer(() => {
|
|
36383
|
-
const texts = messages ??
|
|
37438
|
+
const texts = resolveCockpitWidgetMessages(messages ?? readCockpitMessagesSource(this.messagesSource));
|
|
36384
37439
|
return from(this.resolve(gate)).pipe(switchMap(access => {
|
|
36385
37440
|
if (access.kind === 'noTenant') {
|
|
36386
37441
|
return of({ state: 'unavailable', reason: texts.noTenant, forBuilder: true });
|
|
@@ -36388,6 +37443,10 @@ class CockpitKpiService {
|
|
|
36388
37443
|
if (access.kind === 'denied') {
|
|
36389
37444
|
return of({ state: 'unavailable', reason: texts[gate.reasonMessage], forBuilder: access.builder });
|
|
36390
37445
|
}
|
|
37446
|
+
if (kind === 'versionInfo' && !this.versions) {
|
|
37447
|
+
// Nothing to show without the host's versions: a quiet tile that collapses for non-builders.
|
|
37448
|
+
return of({ state: 'unavailable', reason: texts.notAvailable, forBuilder: false });
|
|
37449
|
+
}
|
|
36391
37450
|
return this.load(kind, access.tenantId, texts).pipe(take(1), map$1((kpi) => ({ state: 'ready', kpi })));
|
|
36392
37451
|
}), catchError$1(error => {
|
|
36393
37452
|
// Details go to the console only; the tile shows a generic text.
|
|
@@ -36414,6 +37473,14 @@ class CockpitKpiService {
|
|
|
36414
37473
|
return this.ckModelStates.counts(tenantId).pipe(map$1(counts => ckModelKpi(counts, texts)));
|
|
36415
37474
|
case 'pipelineExecutions':
|
|
36416
37475
|
return this.dataFlowExecutions.executions(tenantId).pipe(map$1(({ flows, totalCount }) => executionKpi(flows, totalCount, texts)));
|
|
37476
|
+
case 'tenantCount':
|
|
37477
|
+
return this.injector.get(CockpitChildTenantsService).childTenants().pipe(map$1(tenants => tenantCountKpi(tenants, texts)));
|
|
37478
|
+
case 'blueprintUpdates':
|
|
37479
|
+
return this.injector.get(CockpitBlueprintStatusService).status(tenantId).pipe(map$1(status => blueprintUpdatesKpi(status, texts)));
|
|
37480
|
+
case 'servicesHealth':
|
|
37481
|
+
return this.injector.get(CockpitServiceHealthService).services().pipe(map$1(services => servicesHealthKpi(services, texts)));
|
|
37482
|
+
case 'versionInfo':
|
|
37483
|
+
return defer(() => from(Promise.resolve(this.versions?.entries() ?? []))).pipe(map$1(entries => versionKpi(entries, texts)));
|
|
36417
37484
|
}
|
|
36418
37485
|
}
|
|
36419
37486
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitKpiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -36503,7 +37570,9 @@ const COCKPIT_WIDGET_STYLES = `
|
|
|
36503
37570
|
|
|
36504
37571
|
/**
|
|
36505
37572
|
* Reports the natural content height of a cockpit list widget to the board (AB#5558), so the
|
|
36506
|
-
* phone tier grows the tile instead of clipping it (`MeshBoardStateService.setWidgetContentHeight`)
|
|
37573
|
+
* phone tier grows the tile instead of clipping it (`MeshBoardStateService.setWidgetContentHeight`)
|
|
37574
|
+
* and, on the desktop tier outside edit mode, the attention list shrinks to its content within its
|
|
37575
|
+
* configured rows (AB#5622, `DESKTOP_CONTENT_FIT_TYPES`).
|
|
36507
37576
|
*
|
|
36508
37577
|
* `content` must wrap the widget's whole content without any height constraint (no `height` /
|
|
36509
37578
|
* `min-height` relative to the tile), otherwise a growing tile would report a growing height.
|
|
@@ -37007,6 +38076,12 @@ class CockpitKpiWidgetComponent {
|
|
|
37007
38076
|
options = signal({ showDetail: true, showSparkline: true }, /* @ts-ignore */
|
|
37008
38077
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
37009
38078
|
subscription = null;
|
|
38079
|
+
/** Set once a load has been running for {@link COCKPIT_KPI_SLOW_LOADING_MS}. */
|
|
38080
|
+
slowLoading = signal(false, /* @ts-ignore */
|
|
38081
|
+
...(ngDevMode ? [{ debugName: "slowLoading" }] : /* istanbul ignore next */ []));
|
|
38082
|
+
slowTimer = null;
|
|
38083
|
+
kind = signal('adapterStatus', /* @ts-ignore */
|
|
38084
|
+
...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
37010
38085
|
result = this._result.asReadonly();
|
|
37011
38086
|
isLoading = computed(() => this._result().state === 'loading', /* @ts-ignore */
|
|
37012
38087
|
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -37021,6 +38096,15 @@ class CockpitKpiWidgetComponent {
|
|
|
37021
38096
|
}, /* @ts-ignore */
|
|
37022
38097
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
37023
38098
|
kpi = this.data;
|
|
38099
|
+
/** "Loading…", or what a slow KPI is doing (blueprint catalog scan) after a second. */
|
|
38100
|
+
loadingText = computed(() => {
|
|
38101
|
+
const texts = this.texts();
|
|
38102
|
+
if (this.slowLoading() && this.kind() === 'blueprintUpdates') {
|
|
38103
|
+
return texts.kpiBlueprintsScanning ?? texts.loading;
|
|
38104
|
+
}
|
|
38105
|
+
return texts.loading;
|
|
38106
|
+
}, /* @ts-ignore */
|
|
38107
|
+
...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
|
|
37024
38108
|
showDetail = computed(() => this.options().showDetail, /* @ts-ignore */
|
|
37025
38109
|
...(ngDevMode ? [{ debugName: "showDetail" }] : /* istanbul ignore next */ []));
|
|
37026
38110
|
/** Role requirements only for builders; other viewers get a neutral "Not available" (AB#5558 review). */
|
|
@@ -37063,6 +38147,7 @@ class CockpitKpiWidgetComponent {
|
|
|
37063
38147
|
}
|
|
37064
38148
|
ngOnDestroy() {
|
|
37065
38149
|
this.subscription?.unsubscribe();
|
|
38150
|
+
this.clearSlowTimer();
|
|
37066
38151
|
}
|
|
37067
38152
|
refresh() {
|
|
37068
38153
|
this.load();
|
|
@@ -37076,8 +38161,20 @@ class CockpitKpiWidgetComponent {
|
|
|
37076
38161
|
});
|
|
37077
38162
|
void this.context.tenantId().then(tenantId => this.tenantId.set(tenantId));
|
|
37078
38163
|
const previous = this._result();
|
|
38164
|
+
const kind = kpiKindOf(config);
|
|
38165
|
+
this.kind.set(kind);
|
|
38166
|
+
this.clearSlowTimer();
|
|
38167
|
+
this.slowLoading.set(false);
|
|
38168
|
+
// Timer only — the subscription below stays untouched, so a slow scan never blocks the tile.
|
|
38169
|
+
this.slowTimer = setTimeout(() => {
|
|
38170
|
+
this.slowTimer = null;
|
|
38171
|
+
this.slowLoading.set(true);
|
|
38172
|
+
}, COCKPIT_KPI_SLOW_LOADING_MS);
|
|
37079
38173
|
this.loadedTexts = untracked(() => this.texts());
|
|
37080
|
-
this.subscription = this.kpiService.kpi(
|
|
38174
|
+
this.subscription = this.kpiService.kpi(kind, this.loadedTexts).subscribe(result => {
|
|
38175
|
+
if (result.state !== 'loading') {
|
|
38176
|
+
this.clearSlowTimer();
|
|
38177
|
+
}
|
|
37081
38178
|
// A refresh keeps the last figure until the new one arrives (no flicker).
|
|
37082
38179
|
if (result.state === 'loading' && previous.state === 'ready') {
|
|
37083
38180
|
return;
|
|
@@ -37089,6 +38186,12 @@ class CockpitKpiWidgetComponent {
|
|
|
37089
38186
|
}
|
|
37090
38187
|
});
|
|
37091
38188
|
}
|
|
38189
|
+
clearSlowTimer() {
|
|
38190
|
+
if (this.slowTimer !== null) {
|
|
38191
|
+
clearTimeout(this.slowTimer);
|
|
38192
|
+
this.slowTimer = null;
|
|
38193
|
+
}
|
|
38194
|
+
}
|
|
37092
38195
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: CockpitKpiWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
37093
38196
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: CockpitKpiWidgetComponent, isStandalone: true, selector: "mm-cockpit-kpi-widget", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: false, isRequired: false, transformFunction: null }, messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0, template: `
|
|
37094
38197
|
<ng-template #tileBody let-k>
|
|
@@ -37130,7 +38233,7 @@ class CockpitKpiWidgetComponent {
|
|
|
37130
38233
|
<p class="cw-message cw-error-text" role="status" data-state="error">{{ reason() }}</p>
|
|
37131
38234
|
}
|
|
37132
38235
|
@default {
|
|
37133
|
-
<p class="cw-message" role="status" data-state="loading">{{
|
|
38236
|
+
<p class="cw-message" role="status" data-state="loading" aria-live="polite">{{ loadingText() }}</p>
|
|
37134
38237
|
}
|
|
37135
38238
|
}
|
|
37136
38239
|
`, isInline: true, styles: [":host{display:block;width:100%;height:100%;--_cw-text: var(--mm-cockpit-text, var(--kendo-color-on-app-surface, #1f2937));--_cw-muted: var(--mm-cockpit-text-muted, var(--kendo-color-subtle, #6b7280));--_cw-surface: var(--mm-cockpit-surface, transparent);--_cw-border: var(--mm-cockpit-border, var(--kendo-color-border, rgba(0, 0, 0, .12)));--_cw-border-strong: var(--mm-cockpit-border-strong, var(--kendo-color-subtle, rgba(0, 0, 0, .3)));--_cw-success: var(--mm-cockpit-success, var(--kendo-color-success, #2e7d32));--_cw-warning: var(--mm-cockpit-warning, var(--kendo-color-warning, #b26a00));--_cw-error: var(--mm-cockpit-error, var(--kendo-color-error, #c62828));--_cw-info: var(--mm-cockpit-info, var(--kendo-color-info, #0277bd));--_cw-neutral: var(--mm-cockpit-neutral, var(--kendo-color-subtle, #6b7280));--_cw-accent: var(--mm-cockpit-accent, var(--kendo-color-primary, #1976d2));--_cw-ai: var(--mm-cockpit-ai, #7c3aed);color:var(--_cw-text)}.cw-status-chip{display:inline-flex;align-items:center;gap:6px;height:20px;padding:0 7px;border-radius:4px;font-size:.71875rem;font-weight:500;white-space:nowrap;color:var(--_cw-chip);background:color-mix(in srgb,var(--_cw-chip) 14%,transparent)}.cw-status-chip:before{content:\"\";width:6px;height:6px;border-radius:50%;background:currentColor}.cw-status-success{--_cw-chip: var(--_cw-success)}.cw-status-warning{--_cw-chip: var(--_cw-warning)}.cw-status-error{--_cw-chip: var(--_cw-error)}.cw-status-info{--_cw-chip: var(--_cw-info)}.cw-status-neutral{--_cw-chip: var(--_cw-neutral)}.cw-visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.cw-message{margin:0;padding:12px;color:var(--_cw-muted);font-size:.8125rem}a:focus-visible,button:focus-visible{outline:2px solid var(--_cw-accent);outline-offset:2px}\n", ".kpi-tile{display:flex;flex-direction:column;justify-content:center;gap:4px;height:100%;box-sizing:border-box;padding:8px 14px;color:inherit;text-decoration:none;min-width:0}a.kpi-tile:hover .kpi-value{text-decoration:underline;text-decoration-thickness:1px}.kpi-value{font-size:1.75rem;line-height:2.25rem;font-weight:600;font-variant-numeric:tabular-nums}.kpi-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.kpi-detail{color:var(--_cw-muted);font-size:.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kpi-spark{display:block;width:100%;height:36px;margin-top:6px}.spark-area{fill:color-mix(in srgb,var(--_cw-accent) 16%,transparent)}.spark-line{fill:none;stroke:var(--_cw-accent);stroke-width:1.5;vector-effect:non-scaling-stroke}.spark-dot{fill:var(--_cw-accent)}.cw-error-text{color:var(--_cw-error)}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -37177,13 +38280,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
37177
38280
|
<p class="cw-message cw-error-text" role="status" data-state="error">{{ reason() }}</p>
|
|
37178
38281
|
}
|
|
37179
38282
|
@default {
|
|
37180
|
-
<p class="cw-message" role="status" data-state="loading">{{
|
|
38283
|
+
<p class="cw-message" role="status" data-state="loading" aria-live="polite">{{ loadingText() }}</p>
|
|
37181
38284
|
}
|
|
37182
38285
|
}
|
|
37183
38286
|
`, styles: [":host{display:block;width:100%;height:100%;--_cw-text: var(--mm-cockpit-text, var(--kendo-color-on-app-surface, #1f2937));--_cw-muted: var(--mm-cockpit-text-muted, var(--kendo-color-subtle, #6b7280));--_cw-surface: var(--mm-cockpit-surface, transparent);--_cw-border: var(--mm-cockpit-border, var(--kendo-color-border, rgba(0, 0, 0, .12)));--_cw-border-strong: var(--mm-cockpit-border-strong, var(--kendo-color-subtle, rgba(0, 0, 0, .3)));--_cw-success: var(--mm-cockpit-success, var(--kendo-color-success, #2e7d32));--_cw-warning: var(--mm-cockpit-warning, var(--kendo-color-warning, #b26a00));--_cw-error: var(--mm-cockpit-error, var(--kendo-color-error, #c62828));--_cw-info: var(--mm-cockpit-info, var(--kendo-color-info, #0277bd));--_cw-neutral: var(--mm-cockpit-neutral, var(--kendo-color-subtle, #6b7280));--_cw-accent: var(--mm-cockpit-accent, var(--kendo-color-primary, #1976d2));--_cw-ai: var(--mm-cockpit-ai, #7c3aed);color:var(--_cw-text)}.cw-status-chip{display:inline-flex;align-items:center;gap:6px;height:20px;padding:0 7px;border-radius:4px;font-size:.71875rem;font-weight:500;white-space:nowrap;color:var(--_cw-chip);background:color-mix(in srgb,var(--_cw-chip) 14%,transparent)}.cw-status-chip:before{content:\"\";width:6px;height:6px;border-radius:50%;background:currentColor}.cw-status-success{--_cw-chip: var(--_cw-success)}.cw-status-warning{--_cw-chip: var(--_cw-warning)}.cw-status-error{--_cw-chip: var(--_cw-error)}.cw-status-info{--_cw-chip: var(--_cw-info)}.cw-status-neutral{--_cw-chip: var(--_cw-neutral)}.cw-visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.cw-message{margin:0;padding:12px;color:var(--_cw-muted);font-size:.8125rem}a:focus-visible,button:focus-visible{outline:2px solid var(--_cw-accent);outline-offset:2px}\n", ".kpi-tile{display:flex;flex-direction:column;justify-content:center;gap:4px;height:100%;box-sizing:border-box;padding:8px 14px;color:inherit;text-decoration:none;min-width:0}a.kpi-tile:hover .kpi-value{text-decoration:underline;text-decoration-thickness:1px}.kpi-value{font-size:1.75rem;line-height:2.25rem;font-weight:600;font-variant-numeric:tabular-nums}.kpi-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.kpi-detail{color:var(--_cw-muted);font-size:.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kpi-spark{display:block;width:100%;height:36px;margin-top:6px}.spark-area{fill:color-mix(in srgb,var(--_cw-accent) 16%,transparent)}.spark-line{fill:none;stroke:var(--_cw-accent);stroke-width:1.5;vector-effect:non-scaling-stroke}.spark-dot{fill:var(--_cw-accent)}.cw-error-text{color:var(--_cw-error)}\n"] }]
|
|
37184
38287
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
37185
38288
|
type: Input
|
|
37186
38289
|
}], messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }] } });
|
|
38290
|
+
/**
|
|
38291
|
+
* After this long still loading, a slow KPI says what it is doing instead of "Loading…"
|
|
38292
|
+
* (AB#5622: the "Blueprint updates" tile scans every catalog, which can take several seconds).
|
|
38293
|
+
*/
|
|
38294
|
+
const COCKPIT_KPI_SLOW_LOADING_MS = 1000;
|
|
37187
38295
|
/** What viewers without builder roles see instead of role requirements. */
|
|
37188
38296
|
const NOT_AVAILABLE_TEXT = 'Not available';
|
|
37189
38297
|
/** The KPI a cockpit KPI widget shows. */
|
|
@@ -37193,6 +38301,11 @@ function kpiKindOf(config) {
|
|
|
37193
38301
|
return 'ckModelState';
|
|
37194
38302
|
case 'pipelineExecutions':
|
|
37195
38303
|
return 'pipelineExecutions';
|
|
38304
|
+
case 'tenantCount':
|
|
38305
|
+
case 'blueprintUpdates':
|
|
38306
|
+
case 'servicesHealth':
|
|
38307
|
+
case 'versionInfo':
|
|
38308
|
+
return config.type;
|
|
37196
38309
|
default:
|
|
37197
38310
|
return 'adapterStatus';
|
|
37198
38311
|
}
|
|
@@ -37571,11 +38684,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
37571
38684
|
|
|
37572
38685
|
/** The library's attention providers, in display order (errors of the platform first). */
|
|
37573
38686
|
const BUILT_IN_ATTENTION_PROVIDERS = [
|
|
38687
|
+
// System tenant only (AB#5558): hidden — and never loaded — on tenant cockpits.
|
|
38688
|
+
ServicesHealthAttentionProvider,
|
|
37574
38689
|
CkModelsResolveFailedAttentionProvider,
|
|
37575
38690
|
AdaptersAttentionProvider,
|
|
37576
38691
|
FailedExecutionsAttentionProvider,
|
|
37577
38692
|
UnregisteredPoolsAttentionProvider,
|
|
37578
|
-
FeaturesNotInstalledAttentionProvider
|
|
38693
|
+
FeaturesNotInstalledAttentionProvider,
|
|
38694
|
+
BlueprintUpdatesAttentionProvider
|
|
37579
38695
|
];
|
|
37580
38696
|
function parseConfig(data) {
|
|
37581
38697
|
if (!data.config) {
|
|
@@ -37606,7 +38722,11 @@ function positiveInteger(value) {
|
|
|
37606
38722
|
const KPI_DESCRIPTIONS = {
|
|
37607
38723
|
adapterStatus: 'Adapters online of the adapters expected to run — the same rule as the Integration overview. Needs CommunicationManagement.',
|
|
37608
38724
|
ckModelState: 'Construction Kit models available of all installed models; ResolveFailed is shown as an error. Needs AdminPanelManagement.',
|
|
37609
|
-
pipelineExecutions: 'Pipeline executions of all data flows in the last 24 hours with failures and an hourly sparkline — the same counting as the Data Flows list. Needs CommunicationManagement.'
|
|
38725
|
+
pipelineExecutions: 'Pipeline executions of all data flows in the last 24 hours with failures and an hourly sparkline — the same counting as the Data Flows list. Needs CommunicationManagement.',
|
|
38726
|
+
tenantCount: 'Child tenants of this tenant — on the system tenant every tenant of the installation. Needs TenantManagement.',
|
|
38727
|
+
blueprintUpdates: 'Installed blueprints with a newer catalog version; service-managed ones are named separately. Needs AdminPanelManagement.',
|
|
38728
|
+
servicesHealth: 'Platform services (Identity, Asset Repository, Bot, Communication Controller) whose health check reports Healthy.',
|
|
38729
|
+
versionInfo: 'The versions the host application knows, e.g. its own build.'
|
|
37610
38730
|
};
|
|
37611
38731
|
/** Registration of one cockpit KPI widget type (one component and dialog for all three). */
|
|
37612
38732
|
function registerKpiWidget(registry, type, label, defaultSize) {
|
|
@@ -37664,7 +38784,8 @@ function registerKpiWidget(registry, type, label, defaultSize) {
|
|
|
37664
38784
|
}
|
|
37665
38785
|
/**
|
|
37666
38786
|
* Registers the cockpit widgets (AB#5558): "Attention list", "Adapter status", "CK model state",
|
|
37667
|
-
* "Pipeline executions 24 h"
|
|
38787
|
+
* "Pipeline executions 24 h", the system cockpit's "Tenants", "Blueprint updates", "Services
|
|
38788
|
+
* healthy" and "Version", and "Recent items". Persisted with `dataSourceType: 'static'` and a small JSON
|
|
37668
38789
|
* config (see the README widget catalogue for the exact keys).
|
|
37669
38790
|
*/
|
|
37670
38791
|
function registerCockpitWidgets(registry) {
|
|
@@ -37721,6 +38842,11 @@ function registerCockpitWidgets(registry) {
|
|
|
37721
38842
|
registerKpiWidget(registry, 'adapterStatus', 'Adapter Status', { colSpan: 2, rowSpan: 1 });
|
|
37722
38843
|
registerKpiWidget(registry, 'ckModelState', 'CK Model State', { colSpan: 2, rowSpan: 1 });
|
|
37723
38844
|
registerKpiWidget(registry, 'pipelineExecutions', 'Pipeline Executions 24 h', { colSpan: 2, rowSpan: 1 });
|
|
38845
|
+
// System cockpit (AB#5558).
|
|
38846
|
+
registerKpiWidget(registry, 'tenantCount', 'Tenants', { colSpan: 2, rowSpan: 1 });
|
|
38847
|
+
registerKpiWidget(registry, 'blueprintUpdates', 'Blueprint Updates', { colSpan: 2, rowSpan: 1 });
|
|
38848
|
+
registerKpiWidget(registry, 'servicesHealth', 'Services Healthy', { colSpan: 2, rowSpan: 1 });
|
|
38849
|
+
registerKpiWidget(registry, 'versionInfo', 'Version', { colSpan: 2, rowSpan: 1 });
|
|
37724
38850
|
registry.registerWidget({
|
|
37725
38851
|
type: 'recentItems',
|
|
37726
38852
|
label: 'Recent Items',
|
|
@@ -37777,5 +38903,5 @@ function provideCockpitWidgets() {
|
|
|
37777
38903
|
* Generated bundle index. Do not edit.
|
|
37778
38904
|
*/
|
|
37779
38905
|
|
|
37780
|
-
export { ADAPTER_OFFLINE_GRACE_MS, ATTENTION_SEVERITY_ORDER, AdaptersAttentionProvider, AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, AttentionListConfigDialogComponent, AttentionListWidgetComponent, AutoRefreshTimerService, BUILT_IN_ATTENTION_PROVIDERS, BarChartConfigDialogComponent, BarChartWidgetComponent, COCKPIT_ADAPTER_LIMIT, COCKPIT_ATTENTION_PROVIDERS, COCKPIT_DATA_FLOW_LIMIT, COCKPIT_EXPLAIN_HANDLER, COCKPIT_FAILED_EXECUTIONS_OPTIONS, COCKPIT_KPI_GATES, COCKPIT_LINK_RESOLVER, COCKPIT_RECENT_ITEMS, COCKPIT_ROLES, COCKPIT_VIEWER_ACCESS, COCKPIT_WIDGET_MESSAGES, COCKPIT_WIDGET_TYPES, CkModelsResolveFailedAttentionProvider, CockpitAdapterStatesService, CockpitAttentionService, CockpitCkModelStatesService, CockpitContextService, CockpitDataFlowExecutionsService, CockpitKpiConfigDialogComponent, CockpitKpiService, CockpitKpiWidgetComponent, DAY_MS, DEFAULT_ATTENTION_MAX_ITEMS, DEFAULT_COCKPIT_WIDGET_MESSAGES, DEFAULT_FAILED_EXECUTIONS_OPTIONS, DEFAULT_RECENT_ITEMS_MAX, DEFAULT_TIME_ZONE_MODE, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, FailedExecutionsAttentionProvider, FeaturesNotInstalledAttentionProvider, GaugeConfigDialogComponent, GaugeWidgetComponent, HISTOGRAM_HOURS, HOUR_MS, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KPI_ERROR_TEXT, KpiConfigDialogComponent, KpiWidgetComponent, LazyMarkdownComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MAX_RECENT_ITEMS, MESHBOARD_CHROME_ROUTE_DATA, MESHBOARD_DESCRIPTION_MARKER, MESHBOARD_EDIT_QUERY_PARAM, MESHBOARD_HEADER_MODE_ROUTE_DATA, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, NOT_AVAILABLE_TEXT, PieChartConfigDialogComponent, PieChartWidgetComponent, QueryExecutorService, QuerySelectorComponent, RecentItemsConfigDialogComponent, RecentItemsWidgetComponent, RuntimeEntitySelectorComponent, STATE_STATUS_BY_KEY, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, UnregisteredPoolsAttentionProvider, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, adapterKpi, adaptersInError, adaptersOffline, buildHourlyHistogram, categoryStatus, ckModelKpi, classifyQuery, compareMeshBoardNavigation, countFlowExecutions, countLabel, countPipelineExecutions, enabledButNotInstalled, executionKpi, failedExecutionsSeverity, formatCockpitMessage, formatCount, humanizeCategory, isAdapterAtRest, isAdapterExpectedToRun, isAdapterOnline, isCockpitWidgetType, isMeshBoardChrome, isMeshBoardHeaderMode, isServiceConfigured, joinEncodedDescription, kpiKindOf, latestStatisticsUpdate, loadNgxMarkdown, nameList, newMeshBoardConfig, normalizeNavigation, observeThemeChanges, pipelineExecutionInputs, plural, provideCockpitWidgetHost, provideCockpitWidgets, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, queryFamily, readMeshBoardNavigation, recentRelativeTime, registerCockpitWidgets, registerDefaultWidgets, registerProcessWidget, resolveCockpitWidgetMessages, resolveMeshBoardChrome, resolveMeshBoardHeaderMode, selectProviders, sortAttentionFindings, sparklineGeometry, splitEncodedDescription, statusColor, summarizeAdapterOnline, truncationNote, withMeshBoardNavigation };
|
|
38906
|
+
export { ADAPTER_OFFLINE_GRACE_MS, ATTENTION_SEVERITY_ORDER, AdaptersAttentionProvider, AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, AttentionListConfigDialogComponent, AttentionListWidgetComponent, AutoRefreshTimerService, BUILT_IN_ATTENTION_PROVIDERS, BarChartConfigDialogComponent, BarChartWidgetComponent, BlueprintUpdatesAttentionProvider, COCKPIT_ADAPTER_LIMIT, COCKPIT_ATTENTION_PROVIDERS, COCKPIT_BLUEPRINT_CATALOG_LIMIT, COCKPIT_CHILD_TENANT_IDS, COCKPIT_DATA_FLOW_LIMIT, COCKPIT_EXPLAIN_HANDLER, COCKPIT_FAILED_EXECUTIONS_OPTIONS, COCKPIT_KPI_GATES, COCKPIT_KPI_SLOW_LOADING_MS, COCKPIT_LINK_RESOLVER, COCKPIT_RECENT_ITEMS, COCKPIT_ROLES, COCKPIT_SERVICE_NAMES, COCKPIT_VERSION_SOURCE, COCKPIT_VIEWER_ACCESS, COCKPIT_WIDGET_MESSAGES, COCKPIT_WIDGET_TYPES, CkModelsResolveFailedAttentionProvider, CockpitAdapterStatesService, CockpitAttentionService, CockpitBlueprintStatusService, CockpitChildTenantsService, CockpitCkModelStatesService, CockpitContextService, CockpitDataFlowExecutionsService, CockpitKpiConfigDialogComponent, CockpitKpiService, CockpitKpiWidgetComponent, CockpitServiceHealthService, DAY_MS, DEFAULT_ATTENTION_MAX_ITEMS, DEFAULT_COCKPIT_WIDGET_MESSAGES, DEFAULT_FAILED_EXECUTIONS_OPTIONS, DEFAULT_MESHBOARD_MESSAGES, DEFAULT_RECENT_ITEMS_MAX, DEFAULT_TIME_ZONE_MODE, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, FALLBACK_SERIES_COLORS, FailedExecutionsAttentionProvider, FeaturesNotInstalledAttentionProvider, GaugeConfigDialogComponent, GaugeWidgetComponent, HISTOGRAM_HOURS, HOUR_MS, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KPI_ERROR_TEXT, KpiConfigDialogComponent, KpiWidgetComponent, LazyMarkdownComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MAX_RECENT_ITEMS, MESHBOARD_CHROME_ROUTE_DATA, MESHBOARD_DESCRIPTION_MARKER, MESHBOARD_EDIT_QUERY_PARAM, MESHBOARD_HEADER_MODE_ROUTE_DATA, MESHBOARD_LABEL_RESOLVER, MESHBOARD_MESSAGES, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, NOT_AVAILABLE_TEXT, PieChartConfigDialogComponent, PieChartWidgetComponent, QueryExecutorService, QuerySelectorComponent, RecentItemsConfigDialogComponent, RecentItemsWidgetComponent, RuntimeEntitySelectorComponent, SERVICE_MANAGED_BLUEPRINT_PREFIX, STATE_STATUS_BY_KEY, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, ServicesHealthAttentionProvider, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, UnregisteredPoolsAttentionProvider, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, adapterKpi, adaptersInError, adaptersOffline, blueprintUpdatesKpi, buildHourlyHistogram, categoryStatus, chartSeriesColors, ckModelKpi, classifyQuery, compareBlueprintVersions, compareMeshBoardNavigation, countFlowExecutions, countLabel, countPipelineExecutions, enabledButNotInstalled, executionKpi, failedExecutionsSeverity, formatCockpitMessage, formatCount, humanizeCategory, isAdapterAtRest, isAdapterExpectedToRun, isAdapterOnline, isCockpitWidgetType, isMeshBoardChrome, isMeshBoardHeaderMode, isServiceConfigured, joinEncodedDescription, kpiKindOf, latestStatisticsUpdate, legacyCockpitLinkTarget, loadNgxMarkdown, nameList, newMeshBoardConfig, normalizeNavigation, observeThemeChanges, pipelineExecutionInputs, plural, provideCockpitWidgetHost, provideCockpitWidgets, provideDefaultWidgets, provideMeshBoard, provideMeshBoardMessages, provideProcessWidget, provideWidgetRegistrations, queryFamily, readMeshBoardNavigation, recentRelativeTime, registerCockpitWidgets, registerDefaultWidgets, registerProcessWidget, resolveChartColor, resolveCockpitWidgetMessages, resolveMeshBoardChrome, resolveMeshBoardHeaderMode, resolveMeshBoardLabel, resolveMeshBoardMessages, selectProviders, serviceStateText, servicesHealthKpi, sortAttentionFindings, sparklineGeometry, splitBlueprintId, splitEncodedDescription, statusColor, summarizeAdapterOnline, tenantCountKpi, toBlueprintStatus, toServiceHealthStatus, truncationNote, versionKpi, withMeshBoardNavigation };
|
|
37781
38907
|
//# sourceMappingURL=meshmakers-octo-meshboard.mjs.map
|