@meshmakers/octo-meshboard 3.4.210 → 3.4.230
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.
|
@@ -19,13 +19,13 @@ import * as i2 from '@progress/kendo-angular-buttons';
|
|
|
19
19
|
import { ButtonsModule, ButtonModule } from '@progress/kendo-angular-buttons';
|
|
20
20
|
import * as i3 from '@progress/kendo-angular-inputs';
|
|
21
21
|
import { InputsModule, CheckBoxModule, NumericTextBoxModule, TextBoxModule, FormFieldModule } from '@progress/kendo-angular-inputs';
|
|
22
|
+
import * as i4 from '@progress/kendo-angular-dropdowns';
|
|
23
|
+
import { DropDownsModule, DropDownListModule } from '@progress/kendo-angular-dropdowns';
|
|
22
24
|
import * as i1$2 from '@progress/kendo-angular-indicators';
|
|
23
25
|
import { LoaderModule } from '@progress/kendo-angular-indicators';
|
|
24
26
|
import * as i1$4 from '@progress/kendo-angular-icons';
|
|
25
27
|
import { SVGIconModule } from '@progress/kendo-angular-icons';
|
|
26
28
|
import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, circleIcon, questionCircleIcon, minusCircleIcon, warningTriangleIcon, exclamationCircleIcon, xCircleIcon, checkCircleIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, infoCircleIcon, plusIcon, trashIcon, pencilIcon, chartLineIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, checkIcon, xIcon, downloadIcon, uploadIcon, gearIcon, saveIcon, arrowRotateCwIcon, undoIcon } from '@progress/kendo-svg-icons';
|
|
27
|
-
import * as i4 from '@progress/kendo-angular-dropdowns';
|
|
28
|
-
import { DropDownsModule, DropDownListModule } from '@progress/kendo-angular-dropdowns';
|
|
29
29
|
import { NotificationService } from '@progress/kendo-angular-notification';
|
|
30
30
|
import * as i1$6 from '@progress/kendo-angular-gauges';
|
|
31
31
|
import { CollectionChangesService, KENDO_GAUGES } from '@progress/kendo-angular-gauges';
|
|
@@ -49,6 +49,8 @@ import { BreadCrumbService } from '@meshmakers/shared-services';
|
|
|
49
49
|
* MeshBoard Widget Models
|
|
50
50
|
* Defines the interfaces for MeshBoard widgets and their data sources
|
|
51
51
|
*/
|
|
52
|
+
/** The board-level default timezone mode applied when none is persisted. */
|
|
53
|
+
const DEFAULT_TIME_ZONE_MODE = 'local';
|
|
52
54
|
|
|
53
55
|
// Re-export from octo-services (moved there for reuse by octo-ui)
|
|
54
56
|
|
|
@@ -1503,7 +1505,7 @@ class MeshBoardPersistenceService {
|
|
|
1503
1505
|
*/
|
|
1504
1506
|
async createMeshBoard(config) {
|
|
1505
1507
|
// Encode variables, timeFilter, and entitySelectors in description field (temporary until backend adds config field)
|
|
1506
|
-
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds);
|
|
1508
|
+
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds, config.timeZoneMode);
|
|
1507
1509
|
const dashboardInput = {
|
|
1508
1510
|
name: config.name,
|
|
1509
1511
|
description: encodedDescription,
|
|
@@ -1535,7 +1537,7 @@ class MeshBoardPersistenceService {
|
|
|
1535
1537
|
async updateMeshBoard(rtId, config, existingWidgetRtIds = []) {
|
|
1536
1538
|
const result = { createdWidgets: [] };
|
|
1537
1539
|
// Encode variables, timeFilter, and entitySelectors in description field (temporary until backend adds config field)
|
|
1538
|
-
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds);
|
|
1540
|
+
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds, config.timeZoneMode);
|
|
1539
1541
|
const dashboardItem = {
|
|
1540
1542
|
name: config.name,
|
|
1541
1543
|
description: encodedDescription,
|
|
@@ -1599,7 +1601,7 @@ class MeshBoardPersistenceService {
|
|
|
1599
1601
|
toMeshBoardConfig(meshBoard, widgets) {
|
|
1600
1602
|
// Decode variables, timeFilter, entitySelectors, and autoRefresh from description field
|
|
1601
1603
|
// (temporary until backend adds first-class config field)
|
|
1602
|
-
const { description, variables, timeFilter, entitySelectors, autoRefreshSeconds } = this.decodeVariablesFromDescription(meshBoard.description);
|
|
1604
|
+
const { description, variables, timeFilter, entitySelectors, autoRefreshSeconds, timeZoneMode } = this.decodeVariablesFromDescription(meshBoard.description);
|
|
1603
1605
|
return {
|
|
1604
1606
|
id: meshBoard.rtId,
|
|
1605
1607
|
name: meshBoard.name,
|
|
@@ -1610,6 +1612,7 @@ class MeshBoardPersistenceService {
|
|
|
1610
1612
|
gap: meshBoard.gap,
|
|
1611
1613
|
variables,
|
|
1612
1614
|
timeFilter,
|
|
1615
|
+
timeZoneMode,
|
|
1613
1616
|
entitySelectors,
|
|
1614
1617
|
autoRefreshSeconds,
|
|
1615
1618
|
widgets: widgets.map(w => this.toWidgetConfig(w))
|
|
@@ -1733,13 +1736,16 @@ class MeshBoardPersistenceService {
|
|
|
1733
1736
|
* Note: Only static variables are persisted. TimeFilter variables are derived
|
|
1734
1737
|
* from the timeFilter selection when the MeshBoard is loaded.
|
|
1735
1738
|
*/
|
|
1736
|
-
encodeVariablesInDescription(description, variables, timeFilter, entitySelectors, autoRefreshSeconds) {
|
|
1739
|
+
encodeVariablesInDescription(description, variables, timeFilter, entitySelectors, autoRefreshSeconds, timeZoneMode) {
|
|
1737
1740
|
// Filter out timeFilter and entitySelector variables (they are derived, not persisted directly)
|
|
1738
1741
|
const staticVariables = variables?.filter(v => v.source !== 'timeFilter' && v.source !== 'entitySelector');
|
|
1739
|
-
// Check if there's anything to encode
|
|
1742
|
+
// Check if there's anything to encode. 'local' is the default, so only the
|
|
1743
|
+
// non-default 'utc' mode needs persisting.
|
|
1740
1744
|
const hasEntitySelectors = entitySelectors && entitySelectors.length > 0;
|
|
1741
1745
|
const hasAutoRefresh = !!autoRefreshSeconds && autoRefreshSeconds > 0;
|
|
1742
|
-
|
|
1746
|
+
const hasTimeZoneMode = timeZoneMode === 'utc';
|
|
1747
|
+
if ((!staticVariables || staticVariables.length === 0) &&
|
|
1748
|
+
!timeFilter?.enabled && !hasEntitySelectors && !hasAutoRefresh && !hasTimeZoneMode) {
|
|
1743
1749
|
return description;
|
|
1744
1750
|
}
|
|
1745
1751
|
try {
|
|
@@ -1750,6 +1756,9 @@ class MeshBoardPersistenceService {
|
|
|
1750
1756
|
if (timeFilter?.enabled) {
|
|
1751
1757
|
data.timeFilter = timeFilter;
|
|
1752
1758
|
}
|
|
1759
|
+
if (hasTimeZoneMode) {
|
|
1760
|
+
data.timeZoneMode = timeZoneMode;
|
|
1761
|
+
}
|
|
1753
1762
|
if (hasAutoRefresh) {
|
|
1754
1763
|
data.autoRefreshSeconds = autoRefreshSeconds;
|
|
1755
1764
|
}
|
|
@@ -1805,6 +1814,7 @@ class MeshBoardPersistenceService {
|
|
|
1805
1814
|
description,
|
|
1806
1815
|
variables: parsed.variables ?? [],
|
|
1807
1816
|
timeFilter: parsed.timeFilter,
|
|
1817
|
+
timeZoneMode: parsed.timeZoneMode === 'utc' || parsed.timeZoneMode === 'local' ? parsed.timeZoneMode : undefined,
|
|
1808
1818
|
entitySelectors: parsed.entitySelectors,
|
|
1809
1819
|
autoRefreshSeconds: typeof parsed.autoRefreshSeconds === 'number' ? parsed.autoRefreshSeconds : undefined
|
|
1810
1820
|
};
|
|
@@ -2030,6 +2040,13 @@ class MeshBoardStateService {
|
|
|
2030
2040
|
...(ngDevMode ? [{ debugName: "isModelAvailable" }] : /* istanbul ignore next */ []));
|
|
2031
2041
|
variableResolutionErrors = computed(() => this._variableResolutionErrors(), /* @ts-ignore */
|
|
2032
2042
|
...(ngDevMode ? [{ debugName: "variableResolutionErrors" }] : /* istanbul ignore next */ []));
|
|
2043
|
+
/**
|
|
2044
|
+
* The board's effective timezone mode (defaults to `'local'`). Drives both the
|
|
2045
|
+
* time-filter boundary computation and datetime display across all widgets.
|
|
2046
|
+
* Widgets read this to format timestamps consistently with the active filter.
|
|
2047
|
+
*/
|
|
2048
|
+
timeZoneMode = computed(() => this._meshBoardConfig().timeZoneMode ?? DEFAULT_TIME_ZONE_MODE, /* @ts-ignore */
|
|
2049
|
+
...(ngDevMode ? [{ debugName: "timeZoneMode" }] : /* istanbul ignore next */ []));
|
|
2033
2050
|
// Deprecated aliases for backward compatibility
|
|
2034
2051
|
/** @deprecated Use meshBoardConfig instead */
|
|
2035
2052
|
dashboardConfig = this.meshBoardConfig;
|
|
@@ -2269,6 +2286,7 @@ class MeshBoardStateService {
|
|
|
2269
2286
|
columns: settings.columns,
|
|
2270
2287
|
rowHeight: settings.rowHeight,
|
|
2271
2288
|
gap: settings.gap,
|
|
2289
|
+
timeZoneMode: settings.timeZoneMode ?? config.timeZoneMode,
|
|
2272
2290
|
variables: settings.variables ?? config.variables,
|
|
2273
2291
|
timeFilter: settings.timeFilter
|
|
2274
2292
|
? {
|
|
@@ -2300,6 +2318,7 @@ class MeshBoardStateService {
|
|
|
2300
2318
|
gap: config.gap,
|
|
2301
2319
|
variables: config.variables ?? [],
|
|
2302
2320
|
timeFilter: config.timeFilter,
|
|
2321
|
+
timeZoneMode: config.timeZoneMode,
|
|
2303
2322
|
entitySelectors: config.entitySelectors,
|
|
2304
2323
|
autoRefreshSeconds: config.autoRefreshSeconds
|
|
2305
2324
|
};
|
|
@@ -2475,8 +2494,9 @@ class MeshBoardStateService {
|
|
|
2475
2494
|
* Stream-data persistent queries consume this to bound their result set;
|
|
2476
2495
|
* runtime queries ignore it.
|
|
2477
2496
|
*
|
|
2478
|
-
*
|
|
2479
|
-
*
|
|
2497
|
+
* Boundaries are resolved on the board's {@link timeZoneMode} basis (local by
|
|
2498
|
+
* default, or UTC), the same basis widgets format their datetime display on —
|
|
2499
|
+
* so the selected range and the displayed timestamps stay consistent.
|
|
2480
2500
|
*/
|
|
2481
2501
|
resolveCurrentTimeRange() {
|
|
2482
2502
|
const config = this._meshBoardConfig().timeFilter;
|
|
@@ -2492,7 +2512,7 @@ class MeshBoardStateService {
|
|
|
2492
2512
|
customFrom: selection.customFrom ? new Date(selection.customFrom) : undefined,
|
|
2493
2513
|
customTo: selection.customTo ? new Date(selection.customTo) : undefined
|
|
2494
2514
|
};
|
|
2495
|
-
return TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
2515
|
+
return TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.timeZoneMode());
|
|
2496
2516
|
}
|
|
2497
2517
|
/**
|
|
2498
2518
|
* Resolves the active time filter into stream-data `{from, to}` arguments for
|
|
@@ -4017,6 +4037,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4017
4037
|
|
|
4018
4038
|
class EntityCardWidgetComponent {
|
|
4019
4039
|
dataService = inject(DashboardDataService);
|
|
4040
|
+
stateService = inject(MeshBoardStateService);
|
|
4041
|
+
variableService = inject(MeshBoardVariableService);
|
|
4020
4042
|
config;
|
|
4021
4043
|
// Widget state signals
|
|
4022
4044
|
_isLoading = signal(false, /* @ts-ignore */
|
|
@@ -4037,7 +4059,10 @@ class EntityCardWidgetComponent {
|
|
|
4037
4059
|
if (!dataSource)
|
|
4038
4060
|
return true;
|
|
4039
4061
|
if (dataSource.type === 'runtimeEntity') {
|
|
4040
|
-
|
|
4062
|
+
// A selector binding or a variable-bearing rtId/ckTypeId counts as
|
|
4063
|
+
// configured even before a board-level selection has resolved a value —
|
|
4064
|
+
// the empty/loading display is handled by loadData(), not here.
|
|
4065
|
+
return !dataSource.entitySelectorId && !dataSource.rtId && !dataSource.ckTypeId;
|
|
4041
4066
|
}
|
|
4042
4067
|
if (dataSource.type === 'static') {
|
|
4043
4068
|
return false; // Static data is always "configured"
|
|
@@ -4114,11 +4139,23 @@ class EntityCardWidgetComponent {
|
|
|
4114
4139
|
return;
|
|
4115
4140
|
}
|
|
4116
4141
|
// Handle runtime entity data source
|
|
4117
|
-
// Note: isNotConfigured() check ensures rtId and ckTypeId are set
|
|
4118
4142
|
if (dataSource.type === 'runtimeEntity') {
|
|
4143
|
+
// Resolve the effective entity reference: an entity-selector binding or
|
|
4144
|
+
// MeshBoard variables (e.g. $mp_rtId / $mp_rtCkTypeId) may supply the
|
|
4145
|
+
// rtId/ckTypeId from a board-level selection.
|
|
4146
|
+
const { rtId, ckTypeId } = this.resolveEntityRef(dataSource);
|
|
4147
|
+
// No resolvable selection yet (selector unpicked or variable unresolved) —
|
|
4148
|
+
// show nothing rather than fetching with a placeholder string.
|
|
4149
|
+
if (!rtId || !ckTypeId || this.variableService.hasUnresolvedVariables(rtId) ||
|
|
4150
|
+
this.variableService.hasUnresolvedVariables(ckTypeId)) {
|
|
4151
|
+
this._data.set(null);
|
|
4152
|
+
this._error.set(null);
|
|
4153
|
+
this._isLoading.set(false);
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4119
4156
|
this._isLoading.set(true);
|
|
4120
4157
|
this._error.set(null);
|
|
4121
|
-
this.dataService.fetchEntityWithAssociations(
|
|
4158
|
+
this.dataService.fetchEntityWithAssociations(rtId, ckTypeId)
|
|
4122
4159
|
.pipe(catchError(err => {
|
|
4123
4160
|
console.error('Error loading entity card data:', err);
|
|
4124
4161
|
this._error.set('Failed to load data');
|
|
@@ -4130,6 +4167,51 @@ class EntityCardWidgetComponent {
|
|
|
4130
4167
|
});
|
|
4131
4168
|
}
|
|
4132
4169
|
}
|
|
4170
|
+
/**
|
|
4171
|
+
* Message for the neutral empty state — shown when the widget IS configured
|
|
4172
|
+
* but no entity is currently resolved (e.g. a bound selector hasn't been
|
|
4173
|
+
* picked yet, or a variable is still unresolved). This is deliberately not the
|
|
4174
|
+
* red "not configured" placeholder, which would wrongly imply the widget needs
|
|
4175
|
+
* setup.
|
|
4176
|
+
*/
|
|
4177
|
+
emptyMessage() {
|
|
4178
|
+
const dataSource = this.config?.dataSource;
|
|
4179
|
+
if (dataSource?.type === 'runtimeEntity') {
|
|
4180
|
+
if (dataSource.entitySelectorId) {
|
|
4181
|
+
return 'No entity selected';
|
|
4182
|
+
}
|
|
4183
|
+
if ((dataSource.rtId?.includes('$')) || (dataSource.ckTypeId?.includes('$'))) {
|
|
4184
|
+
return 'Waiting for selection';
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
return 'No data';
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Resolves the effective `{ rtId, ckTypeId }` to fetch from a runtime-entity
|
|
4191
|
+
* data source.
|
|
4192
|
+
*
|
|
4193
|
+
* - When `entitySelectorId` is set, the bound MeshBoard entity selector's
|
|
4194
|
+
* current `selectedRtId` and the picked entity's type
|
|
4195
|
+
* (`$<selectorId>_rtCkTypeId`, falling back to the selector's configured
|
|
4196
|
+
* `ckTypeId`) win — so the card follows the asset picked at board level.
|
|
4197
|
+
* - Otherwise the configured `rtId`/`ckTypeId` are resolved against the active
|
|
4198
|
+
* MeshBoard variables, allowing values like `$mp_rtId` / `$mp_rtCkTypeId`.
|
|
4199
|
+
*/
|
|
4200
|
+
resolveEntityRef(dataSource) {
|
|
4201
|
+
const variables = this.stateService.getVariables();
|
|
4202
|
+
if (dataSource.entitySelectorId) {
|
|
4203
|
+
const selector = this.stateService.getEntitySelector(dataSource.entitySelectorId);
|
|
4204
|
+
const rtCkTypeIdVar = variables.find(v => v.name === `${dataSource.entitySelectorId}_rtCkTypeId`);
|
|
4205
|
+
return {
|
|
4206
|
+
rtId: selector?.selectedRtId,
|
|
4207
|
+
ckTypeId: rtCkTypeIdVar?.value || selector?.ckTypeId
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
return {
|
|
4211
|
+
rtId: dataSource.rtId ? this.variableService.resolveVariables(dataSource.rtId, variables) : undefined,
|
|
4212
|
+
ckTypeId: dataSource.ckTypeId ? this.variableService.resolveVariables(dataSource.ckTypeId, variables) : undefined
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4133
4215
|
inferAttributeType(value) {
|
|
4134
4216
|
if (value === null || value === undefined)
|
|
4135
4217
|
return AttributeValueTypeDto.StringDto;
|
|
@@ -4163,11 +4245,11 @@ class EntityCardWidgetComponent {
|
|
|
4163
4245
|
.trim();
|
|
4164
4246
|
}
|
|
4165
4247
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: EntityCardWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4166
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: EntityCardWidgetComponent, isStandalone: true, selector: "mm-entity-card-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"entity-card\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Header (like UML class name) -->\n @if (config.showHeader !== false) {\n <div class=\"entity-header\">\n <span class=\"entity-stereotype\">«{{ entityTypeName() }}»</span>\n <span class=\"entity-name\">{{ displayName() }}</span>\n </div>\n }\n\n <!-- Attributes Section (like UML attributes) -->\n @if (config.showAttributes !== false && filteredAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">\n <mm-property-value-display\n [value]=\"attr.value\"\n [type]=\"inferAttributeType(attr.value)\"\n [attributeName]=\"attr.attributeName\">\n </mm-property-value-display>\n </span>\n </div>\n }\n </div>\n }\n\n <!-- Metadata Footer -->\n <div class=\"entity-footer\">\n <span class=\"entity-id\" title=\"Runtime ID\">{{ data()!.rtId }}</span>\n </div>\n } @else {\n <
|
|
4248
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: EntityCardWidgetComponent, isStandalone: true, selector: "mm-entity-card-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"entity-card\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Header (like UML class name) -->\n @if (config.showHeader !== false) {\n <div class=\"entity-header\">\n <span class=\"entity-stereotype\">«{{ entityTypeName() }}»</span>\n <span class=\"entity-name\">{{ displayName() }}</span>\n </div>\n }\n\n <!-- Attributes Section (like UML attributes) -->\n @if (config.showAttributes !== false && filteredAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">\n <mm-property-value-display\n [value]=\"attr.value\"\n [type]=\"inferAttributeType(attr.value)\"\n [attributeName]=\"attr.attributeName\">\n </mm-property-value-display>\n </span>\n </div>\n }\n </div>\n }\n\n <!-- Metadata Footer -->\n <div class=\"entity-footer\">\n <span class=\"entity-id\" title=\"Runtime ID\">{{ data()!.rtId }}</span>\n </div>\n } @else {\n <div class=\"entity-empty\">\n <span class=\"entity-empty-text\">{{ emptyMessage() }}</span>\n </div>\n }\n</div>\n", styles: [".entity-card{display:flex;flex-direction:column;height:100%;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden;background:var(--kendo-color-surface, #fff)}.entity-card.no-data,.entity-card.loading,.entity-card.error{justify-content:center;align-items:center}.entity-card.loading{color:var(--kendo-color-primary, #0d6efd)}.entity-card.error{color:var(--kendo-color-error, #dc3545)}.loading-indicator{font-size:.875rem;font-style:italic}.error-message{font-size:.875rem;padding:12px;text-align:center}.entity-header{display:flex;flex-direction:column;align-items:center;padding:12px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);text-align:center}.entity-header .entity-stereotype{font-size:.75rem;font-style:italic;opacity:.9;margin-bottom:2px}.entity-header .entity-name{font-size:1rem;font-weight:600}.entity-attributes{flex:1;padding:8px 0;overflow-y:auto}.entity-attributes .attribute-row{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--kendo-color-border-alt, #e9ecef);font-size:.8125rem}.entity-attributes .attribute-row:last-child{border-bottom:none}.entity-attributes .attribute-row:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d);flex-shrink:0;margin-right:12px}.entity-attributes .attribute-value{color:var(--kendo-color-on-surface, #212529);font-weight:500;text-align:right;word-break:break-word;min-width:0;flex:1}.entity-footer{padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6)}.entity-footer .entity-id{font-size:.6875rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.entity-empty{display:flex;align-items:center;justify-content:center;height:100%;padding:8px;text-align:center}.entity-empty-text{font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }, { kind: "component", type: PropertyValueDisplayComponent, selector: "mm-property-value-display", inputs: ["value", "type", "displayMode", "attributeName"], outputs: ["binaryDownload"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
4167
4249
|
}
|
|
4168
4250
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: EntityCardWidgetComponent, decorators: [{
|
|
4169
4251
|
type: Component,
|
|
4170
|
-
args: [{ selector: 'mm-entity-card-widget', standalone: true, imports: [CommonModule, WidgetNotConfiguredComponent, PropertyValueDisplayComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"entity-card\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Header (like UML class name) -->\n @if (config.showHeader !== false) {\n <div class=\"entity-header\">\n <span class=\"entity-stereotype\">«{{ entityTypeName() }}»</span>\n <span class=\"entity-name\">{{ displayName() }}</span>\n </div>\n }\n\n <!-- Attributes Section (like UML attributes) -->\n @if (config.showAttributes !== false && filteredAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">\n <mm-property-value-display\n [value]=\"attr.value\"\n [type]=\"inferAttributeType(attr.value)\"\n [attributeName]=\"attr.attributeName\">\n </mm-property-value-display>\n </span>\n </div>\n }\n </div>\n }\n\n <!-- Metadata Footer -->\n <div class=\"entity-footer\">\n <span class=\"entity-id\" title=\"Runtime ID\">{{ data()!.rtId }}</span>\n </div>\n } @else {\n <
|
|
4252
|
+
args: [{ selector: 'mm-entity-card-widget', standalone: true, imports: [CommonModule, WidgetNotConfiguredComponent, PropertyValueDisplayComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"entity-card\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Header (like UML class name) -->\n @if (config.showHeader !== false) {\n <div class=\"entity-header\">\n <span class=\"entity-stereotype\">«{{ entityTypeName() }}»</span>\n <span class=\"entity-name\">{{ displayName() }}</span>\n </div>\n }\n\n <!-- Attributes Section (like UML attributes) -->\n @if (config.showAttributes !== false && filteredAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">\n <mm-property-value-display\n [value]=\"attr.value\"\n [type]=\"inferAttributeType(attr.value)\"\n [attributeName]=\"attr.attributeName\">\n </mm-property-value-display>\n </span>\n </div>\n }\n </div>\n }\n\n <!-- Metadata Footer -->\n <div class=\"entity-footer\">\n <span class=\"entity-id\" title=\"Runtime ID\">{{ data()!.rtId }}</span>\n </div>\n } @else {\n <div class=\"entity-empty\">\n <span class=\"entity-empty-text\">{{ emptyMessage() }}</span>\n </div>\n }\n</div>\n", styles: [".entity-card{display:flex;flex-direction:column;height:100%;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden;background:var(--kendo-color-surface, #fff)}.entity-card.no-data,.entity-card.loading,.entity-card.error{justify-content:center;align-items:center}.entity-card.loading{color:var(--kendo-color-primary, #0d6efd)}.entity-card.error{color:var(--kendo-color-error, #dc3545)}.loading-indicator{font-size:.875rem;font-style:italic}.error-message{font-size:.875rem;padding:12px;text-align:center}.entity-header{display:flex;flex-direction:column;align-items:center;padding:12px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);text-align:center}.entity-header .entity-stereotype{font-size:.75rem;font-style:italic;opacity:.9;margin-bottom:2px}.entity-header .entity-name{font-size:1rem;font-weight:600}.entity-attributes{flex:1;padding:8px 0;overflow-y:auto}.entity-attributes .attribute-row{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--kendo-color-border-alt, #e9ecef);font-size:.8125rem}.entity-attributes .attribute-row:last-child{border-bottom:none}.entity-attributes .attribute-row:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d);flex-shrink:0;margin-right:12px}.entity-attributes .attribute-value{color:var(--kendo-color-on-surface, #212529);font-weight:500;text-align:right;word-break:break-word;min-width:0;flex:1}.entity-footer{padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6)}.entity-footer .entity-id{font-size:.6875rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.entity-empty{display:flex;align-items:center;justify-content:center;height:100%;padding:8px;text-align:center}.entity-empty-text{font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}\n"] }]
|
|
4171
4253
|
}], propDecorators: { config: [{
|
|
4172
4254
|
type: Input
|
|
4173
4255
|
}] } });
|
|
@@ -4215,27 +4297,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4215
4297
|
class EntityCardConfigDialogComponent {
|
|
4216
4298
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
4217
4299
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
4300
|
+
stateService = inject(MeshBoardStateService);
|
|
4218
4301
|
windowRef = inject(WindowRef);
|
|
4219
4302
|
ckTypeSelectorInput;
|
|
4220
4303
|
entitySelectorInput;
|
|
4221
4304
|
initialCkTypeId;
|
|
4222
4305
|
initialRtId;
|
|
4306
|
+
initialEntitySelectorId;
|
|
4223
4307
|
initialHideEmptyAttributes = false;
|
|
4308
|
+
bindingMode = 'fixed';
|
|
4224
4309
|
selectedCkType = null;
|
|
4225
4310
|
selectedEntity = null;
|
|
4226
4311
|
entityDataSource;
|
|
4227
4312
|
entityDialogDataSource;
|
|
4228
4313
|
isLoadingInitial = false;
|
|
4229
4314
|
hideEmptyAttributes = false;
|
|
4315
|
+
entitySelectorId = '';
|
|
4316
|
+
variableCkTypeId = '';
|
|
4317
|
+
variableRtId = '';
|
|
4318
|
+
get availableSelectors() {
|
|
4319
|
+
return this.stateService.getEntitySelectors();
|
|
4320
|
+
}
|
|
4230
4321
|
get isValid() {
|
|
4231
|
-
|
|
4322
|
+
switch (this.bindingMode) {
|
|
4323
|
+
case 'selector':
|
|
4324
|
+
return !!this.entitySelectorId;
|
|
4325
|
+
case 'variable':
|
|
4326
|
+
return this.variableRtId.trim().length > 0 && this.variableCkTypeId.trim().length > 0;
|
|
4327
|
+
default:
|
|
4328
|
+
return this.selectedCkType !== null && this.selectedEntity !== null;
|
|
4329
|
+
}
|
|
4232
4330
|
}
|
|
4233
4331
|
async ngOnInit() {
|
|
4234
4332
|
this.hideEmptyAttributes = this.initialHideEmptyAttributes;
|
|
4333
|
+
// Restore the binding mode from the persisted config.
|
|
4334
|
+
if (this.initialEntitySelectorId) {
|
|
4335
|
+
this.bindingMode = 'selector';
|
|
4336
|
+
this.entitySelectorId = this.initialEntitySelectorId;
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
if (this.containsVariable(this.initialCkTypeId) || this.containsVariable(this.initialRtId)) {
|
|
4340
|
+
this.bindingMode = 'variable';
|
|
4341
|
+
this.variableCkTypeId = this.initialCkTypeId ?? '';
|
|
4342
|
+
this.variableRtId = this.initialRtId ?? '';
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4345
|
+
this.bindingMode = 'fixed';
|
|
4235
4346
|
if (this.initialCkTypeId) {
|
|
4236
4347
|
await this.loadInitialValues();
|
|
4237
4348
|
}
|
|
4238
4349
|
}
|
|
4350
|
+
containsVariable(value) {
|
|
4351
|
+
return !!value && value.includes('$');
|
|
4352
|
+
}
|
|
4239
4353
|
async loadInitialValues() {
|
|
4240
4354
|
if (!this.initialCkTypeId) {
|
|
4241
4355
|
return;
|
|
@@ -4312,70 +4426,149 @@ class EntityCardConfigDialogComponent {
|
|
|
4312
4426
|
this.selectedEntity = null;
|
|
4313
4427
|
}
|
|
4314
4428
|
onSave() {
|
|
4315
|
-
if (this.
|
|
4316
|
-
|
|
4429
|
+
if (!this.isValid) {
|
|
4430
|
+
return;
|
|
4431
|
+
}
|
|
4432
|
+
let result;
|
|
4433
|
+
if (this.bindingMode === 'selector') {
|
|
4434
|
+
result = {
|
|
4435
|
+
ckTypeId: '',
|
|
4436
|
+
rtId: '',
|
|
4437
|
+
entitySelectorId: this.entitySelectorId,
|
|
4438
|
+
hideEmptyAttributes: this.hideEmptyAttributes
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
else if (this.bindingMode === 'variable') {
|
|
4442
|
+
result = {
|
|
4443
|
+
ckTypeId: this.variableCkTypeId.trim(),
|
|
4444
|
+
rtId: this.variableRtId.trim(),
|
|
4445
|
+
hideEmptyAttributes: this.hideEmptyAttributes
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
else {
|
|
4449
|
+
result = {
|
|
4317
4450
|
ckTypeId: this.selectedCkType.rtCkTypeId,
|
|
4318
4451
|
rtId: this.selectedEntity.rtId,
|
|
4319
4452
|
hideEmptyAttributes: this.hideEmptyAttributes
|
|
4320
|
-
}
|
|
4453
|
+
};
|
|
4321
4454
|
}
|
|
4455
|
+
this.windowRef.close(result);
|
|
4322
4456
|
}
|
|
4323
4457
|
onCancel() {
|
|
4324
4458
|
this.windowRef.close();
|
|
4325
4459
|
}
|
|
4326
4460
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: EntityCardConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4327
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: EntityCardConfigDialogComponent, isStandalone: true, selector: "mm-entity-card-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialHideEmptyAttributes: "initialHideEmptyAttributes" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }], ngImport: i0, template: `
|
|
4461
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: EntityCardConfigDialogComponent, isStandalone: true, selector: "mm-entity-card-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialEntitySelectorId: "initialEntitySelectorId", initialHideEmptyAttributes: "initialHideEmptyAttributes" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }], ngImport: i0, template: `
|
|
4328
4462
|
<div class="config-container">
|
|
4329
4463
|
|
|
4330
4464
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
4331
4465
|
<mm-loading-overlay [loading]="isLoadingInitial" />
|
|
4466
|
+
|
|
4332
4467
|
<div class="form-field">
|
|
4333
|
-
<label>
|
|
4334
|
-
<
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4468
|
+
<label>Entity source</label>
|
|
4469
|
+
<ul class="k-radio-list">
|
|
4470
|
+
<li class="k-radio-item">
|
|
4471
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="fixed"
|
|
4472
|
+
[(ngModel)]="bindingMode" />
|
|
4473
|
+
<span class="radio-label">Fixed entity</span>
|
|
4474
|
+
</li>
|
|
4475
|
+
<li class="k-radio-item">
|
|
4476
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="selector"
|
|
4477
|
+
[(ngModel)]="bindingMode" [disabled]="availableSelectors.length === 0" />
|
|
4478
|
+
<span class="radio-label">Entity selector{{ availableSelectors.length === 0 ? ' (none configured)' : '' }}</span>
|
|
4479
|
+
</li>
|
|
4480
|
+
<li class="k-radio-item">
|
|
4481
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="variable"
|
|
4482
|
+
[(ngModel)]="bindingMode" />
|
|
4483
|
+
<span class="radio-label">Variable</span>
|
|
4484
|
+
</li>
|
|
4485
|
+
</ul>
|
|
4486
|
+
<p class="field-hint">
|
|
4487
|
+
Choose a fixed entity, follow a board-level entity selector, or bind the
|
|
4488
|
+
type and rtId to MeshBoard variables (e.g. <code>$mp_rtCkTypeId</code> / <code>$mp_rtId</code>).
|
|
4489
|
+
</p>
|
|
4344
4490
|
</div>
|
|
4345
4491
|
|
|
4346
|
-
|
|
4347
|
-
<
|
|
4348
|
-
|
|
4349
|
-
<mm-
|
|
4350
|
-
#
|
|
4351
|
-
|
|
4352
|
-
[
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
} @else {
|
|
4361
|
-
<kendo-textbox
|
|
4362
|
-
[disabled]="true"
|
|
4363
|
-
placeholder="First select Runtime Entities...">
|
|
4364
|
-
</kendo-textbox>
|
|
4365
|
-
}
|
|
4366
|
-
<p class="field-hint">Select the specific entity to display in this widget.</p>
|
|
4367
|
-
</div>
|
|
4492
|
+
@if (bindingMode === 'fixed') {
|
|
4493
|
+
<div class="form-field">
|
|
4494
|
+
<label>Runtime Entities</label>
|
|
4495
|
+
<mm-ck-type-selector-input
|
|
4496
|
+
#ckTypeSelector
|
|
4497
|
+
placeholder="Select Runtime Entities..."
|
|
4498
|
+
[minSearchLength]="2"
|
|
4499
|
+
dialogTitle="Select Runtime Entities"
|
|
4500
|
+
[ngModel]="selectedCkType"
|
|
4501
|
+
(ckTypeSelected)="onCkTypeSelected($event)"
|
|
4502
|
+
(ckTypeCleared)="onCkTypeCleared()">
|
|
4503
|
+
</mm-ck-type-selector-input>
|
|
4504
|
+
<p class="field-hint">Select the type of entities to choose from.</p>
|
|
4505
|
+
</div>
|
|
4368
4506
|
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4507
|
+
<div class="form-field" [class.disabled]="!selectedCkType">
|
|
4508
|
+
<label>Entity</label>
|
|
4509
|
+
@if (selectedCkType && entityDataSource) {
|
|
4510
|
+
<mm-entity-select-input
|
|
4511
|
+
#entitySelector
|
|
4512
|
+
[dataSource]="entityDataSource"
|
|
4513
|
+
[dialogDataSource]="entityDialogDataSource"
|
|
4514
|
+
placeholder="Search for an entity..."
|
|
4515
|
+
dialogTitle="Select Entity"
|
|
4516
|
+
[minSearchLength]="1"
|
|
4517
|
+
[ngModel]="selectedEntity"
|
|
4518
|
+
(entitySelected)="onEntitySelected($event)"
|
|
4519
|
+
(entityCleared)="onEntityCleared()">
|
|
4520
|
+
</mm-entity-select-input>
|
|
4521
|
+
} @else {
|
|
4522
|
+
<kendo-textbox
|
|
4523
|
+
[disabled]="true"
|
|
4524
|
+
placeholder="First select Runtime Entities...">
|
|
4525
|
+
</kendo-textbox>
|
|
4526
|
+
}
|
|
4527
|
+
<p class="field-hint">Select the specific entity to display in this widget.</p>
|
|
4528
|
+
</div>
|
|
4529
|
+
|
|
4530
|
+
@if (selectedEntity) {
|
|
4531
|
+
<div class="selection-preview">
|
|
4532
|
+
<h4>Selected Entity</h4>
|
|
4533
|
+
<div class="preview-content">
|
|
4534
|
+
<p><strong>Type:</strong> {{ selectedCkType?.rtCkTypeId }}</p>
|
|
4535
|
+
<p><strong>RT-ID:</strong> {{ selectedEntity.rtId }}</p>
|
|
4536
|
+
@if (selectedEntity.rtWellKnownName) {
|
|
4537
|
+
<p><strong>Name:</strong> {{ selectedEntity.rtWellKnownName }}</p>
|
|
4538
|
+
}
|
|
4539
|
+
</div>
|
|
4378
4540
|
</div>
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
@if (bindingMode === 'selector') {
|
|
4545
|
+
<div class="form-field">
|
|
4546
|
+
<label>Bind to entity selector</label>
|
|
4547
|
+
<kendo-dropdownlist
|
|
4548
|
+
[data]="availableSelectors"
|
|
4549
|
+
textField="label"
|
|
4550
|
+
valueField="id"
|
|
4551
|
+
[valuePrimitive]="true"
|
|
4552
|
+
[defaultItem]="{ id: '', label: '— Select —' }"
|
|
4553
|
+
[(ngModel)]="entitySelectorId">
|
|
4554
|
+
</kendo-dropdownlist>
|
|
4555
|
+
<p class="field-hint">
|
|
4556
|
+
The card follows the asset picked in this selector at board level —
|
|
4557
|
+
its rtId and CK type are resolved from the current selection.
|
|
4558
|
+
</p>
|
|
4559
|
+
</div>
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
@if (bindingMode === 'variable') {
|
|
4563
|
+
<div class="form-field">
|
|
4564
|
+
<label>CK Type variable</label>
|
|
4565
|
+
<kendo-textbox [(ngModel)]="variableCkTypeId" placeholder="$mp_rtCkTypeId"></kendo-textbox>
|
|
4566
|
+
<p class="field-hint">CK type id, or a variable like <code>$mp_rtCkTypeId</code>.</p>
|
|
4567
|
+
</div>
|
|
4568
|
+
<div class="form-field">
|
|
4569
|
+
<label>rtId variable</label>
|
|
4570
|
+
<kendo-textbox [(ngModel)]="variableRtId" placeholder="$mp_rtId"></kendo-textbox>
|
|
4571
|
+
<p class="field-hint">Runtime id, or a variable like <code>$mp_rtId</code>.</p>
|
|
4379
4572
|
</div>
|
|
4380
4573
|
}
|
|
4381
4574
|
|
|
@@ -4402,7 +4595,7 @@ class EntityCardConfigDialogComponent {
|
|
|
4402
4595
|
</button>
|
|
4403
4596
|
</div>
|
|
4404
4597
|
</div>
|
|
4405
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.checkbox-row input{margin:0}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
4598
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.checkbox-row input{margin:0}.k-radio-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.radio-label{margin-left:8px;font-size:.9rem}.field-hint code{font-size:.78rem}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\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.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { 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: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
4406
4599
|
}
|
|
4407
4600
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: EntityCardConfigDialogComponent, decorators: [{
|
|
4408
4601
|
type: Component,
|
|
@@ -4411,6 +4604,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4411
4604
|
FormsModule,
|
|
4412
4605
|
ButtonsModule,
|
|
4413
4606
|
InputsModule,
|
|
4607
|
+
DropDownsModule,
|
|
4414
4608
|
CkTypeSelectorInputComponent,
|
|
4415
4609
|
EntitySelectInputComponent,
|
|
4416
4610
|
LoadingOverlayComponent
|
|
@@ -4419,53 +4613,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4419
4613
|
|
|
4420
4614
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
4421
4615
|
<mm-loading-overlay [loading]="isLoadingInitial" />
|
|
4616
|
+
|
|
4422
4617
|
<div class="form-field">
|
|
4423
|
-
<label>
|
|
4424
|
-
<
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4618
|
+
<label>Entity source</label>
|
|
4619
|
+
<ul class="k-radio-list">
|
|
4620
|
+
<li class="k-radio-item">
|
|
4621
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="fixed"
|
|
4622
|
+
[(ngModel)]="bindingMode" />
|
|
4623
|
+
<span class="radio-label">Fixed entity</span>
|
|
4624
|
+
</li>
|
|
4625
|
+
<li class="k-radio-item">
|
|
4626
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="selector"
|
|
4627
|
+
[(ngModel)]="bindingMode" [disabled]="availableSelectors.length === 0" />
|
|
4628
|
+
<span class="radio-label">Entity selector{{ availableSelectors.length === 0 ? ' (none configured)' : '' }}</span>
|
|
4629
|
+
</li>
|
|
4630
|
+
<li class="k-radio-item">
|
|
4631
|
+
<input type="radio" kendoRadioButton name="bindingMode" value="variable"
|
|
4632
|
+
[(ngModel)]="bindingMode" />
|
|
4633
|
+
<span class="radio-label">Variable</span>
|
|
4634
|
+
</li>
|
|
4635
|
+
</ul>
|
|
4636
|
+
<p class="field-hint">
|
|
4637
|
+
Choose a fixed entity, follow a board-level entity selector, or bind the
|
|
4638
|
+
type and rtId to MeshBoard variables (e.g. <code>$mp_rtCkTypeId</code> / <code>$mp_rtId</code>).
|
|
4639
|
+
</p>
|
|
4434
4640
|
</div>
|
|
4435
4641
|
|
|
4436
|
-
|
|
4437
|
-
<
|
|
4438
|
-
|
|
4439
|
-
<mm-
|
|
4440
|
-
#
|
|
4441
|
-
|
|
4442
|
-
[
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
} @else {
|
|
4451
|
-
<kendo-textbox
|
|
4452
|
-
[disabled]="true"
|
|
4453
|
-
placeholder="First select Runtime Entities...">
|
|
4454
|
-
</kendo-textbox>
|
|
4455
|
-
}
|
|
4456
|
-
<p class="field-hint">Select the specific entity to display in this widget.</p>
|
|
4457
|
-
</div>
|
|
4642
|
+
@if (bindingMode === 'fixed') {
|
|
4643
|
+
<div class="form-field">
|
|
4644
|
+
<label>Runtime Entities</label>
|
|
4645
|
+
<mm-ck-type-selector-input
|
|
4646
|
+
#ckTypeSelector
|
|
4647
|
+
placeholder="Select Runtime Entities..."
|
|
4648
|
+
[minSearchLength]="2"
|
|
4649
|
+
dialogTitle="Select Runtime Entities"
|
|
4650
|
+
[ngModel]="selectedCkType"
|
|
4651
|
+
(ckTypeSelected)="onCkTypeSelected($event)"
|
|
4652
|
+
(ckTypeCleared)="onCkTypeCleared()">
|
|
4653
|
+
</mm-ck-type-selector-input>
|
|
4654
|
+
<p class="field-hint">Select the type of entities to choose from.</p>
|
|
4655
|
+
</div>
|
|
4458
4656
|
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4657
|
+
<div class="form-field" [class.disabled]="!selectedCkType">
|
|
4658
|
+
<label>Entity</label>
|
|
4659
|
+
@if (selectedCkType && entityDataSource) {
|
|
4660
|
+
<mm-entity-select-input
|
|
4661
|
+
#entitySelector
|
|
4662
|
+
[dataSource]="entityDataSource"
|
|
4663
|
+
[dialogDataSource]="entityDialogDataSource"
|
|
4664
|
+
placeholder="Search for an entity..."
|
|
4665
|
+
dialogTitle="Select Entity"
|
|
4666
|
+
[minSearchLength]="1"
|
|
4667
|
+
[ngModel]="selectedEntity"
|
|
4668
|
+
(entitySelected)="onEntitySelected($event)"
|
|
4669
|
+
(entityCleared)="onEntityCleared()">
|
|
4670
|
+
</mm-entity-select-input>
|
|
4671
|
+
} @else {
|
|
4672
|
+
<kendo-textbox
|
|
4673
|
+
[disabled]="true"
|
|
4674
|
+
placeholder="First select Runtime Entities...">
|
|
4675
|
+
</kendo-textbox>
|
|
4676
|
+
}
|
|
4677
|
+
<p class="field-hint">Select the specific entity to display in this widget.</p>
|
|
4678
|
+
</div>
|
|
4679
|
+
|
|
4680
|
+
@if (selectedEntity) {
|
|
4681
|
+
<div class="selection-preview">
|
|
4682
|
+
<h4>Selected Entity</h4>
|
|
4683
|
+
<div class="preview-content">
|
|
4684
|
+
<p><strong>Type:</strong> {{ selectedCkType?.rtCkTypeId }}</p>
|
|
4685
|
+
<p><strong>RT-ID:</strong> {{ selectedEntity.rtId }}</p>
|
|
4686
|
+
@if (selectedEntity.rtWellKnownName) {
|
|
4687
|
+
<p><strong>Name:</strong> {{ selectedEntity.rtWellKnownName }}</p>
|
|
4688
|
+
}
|
|
4689
|
+
</div>
|
|
4468
4690
|
</div>
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4694
|
+
@if (bindingMode === 'selector') {
|
|
4695
|
+
<div class="form-field">
|
|
4696
|
+
<label>Bind to entity selector</label>
|
|
4697
|
+
<kendo-dropdownlist
|
|
4698
|
+
[data]="availableSelectors"
|
|
4699
|
+
textField="label"
|
|
4700
|
+
valueField="id"
|
|
4701
|
+
[valuePrimitive]="true"
|
|
4702
|
+
[defaultItem]="{ id: '', label: '— Select —' }"
|
|
4703
|
+
[(ngModel)]="entitySelectorId">
|
|
4704
|
+
</kendo-dropdownlist>
|
|
4705
|
+
<p class="field-hint">
|
|
4706
|
+
The card follows the asset picked in this selector at board level —
|
|
4707
|
+
its rtId and CK type are resolved from the current selection.
|
|
4708
|
+
</p>
|
|
4709
|
+
</div>
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
@if (bindingMode === 'variable') {
|
|
4713
|
+
<div class="form-field">
|
|
4714
|
+
<label>CK Type variable</label>
|
|
4715
|
+
<kendo-textbox [(ngModel)]="variableCkTypeId" placeholder="$mp_rtCkTypeId"></kendo-textbox>
|
|
4716
|
+
<p class="field-hint">CK type id, or a variable like <code>$mp_rtCkTypeId</code>.</p>
|
|
4717
|
+
</div>
|
|
4718
|
+
<div class="form-field">
|
|
4719
|
+
<label>rtId variable</label>
|
|
4720
|
+
<kendo-textbox [(ngModel)]="variableRtId" placeholder="$mp_rtId"></kendo-textbox>
|
|
4721
|
+
<p class="field-hint">Runtime id, or a variable like <code>$mp_rtId</code>.</p>
|
|
4469
4722
|
</div>
|
|
4470
4723
|
}
|
|
4471
4724
|
|
|
@@ -4492,7 +4745,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4492
4745
|
</button>
|
|
4493
4746
|
</div>
|
|
4494
4747
|
</div>
|
|
4495
|
-
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.checkbox-row input{margin:0}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"] }]
|
|
4748
|
+
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.checkbox-row input{margin:0}.k-radio-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.radio-label{margin-left:8px;font-size:.9rem}.field-hint code{font-size:.78rem}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"] }]
|
|
4496
4749
|
}], propDecorators: { ckTypeSelectorInput: [{
|
|
4497
4750
|
type: ViewChild,
|
|
4498
4751
|
args: ['ckTypeSelector']
|
|
@@ -4503,6 +4756,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4503
4756
|
type: Input
|
|
4504
4757
|
}], initialRtId: [{
|
|
4505
4758
|
type: Input
|
|
4759
|
+
}], initialEntitySelectorId: [{
|
|
4760
|
+
type: Input
|
|
4506
4761
|
}], initialHideEmptyAttributes: [{
|
|
4507
4762
|
type: Input
|
|
4508
4763
|
}] } });
|
|
@@ -4514,7 +4769,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
4514
4769
|
/**
|
|
4515
4770
|
* Supported row types for query processing.
|
|
4516
4771
|
*/
|
|
4517
|
-
const SUPPORTED_ROW_TYPES = [
|
|
4772
|
+
const SUPPORTED_ROW_TYPES$1 = [
|
|
4518
4773
|
'RtSimpleQueryRow',
|
|
4519
4774
|
'RtAggregationQueryRow',
|
|
4520
4775
|
'RtGroupingAggregationQueryRow'
|
|
@@ -4615,7 +4870,7 @@ function parseNumericValue(value) {
|
|
|
4615
4870
|
function extractAggregationValue(queryResult, valueField) {
|
|
4616
4871
|
const rows = queryResult.rows?.items ?? [];
|
|
4617
4872
|
// Get the first supported row
|
|
4618
|
-
const firstRow = rows.find(row => row && SUPPORTED_ROW_TYPES.includes(row.__typename ?? ''));
|
|
4873
|
+
const firstRow = rows.find(row => row && SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''));
|
|
4619
4874
|
if (!firstRow)
|
|
4620
4875
|
return 0;
|
|
4621
4876
|
const cells = firstRow.cells?.items ?? [];
|
|
@@ -4650,7 +4905,7 @@ function extractGroupedAggregationValue(queryResult, categoryField, categoryValu
|
|
|
4650
4905
|
const rows = queryResult.rows?.items ?? [];
|
|
4651
4906
|
// Find the row where category matches
|
|
4652
4907
|
for (const row of rows) {
|
|
4653
|
-
if (!row || !SUPPORTED_ROW_TYPES.includes(row.__typename ?? ''))
|
|
4908
|
+
if (!row || !SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4654
4909
|
continue;
|
|
4655
4910
|
const cells = row.cells?.items ?? [];
|
|
4656
4911
|
let categoryMatch = false;
|
|
@@ -4687,7 +4942,7 @@ function processStaticSeriesData(rows, categoryField, seriesConfigs) {
|
|
|
4687
4942
|
seriesMap.set(seriesConfig.field, []);
|
|
4688
4943
|
}
|
|
4689
4944
|
for (const row of rows) {
|
|
4690
|
-
if (!SUPPORTED_ROW_TYPES.includes(row.__typename ?? ''))
|
|
4945
|
+
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4691
4946
|
continue;
|
|
4692
4947
|
const cells = row.cells?.items ?? [];
|
|
4693
4948
|
let categoryValue = '';
|
|
@@ -4737,7 +4992,7 @@ function processDynamicSeriesData(rows, categoryField, seriesGroupField, valueFi
|
|
|
4737
4992
|
const allCategories = new Set();
|
|
4738
4993
|
const allSeriesGroups = new Set();
|
|
4739
4994
|
for (const row of rows) {
|
|
4740
|
-
if (!SUPPORTED_ROW_TYPES.includes(row.__typename ?? ''))
|
|
4995
|
+
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4741
4996
|
continue;
|
|
4742
4997
|
const cells = row.cells?.items ?? [];
|
|
4743
4998
|
let categoryValue = '';
|
|
@@ -4790,7 +5045,7 @@ function processDynamicSeriesData(rows, categoryField, seriesGroupField, valueFi
|
|
|
4790
5045
|
function processPieChartData(rows, categoryField, valueField) {
|
|
4791
5046
|
const result = [];
|
|
4792
5047
|
for (const row of rows) {
|
|
4793
|
-
if (!SUPPORTED_ROW_TYPES.includes(row.__typename ?? ''))
|
|
5048
|
+
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4794
5049
|
continue;
|
|
4795
5050
|
const cells = row.cells?.items ?? [];
|
|
4796
5051
|
let categoryValue = '';
|
|
@@ -7006,8 +7261,148 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
7006
7261
|
type: Output
|
|
7007
7262
|
}] } });
|
|
7008
7263
|
|
|
7264
|
+
/**
|
|
7265
|
+
* Centralized, timezone-aware datetime formatting for MeshBoard widgets.
|
|
7266
|
+
*
|
|
7267
|
+
* Every widget that renders a timestamp routes through here so the board's
|
|
7268
|
+
* {@link MeshBoardTimeZoneMode} governs display consistently — matching the
|
|
7269
|
+
* basis the time-filter boundaries are computed on. In `'local'` mode values
|
|
7270
|
+
* render in the browser's timezone; in `'utc'` mode they render in UTC.
|
|
7271
|
+
*/
|
|
7272
|
+
/** Locale used for all MeshBoard datetime rendering (matches the app's LOCALE_ID family). */
|
|
7273
|
+
const MESHBOARD_LOCALE = 'de-AT';
|
|
7274
|
+
/**
|
|
7275
|
+
* Strict ISO-8601 *date-time* matcher (date + time, optional fractional seconds
|
|
7276
|
+
* and `Z`/offset). Plain dates (`2026-01-01`) are intentionally excluded so they
|
|
7277
|
+
* are not reinterpreted across a timezone boundary.
|
|
7278
|
+
*/
|
|
7279
|
+
const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
|
|
7280
|
+
/** True when the value is a string in strict ISO-8601 date-time form. */
|
|
7281
|
+
function isIsoDateTime(value) {
|
|
7282
|
+
return typeof value === 'string' && ISO_DATE_TIME_RE.test(value);
|
|
7283
|
+
}
|
|
7284
|
+
/** Parses a value into a valid `Date`, or returns `null` when it is not a usable instant. */
|
|
7285
|
+
function toInstant(value) {
|
|
7286
|
+
if (value instanceof Date) {
|
|
7287
|
+
return isNaN(value.getTime()) ? null : value;
|
|
7288
|
+
}
|
|
7289
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
7290
|
+
const date = new Date(value);
|
|
7291
|
+
return isNaN(date.getTime()) ? null : date;
|
|
7292
|
+
}
|
|
7293
|
+
return null;
|
|
7294
|
+
}
|
|
7295
|
+
/** Maps the board mode to the `Intl` `timeZone` option (`undefined` = browser local). */
|
|
7296
|
+
function timeZoneOption(mode) {
|
|
7297
|
+
return mode === 'utc' ? 'UTC' : undefined;
|
|
7298
|
+
}
|
|
7299
|
+
/**
|
|
7300
|
+
* Cached `Intl.DateTimeFormat` per board mode. Bucketing widgets call
|
|
7301
|
+
* {@link getZonedDateParts} once per data row, so re-creating the (relatively
|
|
7302
|
+
* expensive) formatter each time would be wasteful.
|
|
7303
|
+
*/
|
|
7304
|
+
const partsFormatterCache = new Map();
|
|
7305
|
+
function partsFormatter(mode) {
|
|
7306
|
+
let formatter = partsFormatterCache.get(mode);
|
|
7307
|
+
if (!formatter) {
|
|
7308
|
+
formatter = new Intl.DateTimeFormat('en-US', {
|
|
7309
|
+
year: 'numeric',
|
|
7310
|
+
month: '2-digit',
|
|
7311
|
+
day: '2-digit',
|
|
7312
|
+
hour: '2-digit',
|
|
7313
|
+
minute: '2-digit',
|
|
7314
|
+
hourCycle: 'h23',
|
|
7315
|
+
timeZone: timeZoneOption(mode)
|
|
7316
|
+
});
|
|
7317
|
+
partsFormatterCache.set(mode, formatter);
|
|
7318
|
+
}
|
|
7319
|
+
return formatter;
|
|
7320
|
+
}
|
|
7321
|
+
/**
|
|
7322
|
+
* Decomposes an instant into its calendar/clock components **in the board's
|
|
7323
|
+
* timezone**. In `'local'` mode the parts are the browser-local wall-clock
|
|
7324
|
+
* values; in `'utc'` mode they are the UTC values.
|
|
7325
|
+
*
|
|
7326
|
+
* This is the bucketing counterpart to {@link formatInstant}: widgets that group
|
|
7327
|
+
* data by hour-of-day / day (e.g. the heatmap) must derive those buckets on the
|
|
7328
|
+
* same timezone basis the time-filter boundaries use, otherwise a UTC+offset
|
|
7329
|
+
* tenant sees rows shifted into the wrong hour/day. Returns `null` for
|
|
7330
|
+
* non-instants so callers can skip the row.
|
|
7331
|
+
*/
|
|
7332
|
+
function getZonedDateParts(value, mode) {
|
|
7333
|
+
const date = toInstant(value);
|
|
7334
|
+
if (!date) {
|
|
7335
|
+
return null;
|
|
7336
|
+
}
|
|
7337
|
+
const lookup = new Map();
|
|
7338
|
+
for (const part of partsFormatter(mode).formatToParts(date)) {
|
|
7339
|
+
lookup.set(part.type, part.value);
|
|
7340
|
+
}
|
|
7341
|
+
const year = Number(lookup.get('year'));
|
|
7342
|
+
const month = Number(lookup.get('month'));
|
|
7343
|
+
const day = Number(lookup.get('day'));
|
|
7344
|
+
// `h23` yields 0–23, but some engines emit '24' for midnight — normalize it.
|
|
7345
|
+
const hour = Number(lookup.get('hour')) % 24;
|
|
7346
|
+
const minute = Number(lookup.get('minute'));
|
|
7347
|
+
if ([year, month, day, hour, minute].some(Number.isNaN)) {
|
|
7348
|
+
return null;
|
|
7349
|
+
}
|
|
7350
|
+
return { year, month, day, hour, minute };
|
|
7351
|
+
}
|
|
7352
|
+
/** Zero-padded `yyyy-MM-dd` key for {@link ZonedDateParts} (stable for sorting). */
|
|
7353
|
+
function zonedDateKey(parts) {
|
|
7354
|
+
const month = parts.month.toString().padStart(2, '0');
|
|
7355
|
+
const day = parts.day.toString().padStart(2, '0');
|
|
7356
|
+
return `${parts.year}-${month}-${day}`;
|
|
7357
|
+
}
|
|
7358
|
+
/**
|
|
7359
|
+
* Formats an instant honoring the board's timezone mode, with caller-supplied
|
|
7360
|
+
* `Intl.DateTimeFormatOptions`. The `timeZone` option is injected from `mode`
|
|
7361
|
+
* and overrides any passed in. Returns `null` when the value is not a valid
|
|
7362
|
+
* instant, so callers can fall back to their own rendering.
|
|
7363
|
+
*/
|
|
7364
|
+
function formatInstant(value, mode, options) {
|
|
7365
|
+
const date = toInstant(value);
|
|
7366
|
+
if (!date) {
|
|
7367
|
+
return null;
|
|
7368
|
+
}
|
|
7369
|
+
return date.toLocaleString(MESHBOARD_LOCALE, { ...options, timeZone: timeZoneOption(mode) });
|
|
7370
|
+
}
|
|
7371
|
+
/** Full date with `dd.MM.yyyy` in the board's timezone, or `null`. */
|
|
7372
|
+
function formatBoardDate(value, mode) {
|
|
7373
|
+
return formatInstant(value, mode, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
7374
|
+
}
|
|
7375
|
+
/**
|
|
7376
|
+
* Full date + time with seconds (`dd.MM.yyyy HH:mm:ss`) in the board's timezone.
|
|
7377
|
+
* This is the table-widget cell format. Returns `null` for non-instants.
|
|
7378
|
+
*
|
|
7379
|
+
* Date and time parts are formatted separately and joined with a single space so
|
|
7380
|
+
* the output is exactly `dd.MM.yyyy HH:mm:ss` — the locale's combined format
|
|
7381
|
+
* would otherwise insert a comma (`dd.MM.yyyy, HH:mm:ss`).
|
|
7382
|
+
*/
|
|
7383
|
+
function formatBoardDateTime(value, mode) {
|
|
7384
|
+
const datePart = formatInstant(value, mode, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
7385
|
+
const timePart = formatInstant(value, mode, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
7386
|
+
if (datePart === null || timePart === null) {
|
|
7387
|
+
return null;
|
|
7388
|
+
}
|
|
7389
|
+
return `${datePart} ${timePart}`;
|
|
7390
|
+
}
|
|
7391
|
+
/**
|
|
7392
|
+
* Table-cell formatter: ISO-8601 date-time strings are rendered in the board's
|
|
7393
|
+
* timezone (`dd.MM.yyyy HH:mm:ss`); every other value passes through unchanged.
|
|
7394
|
+
* Safe to attach to all columns — non-datetime cells are returned via `String`.
|
|
7395
|
+
*/
|
|
7396
|
+
function formatTableCellValue(value, mode) {
|
|
7397
|
+
if (isIsoDateTime(value)) {
|
|
7398
|
+
return formatBoardDateTime(value, mode) ?? String(value);
|
|
7399
|
+
}
|
|
7400
|
+
return value === null || value === undefined ? '' : String(value);
|
|
7401
|
+
}
|
|
7402
|
+
|
|
7009
7403
|
class EntityAssociationsWidgetComponent {
|
|
7010
7404
|
dataService = inject(DashboardDataService);
|
|
7405
|
+
stateService = inject(MeshBoardStateService);
|
|
7011
7406
|
config;
|
|
7012
7407
|
arrowRightIcon = arrowRightIcon;
|
|
7013
7408
|
arrowLeftIcon = arrowLeftIcon;
|
|
@@ -7218,14 +7613,13 @@ class EntityAssociationsWidgetComponent {
|
|
|
7218
7613
|
maximumFractionDigits: 2
|
|
7219
7614
|
});
|
|
7220
7615
|
}
|
|
7616
|
+
const mode = this.stateService.timeZoneMode();
|
|
7221
7617
|
if (value instanceof Date)
|
|
7222
|
-
return value
|
|
7618
|
+
return formatBoardDate(value, mode) ?? String(value);
|
|
7223
7619
|
if (typeof value === 'string') {
|
|
7224
|
-
//
|
|
7225
|
-
if (
|
|
7226
|
-
|
|
7227
|
-
if (!isNaN(date.getTime()))
|
|
7228
|
-
return date.toLocaleDateString('de-AT');
|
|
7620
|
+
// Format ISO date-time strings on the board's timezone basis
|
|
7621
|
+
if (isIsoDateTime(value)) {
|
|
7622
|
+
return formatBoardDate(value, mode) ?? value;
|
|
7229
7623
|
}
|
|
7230
7624
|
return value;
|
|
7231
7625
|
}
|
|
@@ -8514,6 +8908,7 @@ function resolveStatusMapping(config) {
|
|
|
8514
8908
|
class TableWidgetComponent {
|
|
8515
8909
|
config;
|
|
8516
8910
|
dataSource;
|
|
8911
|
+
stateService = inject(MeshBoardStateService);
|
|
8517
8912
|
// Widget state signals
|
|
8518
8913
|
_isLoading = signal(false, /* @ts-ignore */
|
|
8519
8914
|
...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -8545,9 +8940,25 @@ class TableWidgetComponent {
|
|
|
8545
8940
|
* For persistent queries, uses dynamically derived columns from the query response.
|
|
8546
8941
|
*/
|
|
8547
8942
|
listViewColumns = computed(() => {
|
|
8943
|
+
// Read the board's timezone mode so datetime cells (e.g. archive
|
|
8944
|
+
// window_start/window_end, which arrive as raw ISO-8601 strings) render on
|
|
8945
|
+
// the same basis the time filter is computed on. Reading it here makes the
|
|
8946
|
+
// columns recompute when the mode changes.
|
|
8947
|
+
const mode = this.stateService.timeZoneMode();
|
|
8948
|
+
// Only plain-text columns get the datetime formatter — columns the user
|
|
8949
|
+
// explicitly typed ('date', 'numeric', 'iso8601', …) keep their own
|
|
8950
|
+
// rendering, and any value that is not an ISO-8601 date-time passes through
|
|
8951
|
+
// unchanged. Derived stream-data columns are all 'text', so they're covered.
|
|
8952
|
+
const withDateFormatter = (column) => {
|
|
8953
|
+
const isPlainText = !column.dataType || column.dataType === 'text';
|
|
8954
|
+
if (!isPlainText || column.formatter) {
|
|
8955
|
+
return column;
|
|
8956
|
+
}
|
|
8957
|
+
return { ...column, formatter: (value) => formatTableCellValue(value, mode) };
|
|
8958
|
+
};
|
|
8548
8959
|
// If explicit columns are configured, use them (works for both data source types)
|
|
8549
8960
|
if (this.config?.columns && this.config.columns.length > 0) {
|
|
8550
|
-
return this.config.columns.map(col => ({
|
|
8961
|
+
return this.config.columns.map(col => withDateFormatter({
|
|
8551
8962
|
field: col.field,
|
|
8552
8963
|
displayName: col.title,
|
|
8553
8964
|
dataType: (col.dataType ?? 'text'),
|
|
@@ -8559,7 +8970,7 @@ class TableWidgetComponent {
|
|
|
8559
8970
|
if (this.config?.dataSource?.type === 'persistentQuery') {
|
|
8560
8971
|
const queryColumns = this._queryColumnsForView();
|
|
8561
8972
|
if (queryColumns.length > 0) {
|
|
8562
|
-
return queryColumns;
|
|
8973
|
+
return queryColumns.map(withDateFormatter);
|
|
8563
8974
|
}
|
|
8564
8975
|
return [];
|
|
8565
8976
|
}
|
|
@@ -12781,18 +13192,18 @@ class BarChartWidgetComponent {
|
|
|
12781
13192
|
return defaultColor;
|
|
12782
13193
|
}
|
|
12783
13194
|
formatCategoryValue(value) {
|
|
12784
|
-
|
|
12785
|
-
//
|
|
12786
|
-
if (
|
|
12787
|
-
const
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
13195
|
+
// ISO-8601 date-time values are formatted on the board's timezone basis;
|
|
13196
|
+
// everything else passes through as a string.
|
|
13197
|
+
if (isIsoDateTime(value)) {
|
|
13198
|
+
const formatted = formatInstant(value, this.stateService.timeZoneMode(), {
|
|
13199
|
+
day: '2-digit', month: '2-digit',
|
|
13200
|
+
hour: '2-digit', minute: '2-digit'
|
|
13201
|
+
});
|
|
13202
|
+
if (formatted !== null) {
|
|
13203
|
+
return formatted;
|
|
12793
13204
|
}
|
|
12794
13205
|
}
|
|
12795
|
-
return
|
|
13206
|
+
return String(value ?? '');
|
|
12796
13207
|
}
|
|
12797
13208
|
/**
|
|
12798
13209
|
* Converts widget filter configuration to GraphQL FieldFilterDto format.
|
|
@@ -14147,8 +14558,11 @@ class LineChartWidgetComponent {
|
|
|
14147
14558
|
// Sort categories chronologically
|
|
14148
14559
|
const sortedCategoryEntries = Array.from(allCategories.entries())
|
|
14149
14560
|
.sort((a, b) => a[1].getTime() - b[1].getTime());
|
|
14150
|
-
// Detect if we need time precision (multiple data points per day)
|
|
14151
|
-
|
|
14561
|
+
// Detect if we need time precision (multiple data points per day). The
|
|
14562
|
+
// day-key is computed on the board's timezone basis so the date axis stays
|
|
14563
|
+
// consistent with the time filter and the rest of the board.
|
|
14564
|
+
const mode = this.stateService.timeZoneMode();
|
|
14565
|
+
const dateOnlySet = new Set(sortedCategoryEntries.map(([, date]) => formatInstant(date, mode, { day: '2-digit', month: '2-digit', year: 'numeric' }) ?? '?'));
|
|
14152
14566
|
const needsTime = dateOnlySet.size < sortedCategoryEntries.length;
|
|
14153
14567
|
const categories = sortedCategoryEntries.map(([, date]) => needsTime ? this.formatDateTime(date) : this.formatDate(date));
|
|
14154
14568
|
const categoryKeys = sortedCategoryEntries.map(([key]) => key);
|
|
@@ -14194,27 +14608,24 @@ class LineChartWidgetComponent {
|
|
|
14194
14608
|
* Formats a date for display on the category axis (date only).
|
|
14195
14609
|
*/
|
|
14196
14610
|
formatDate(date) {
|
|
14197
|
-
|
|
14198
|
-
return '?';
|
|
14199
|
-
return date.toLocaleDateString('de-AT', {
|
|
14611
|
+
return formatInstant(date, this.stateService.timeZoneMode(), {
|
|
14200
14612
|
day: '2-digit',
|
|
14201
14613
|
month: '2-digit',
|
|
14202
14614
|
year: 'numeric'
|
|
14203
|
-
});
|
|
14615
|
+
}) ?? '?';
|
|
14204
14616
|
}
|
|
14205
14617
|
/**
|
|
14206
14618
|
* Formats a date with time for display on the category axis.
|
|
14619
|
+
* Date and time parts are formatted separately (joined with a space) to keep
|
|
14620
|
+
* the compact axis label; both honor the board's timezone mode.
|
|
14207
14621
|
*/
|
|
14208
14622
|
formatDateTime(date) {
|
|
14209
|
-
|
|
14623
|
+
const mode = this.stateService.timeZoneMode();
|
|
14624
|
+
const datePart = formatInstant(date, mode, { day: '2-digit', month: '2-digit' });
|
|
14625
|
+
const timePart = formatInstant(date, mode, { hour: '2-digit', minute: '2-digit' });
|
|
14626
|
+
if (datePart === null || timePart === null)
|
|
14210
14627
|
return '?';
|
|
14211
|
-
return
|
|
14212
|
-
day: '2-digit',
|
|
14213
|
-
month: '2-digit'
|
|
14214
|
-
}) + ' ' + date.toLocaleTimeString('de-AT', {
|
|
14215
|
-
hour: '2-digit',
|
|
14216
|
-
minute: '2-digit'
|
|
14217
|
-
});
|
|
14628
|
+
return `${datePart} ${timePart}`;
|
|
14218
14629
|
}
|
|
14219
14630
|
sanitizeAxisName(name) {
|
|
14220
14631
|
return name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
@@ -14303,6 +14714,7 @@ class LineChartWidgetComponent {
|
|
|
14303
14714
|
[axis]="series.axisName ?? ''"
|
|
14304
14715
|
[color]="series.color"
|
|
14305
14716
|
[opacity]="0.18"
|
|
14717
|
+
[visibleInLegend]="false"
|
|
14306
14718
|
[markers]="{ visible: false }">
|
|
14307
14719
|
</kendo-chart-series-item>
|
|
14308
14720
|
}
|
|
@@ -14335,7 +14747,7 @@ class LineChartWidgetComponent {
|
|
|
14335
14747
|
</kendo-chart>
|
|
14336
14748
|
}
|
|
14337
14749
|
</div>
|
|
14338
|
-
`, isInline: true, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.data-count{position:absolute;top:
|
|
14750
|
+
`, isInline: true, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.data-count{position:absolute;top:4px;right:6px;z-index:2;max-width:calc(100% - 12px);padding:1px 7px;border-radius:9px;font-size:.7rem;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--kendo-color-on-app-surface, #6c757d);background:color-mix(in srgb,var(--kendo-color-app-surface, #888) 12%,transparent);pointer-events:none;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.line-chart-widget.loading,.line-chart-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$7.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$7.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$7.CategoryAxisComponent, selector: "kendo-chart-category-axis" }, { kind: "component", type: i1$7.CategoryAxisItemComponent, selector: "kendo-chart-category-axis-item", inputs: ["autoBaseUnitSteps", "axisCrossingValue", "background", "baseUnit", "baseUnitStep", "categories", "color", "justified", "line", "majorGridLines", "majorTicks", "max", "maxDateGroups", "maxDivisions", "min", "minorGridLines", "minorTicks", "name", "pane", "plotBands", "reverse", "roundToBaseUnit", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "select", "title", "rangeLabels", "highlight"] }, { kind: "component", type: i1$7.CategoryAxisLabelsComponent, selector: "kendo-chart-category-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$7.ChartAreaComponent, selector: "kendo-chart-area", inputs: ["background", "border", "height", "margin", "opacity", "width"] }, { kind: "component", type: i1$7.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$7.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$7.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "lineStyle", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$7.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: i1$7.ValueAxisComponent, selector: "kendo-chart-value-axis" }, { kind: "component", type: i1$7.ValueAxisItemComponent, selector: "kendo-chart-value-axis-item", inputs: ["axisCrossingValue", "background", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
14339
14751
|
}
|
|
14340
14752
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: LineChartWidgetComponent, decorators: [{
|
|
14341
14753
|
type: Component,
|
|
@@ -14415,6 +14827,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
14415
14827
|
[axis]="series.axisName ?? ''"
|
|
14416
14828
|
[color]="series.color"
|
|
14417
14829
|
[opacity]="0.18"
|
|
14830
|
+
[visibleInLegend]="false"
|
|
14418
14831
|
[markers]="{ visible: false }">
|
|
14419
14832
|
</kendo-chart-series-item>
|
|
14420
14833
|
}
|
|
@@ -14447,7 +14860,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
14447
14860
|
</kendo-chart>
|
|
14448
14861
|
}
|
|
14449
14862
|
</div>
|
|
14450
|
-
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.data-count{position:absolute;top:
|
|
14863
|
+
`, changeDetection: ChangeDetectionStrategy.Eager, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.data-count{position:absolute;top:4px;right:6px;z-index:2;max-width:calc(100% - 12px);padding:1px 7px;border-radius:9px;font-size:.7rem;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--kendo-color-on-app-surface, #6c757d);background:color-mix(in srgb,var(--kendo-color-app-surface, #888) 12%,transparent);pointer-events:none;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.line-chart-widget.loading,.line-chart-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"] }]
|
|
14451
14864
|
}], propDecorators: { config: [{
|
|
14452
14865
|
type: Input
|
|
14453
14866
|
}] } });
|
|
@@ -15380,11 +15793,15 @@ class HeatmapWidgetComponent {
|
|
|
15380
15793
|
// Detect interval width in minutes from the first row that has both from and to
|
|
15381
15794
|
const intervalMinutes = this.detectIntervalMinutes(parsedRows);
|
|
15382
15795
|
const hasSubHourIntervals = dateEndField && intervalMinutes > 0 && intervalMinutes < 60;
|
|
15796
|
+
// Bucket on the board's timezone basis so hour-of-day / day axes match the
|
|
15797
|
+
// time-filter boundaries (default 'local'); using UTC here shifts a UTC+N
|
|
15798
|
+
// tenant's rows into the wrong hour/day.
|
|
15799
|
+
const mode = this.stateService.timeZoneMode();
|
|
15383
15800
|
if (hasSubHourIntervals) {
|
|
15384
|
-
this.processSubHourData(parsedRows, intervalMinutes, aggregation);
|
|
15801
|
+
this.processSubHourData(parsedRows, intervalMinutes, aggregation, mode);
|
|
15385
15802
|
}
|
|
15386
15803
|
else {
|
|
15387
|
-
this.processHourlyData(parsedRows, aggregation);
|
|
15804
|
+
this.processHourlyData(parsedRows, aggregation, mode);
|
|
15388
15805
|
}
|
|
15389
15806
|
}
|
|
15390
15807
|
/**
|
|
@@ -15404,11 +15821,14 @@ class HeatmapWidgetComponent {
|
|
|
15404
15821
|
/**
|
|
15405
15822
|
* Processes data into hourly buckets (original behavior).
|
|
15406
15823
|
*/
|
|
15407
|
-
processHourlyData(rows, aggregation) {
|
|
15824
|
+
processHourlyData(rows, aggregation, mode) {
|
|
15408
15825
|
const buckets = new Map();
|
|
15409
15826
|
for (const { dateFrom, numericValue } of rows) {
|
|
15410
|
-
const
|
|
15411
|
-
|
|
15827
|
+
const parts = getZonedDateParts(dateFrom, mode);
|
|
15828
|
+
if (!parts)
|
|
15829
|
+
continue;
|
|
15830
|
+
const dateKey = zonedDateKey(parts);
|
|
15831
|
+
const hour = parts.hour;
|
|
15412
15832
|
if (!buckets.has(dateKey))
|
|
15413
15833
|
buckets.set(dateKey, new Map());
|
|
15414
15834
|
const hourMap = buckets.get(dateKey);
|
|
@@ -15438,16 +15858,19 @@ class HeatmapWidgetComponent {
|
|
|
15438
15858
|
* Y-axis: hours (00:00..23:00), X-axis: interval labels (e.g. "00-15", "15-30", ...).
|
|
15439
15859
|
* For multi-day data, X-axis labels are prefixed with the date.
|
|
15440
15860
|
*/
|
|
15441
|
-
processSubHourData(rows, intervalMinutes, aggregation) {
|
|
15861
|
+
processSubHourData(rows, intervalMinutes, aggregation, mode) {
|
|
15442
15862
|
const intervalsPerHour = Math.floor(60 / intervalMinutes);
|
|
15443
15863
|
// Bucket: xKey (date+interval) -> hour -> values[]
|
|
15444
15864
|
const buckets = new Map();
|
|
15445
15865
|
const allDates = new Set();
|
|
15446
15866
|
for (const { dateFrom, numericValue } of rows) {
|
|
15447
|
-
const
|
|
15867
|
+
const parts = getZonedDateParts(dateFrom, mode);
|
|
15868
|
+
if (!parts)
|
|
15869
|
+
continue;
|
|
15870
|
+
const dateKey = zonedDateKey(parts);
|
|
15448
15871
|
allDates.add(dateKey);
|
|
15449
|
-
const hour =
|
|
15450
|
-
const minute =
|
|
15872
|
+
const hour = parts.hour;
|
|
15873
|
+
const minute = parts.minute;
|
|
15451
15874
|
const intervalIndex = Math.floor(minute / intervalMinutes);
|
|
15452
15875
|
const intervalStart = intervalIndex * intervalMinutes;
|
|
15453
15876
|
const intervalEnd = intervalStart + intervalMinutes;
|
|
@@ -15540,12 +15963,6 @@ class HeatmapWidgetComponent {
|
|
|
15540
15963
|
const date = new Date(String(value));
|
|
15541
15964
|
return isNaN(date.getTime()) ? null : date;
|
|
15542
15965
|
}
|
|
15543
|
-
formatDateKey(date) {
|
|
15544
|
-
const year = date.getUTCFullYear();
|
|
15545
|
-
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
15546
|
-
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
15547
|
-
return `${year}-${month}-${day}`;
|
|
15548
|
-
}
|
|
15549
15966
|
convertFiltersToDto(filters) {
|
|
15550
15967
|
const variables = this.stateService.getVariables();
|
|
15551
15968
|
return this.variableService.convertToFieldFilterDto(filters, variables);
|
|
@@ -16413,8 +16830,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
16413
16830
|
type: Input
|
|
16414
16831
|
}] } });
|
|
16415
16832
|
|
|
16833
|
+
/**
|
|
16834
|
+
* Row `__typename`s the value extraction recognises. Runtime queries
|
|
16835
|
+
* discriminate by kind; stream-data queries collapse every kind
|
|
16836
|
+
* (simple / aggregation / grouped / downsampling) into `StreamDataQueryRow`.
|
|
16837
|
+
*/
|
|
16838
|
+
const SUPPORTED_ROW_TYPES = new Set([
|
|
16839
|
+
'RtAggregationQueryRow',
|
|
16840
|
+
'RtGroupingAggregationQueryRow',
|
|
16841
|
+
'StreamDataQueryRow'
|
|
16842
|
+
]);
|
|
16843
|
+
function extractAggregation(result, valueField) {
|
|
16844
|
+
const firstRow = result.rows.find(row => SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
16845
|
+
if (!firstRow)
|
|
16846
|
+
return 0;
|
|
16847
|
+
if (valueField) {
|
|
16848
|
+
for (const cell of firstRow.cells) {
|
|
16849
|
+
if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
16850
|
+
return parseNumericValue(cell.value);
|
|
16851
|
+
}
|
|
16852
|
+
}
|
|
16853
|
+
}
|
|
16854
|
+
// Fallback: first cell value when no specific field is configured.
|
|
16855
|
+
return firstRow.cells.length > 0 ? parseNumericValue(firstRow.cells[0].value) : 0;
|
|
16856
|
+
}
|
|
16857
|
+
function extractGroupedAggregation(result, categoryField, categoryValue, valueField) {
|
|
16858
|
+
if (!categoryField || !categoryValue || !valueField)
|
|
16859
|
+
return 0;
|
|
16860
|
+
for (const row of result.rows) {
|
|
16861
|
+
if (!SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
16862
|
+
continue;
|
|
16863
|
+
let categoryMatch = false;
|
|
16864
|
+
let value = 0;
|
|
16865
|
+
for (const cell of row.cells) {
|
|
16866
|
+
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
16867
|
+
categoryMatch = true;
|
|
16868
|
+
}
|
|
16869
|
+
if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
16870
|
+
value = parseNumericValue(cell.value);
|
|
16871
|
+
}
|
|
16872
|
+
}
|
|
16873
|
+
if (categoryMatch)
|
|
16874
|
+
return value;
|
|
16875
|
+
}
|
|
16876
|
+
return 0;
|
|
16877
|
+
}
|
|
16878
|
+
/**
|
|
16879
|
+
* Reduces a unified query result to the single numeric value a cell displays,
|
|
16880
|
+
* according to the source's `queryMode`. Mirrors the KPI widget extraction.
|
|
16881
|
+
*/
|
|
16882
|
+
function extractPersistentQueryCellValue(result, source) {
|
|
16883
|
+
switch (source.queryMode ?? 'simpleCount') {
|
|
16884
|
+
case 'aggregation':
|
|
16885
|
+
return extractAggregation(result, source.queryValueField);
|
|
16886
|
+
case 'groupedAggregation':
|
|
16887
|
+
return extractGroupedAggregation(result, source.queryCategoryField, source.queryCategoryValue, source.queryValueField);
|
|
16888
|
+
case 'simpleCount':
|
|
16889
|
+
default:
|
|
16890
|
+
return result.totalCount;
|
|
16891
|
+
}
|
|
16892
|
+
}
|
|
16893
|
+
|
|
16416
16894
|
class StatsGridWidgetComponent {
|
|
16417
16895
|
dataService = inject(MeshBoardDataService);
|
|
16896
|
+
queryExecutor = inject(QueryExecutorService);
|
|
16897
|
+
stateService = inject(MeshBoardStateService);
|
|
16898
|
+
variableService = inject(MeshBoardVariableService);
|
|
16418
16899
|
config;
|
|
16419
16900
|
_isLoading = signal(false, /* @ts-ignore */
|
|
16420
16901
|
...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -16461,26 +16942,22 @@ class StatsGridWidgetComponent {
|
|
|
16461
16942
|
this._isLoading.set(true);
|
|
16462
16943
|
this._error.set(null);
|
|
16463
16944
|
try {
|
|
16945
|
+
// Runtime aggregation queries are only needed for stats WITHOUT a
|
|
16946
|
+
// per-stat persistent-query source — those run through the executor below.
|
|
16464
16947
|
const dataSource = this.config.dataSource;
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
this.
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
color: this.getColorClass(stat.color),
|
|
16479
|
-
prefix: stat.prefix,
|
|
16480
|
-
suffix: stat.suffix,
|
|
16481
|
-
isLoading: false
|
|
16482
|
-
};
|
|
16483
|
-
});
|
|
16948
|
+
const aggregationQueries = dataSource?.type === 'aggregation' ? (dataSource.queries ?? []) : [];
|
|
16949
|
+
const aggregationResults = aggregationQueries.length > 0
|
|
16950
|
+
? await this.dataService.fetchAggregations(aggregationQueries)
|
|
16951
|
+
: new Map();
|
|
16952
|
+
// Resolve every stat's value (persistent-query stats execute in parallel).
|
|
16953
|
+
const updatedValues = await Promise.all(this.config.stats.map(async (stat) => ({
|
|
16954
|
+
label: stat.label,
|
|
16955
|
+
value: await this.resolveStatValue(stat, aggregationResults),
|
|
16956
|
+
color: this.getColorClass(stat.color),
|
|
16957
|
+
prefix: stat.prefix,
|
|
16958
|
+
suffix: stat.suffix,
|
|
16959
|
+
isLoading: false
|
|
16960
|
+
})));
|
|
16484
16961
|
this._statValues.set(updatedValues);
|
|
16485
16962
|
}
|
|
16486
16963
|
catch (err) {
|
|
@@ -16492,6 +16969,41 @@ class StatsGridWidgetComponent {
|
|
|
16492
16969
|
this._isLoading.set(false);
|
|
16493
16970
|
}
|
|
16494
16971
|
}
|
|
16972
|
+
/**
|
|
16973
|
+
* Resolves a single stat's value: a per-stat persistent query (runtime or
|
|
16974
|
+
* stream-data) when configured, otherwise the runtime aggregation result.
|
|
16975
|
+
*/
|
|
16976
|
+
async resolveStatValue(stat, aggregationResults) {
|
|
16977
|
+
if (stat.persistentQuerySource) {
|
|
16978
|
+
return this.loadPersistentQueryValue(stat.persistentQuerySource);
|
|
16979
|
+
}
|
|
16980
|
+
return aggregationResults.get(stat.queryId) ?? null;
|
|
16981
|
+
}
|
|
16982
|
+
async loadPersistentQueryValue(source) {
|
|
16983
|
+
try {
|
|
16984
|
+
const result = await firstValueFrom(this.queryExecutor.execute(source.queryFamily, source.queryRtId, {
|
|
16985
|
+
fieldFilter: this.convertFiltersToDto(source.filters),
|
|
16986
|
+
streamDataArgs: this.buildStreamDataArgs(source)
|
|
16987
|
+
}));
|
|
16988
|
+
return extractPersistentQueryCellValue(result, source);
|
|
16989
|
+
}
|
|
16990
|
+
catch (err) {
|
|
16991
|
+
console.error('Error loading stat persistent-query data:', err);
|
|
16992
|
+
return null;
|
|
16993
|
+
}
|
|
16994
|
+
}
|
|
16995
|
+
buildStreamDataArgs(source) {
|
|
16996
|
+
const timeArgs = this.stateService.resolveStreamDataTimeArgs(source.ignoreTimeFilter);
|
|
16997
|
+
const rtIds = this.stateService.resolveStreamDataRtIds(source.entitySelectorId);
|
|
16998
|
+
if (!timeArgs && !rtIds) {
|
|
16999
|
+
return undefined;
|
|
17000
|
+
}
|
|
17001
|
+
return { ...timeArgs, rtIds };
|
|
17002
|
+
}
|
|
17003
|
+
convertFiltersToDto(filters) {
|
|
17004
|
+
const variables = this.stateService.getVariables();
|
|
17005
|
+
return this.variableService.convertToFieldFilterDto(filters, variables) ?? undefined;
|
|
17006
|
+
}
|
|
16495
17007
|
getColorClass(color) {
|
|
16496
17008
|
switch (color) {
|
|
16497
17009
|
case 'mint': return 'stat-mint';
|
|
@@ -16526,11 +17038,259 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
16526
17038
|
type: Input
|
|
16527
17039
|
}] } });
|
|
16528
17040
|
|
|
17041
|
+
/**
|
|
17042
|
+
* Reusable editor for a {@link PersistentQueryCellSource} (a single-value
|
|
17043
|
+
* persistent-query data source). Shared by the stats-grid stat editor and the
|
|
17044
|
+
* summary-card tile editor so both surface the identical runtime/stream-data
|
|
17045
|
+
* configuration UX (query picker, SD time-filter opt-out + asset-scope, query
|
|
17046
|
+
* mode and value/category fields). The runtime ↔ stream-data choice is derived
|
|
17047
|
+
* from the picked query's family — the SD-only controls hide for runtime queries.
|
|
17048
|
+
*/
|
|
17049
|
+
class PersistentQueryCellEditorComponent {
|
|
17050
|
+
getQueriesGQL = inject(GetSystemPersistentQueriesDtoGQL);
|
|
17051
|
+
initialSource;
|
|
17052
|
+
selectors = [];
|
|
17053
|
+
sourceChange = new EventEmitter();
|
|
17054
|
+
queries = [];
|
|
17055
|
+
selectedQuery = null;
|
|
17056
|
+
isLoading = false;
|
|
17057
|
+
ignoreTimeFilter = false;
|
|
17058
|
+
entitySelectorId;
|
|
17059
|
+
queryMode = 'simpleCount';
|
|
17060
|
+
queryValueField;
|
|
17061
|
+
queryCategoryField;
|
|
17062
|
+
queryCategoryValue;
|
|
17063
|
+
queryModeOptions = [
|
|
17064
|
+
{ value: 'simpleCount', label: 'Simple (total count)' },
|
|
17065
|
+
{ value: 'aggregation', label: 'Aggregation (single value)' },
|
|
17066
|
+
{ value: 'groupedAggregation', label: 'Grouped aggregation' }
|
|
17067
|
+
];
|
|
17068
|
+
/** Family of the picked query, derived from its own CK type. */
|
|
17069
|
+
get family() {
|
|
17070
|
+
return queryFamily(this.selectedQuery?.ckTypeId) ?? undefined;
|
|
17071
|
+
}
|
|
17072
|
+
async ngOnInit() {
|
|
17073
|
+
const src = this.initialSource;
|
|
17074
|
+
if (src) {
|
|
17075
|
+
this.ignoreTimeFilter = src.ignoreTimeFilter ?? false;
|
|
17076
|
+
this.entitySelectorId = src.entitySelectorId;
|
|
17077
|
+
this.queryMode = src.queryMode ?? 'simpleCount';
|
|
17078
|
+
this.queryValueField = src.queryValueField;
|
|
17079
|
+
this.queryCategoryField = src.queryCategoryField;
|
|
17080
|
+
this.queryCategoryValue = src.queryCategoryValue;
|
|
17081
|
+
}
|
|
17082
|
+
await this.loadQueries();
|
|
17083
|
+
if (src?.queryRtId) {
|
|
17084
|
+
this.selectedQuery = this.queries.find(q => q.rtId === src.queryRtId)
|
|
17085
|
+
?? { rtId: src.queryRtId, name: src.queryName ?? src.queryRtId, ckTypeId: undefined, queryCkTypeId: undefined };
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
17088
|
+
async onFilter(text) {
|
|
17089
|
+
await this.loadQueries(text);
|
|
17090
|
+
}
|
|
17091
|
+
onQuerySelected(query) {
|
|
17092
|
+
this.selectedQuery = query;
|
|
17093
|
+
// Reset SD-only state when the picked query is not a stream-data query.
|
|
17094
|
+
if (this.family !== 'streamData') {
|
|
17095
|
+
this.ignoreTimeFilter = false;
|
|
17096
|
+
this.entitySelectorId = undefined;
|
|
17097
|
+
}
|
|
17098
|
+
this.emit();
|
|
17099
|
+
}
|
|
17100
|
+
emit() {
|
|
17101
|
+
if (!this.selectedQuery) {
|
|
17102
|
+
// Emit an empty-rtId source so the parent can flag the cell invalid.
|
|
17103
|
+
this.sourceChange.emit({ queryRtId: '', queryMode: this.queryMode });
|
|
17104
|
+
return;
|
|
17105
|
+
}
|
|
17106
|
+
const family = this.family;
|
|
17107
|
+
const source = {
|
|
17108
|
+
queryRtId: this.selectedQuery.rtId,
|
|
17109
|
+
queryName: this.selectedQuery.name,
|
|
17110
|
+
queryFamily: family,
|
|
17111
|
+
queryMode: this.queryMode,
|
|
17112
|
+
queryValueField: this.queryValueField || undefined,
|
|
17113
|
+
queryCategoryField: this.queryCategoryField || undefined,
|
|
17114
|
+
queryCategoryValue: this.queryCategoryValue || undefined,
|
|
17115
|
+
filters: this.initialSource?.filters
|
|
17116
|
+
};
|
|
17117
|
+
if (family === 'streamData') {
|
|
17118
|
+
if (this.ignoreTimeFilter)
|
|
17119
|
+
source.ignoreTimeFilter = true;
|
|
17120
|
+
if (this.entitySelectorId)
|
|
17121
|
+
source.entitySelectorId = this.entitySelectorId;
|
|
17122
|
+
}
|
|
17123
|
+
this.sourceChange.emit(source);
|
|
17124
|
+
}
|
|
17125
|
+
async loadQueries(searchText) {
|
|
17126
|
+
this.isLoading = true;
|
|
17127
|
+
try {
|
|
17128
|
+
const result = await firstValueFrom(this.getQueriesGQL.fetch({
|
|
17129
|
+
first: 100,
|
|
17130
|
+
searchFilter: searchText ? { searchTerm: searchText, language: 'de' } : undefined
|
|
17131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Apollo fetch requires flexible variable typing
|
|
17132
|
+
}));
|
|
17133
|
+
this.queries = (result.data?.runtime?.systemPersistentQuery?.items ?? [])
|
|
17134
|
+
.filter((item) => item !== null)
|
|
17135
|
+
.map(item => ({
|
|
17136
|
+
rtId: item.rtId,
|
|
17137
|
+
name: item.name,
|
|
17138
|
+
description: item.description,
|
|
17139
|
+
ckTypeId: item.ckTypeId,
|
|
17140
|
+
queryCkTypeId: item.queryCkTypeId
|
|
17141
|
+
}));
|
|
17142
|
+
}
|
|
17143
|
+
finally {
|
|
17144
|
+
this.isLoading = false;
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PersistentQueryCellEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
17148
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: PersistentQueryCellEditorComponent, isStandalone: true, selector: "mm-persistent-query-cell-editor", inputs: { initialSource: "initialSource", selectors: "selectors" }, outputs: { sourceChange: "sourceChange" }, ngImport: i0, template: `
|
|
17149
|
+
<div class="pq-editor">
|
|
17150
|
+
<div class="pq-field">
|
|
17151
|
+
<label>Persistent Query <span class="required">*</span></label>
|
|
17152
|
+
<kendo-combobox
|
|
17153
|
+
[data]="queries"
|
|
17154
|
+
[textField]="'name'"
|
|
17155
|
+
[valueField]="'rtId'"
|
|
17156
|
+
[valuePrimitive]="false"
|
|
17157
|
+
[(ngModel)]="selectedQuery"
|
|
17158
|
+
[filterable]="true"
|
|
17159
|
+
(filterChange)="onFilter($event)"
|
|
17160
|
+
(valueChange)="onQuerySelected($event)"
|
|
17161
|
+
[loading]="isLoading"
|
|
17162
|
+
placeholder="Select a query...">
|
|
17163
|
+
</kendo-combobox>
|
|
17164
|
+
</div>
|
|
17165
|
+
|
|
17166
|
+
<!-- Stream-data-only options (hidden for runtime queries) -->
|
|
17167
|
+
<mm-sd-time-filter-toggle
|
|
17168
|
+
[family]="family"
|
|
17169
|
+
[(ignoreTimeFilter)]="ignoreTimeFilter"
|
|
17170
|
+
(ignoreTimeFilterChange)="emit()">
|
|
17171
|
+
</mm-sd-time-filter-toggle>
|
|
17172
|
+
<mm-entity-selector-scope-picker
|
|
17173
|
+
[family]="family"
|
|
17174
|
+
[selectors]="selectors"
|
|
17175
|
+
[(entitySelectorId)]="entitySelectorId"
|
|
17176
|
+
(entitySelectorIdChange)="emit()">
|
|
17177
|
+
</mm-entity-selector-scope-picker>
|
|
17178
|
+
|
|
17179
|
+
<div class="pq-field">
|
|
17180
|
+
<label>Query Mode</label>
|
|
17181
|
+
<kendo-dropdownlist
|
|
17182
|
+
[data]="queryModeOptions"
|
|
17183
|
+
textField="label"
|
|
17184
|
+
valueField="value"
|
|
17185
|
+
[valuePrimitive]="true"
|
|
17186
|
+
[(ngModel)]="queryMode"
|
|
17187
|
+
(valueChange)="emit()">
|
|
17188
|
+
</kendo-dropdownlist>
|
|
17189
|
+
</div>
|
|
17190
|
+
|
|
17191
|
+
@if (queryMode === 'aggregation' || queryMode === 'groupedAggregation') {
|
|
17192
|
+
<div class="pq-field">
|
|
17193
|
+
<label>Value Field</label>
|
|
17194
|
+
<kendo-textbox [(ngModel)]="queryValueField" (valueChange)="emit()" placeholder="e.g. amountvalue_sum"></kendo-textbox>
|
|
17195
|
+
</div>
|
|
17196
|
+
}
|
|
17197
|
+
|
|
17198
|
+
@if (queryMode === 'groupedAggregation') {
|
|
17199
|
+
<div class="pq-field">
|
|
17200
|
+
<label>Category Field</label>
|
|
17201
|
+
<kendo-textbox [(ngModel)]="queryCategoryField" (valueChange)="emit()" placeholder="e.g. operatingstatus"></kendo-textbox>
|
|
17202
|
+
</div>
|
|
17203
|
+
<div class="pq-field">
|
|
17204
|
+
<label>Category Value</label>
|
|
17205
|
+
<kendo-textbox [(ngModel)]="queryCategoryValue" (valueChange)="emit()" placeholder="value to match"></kendo-textbox>
|
|
17206
|
+
</div>
|
|
17207
|
+
}
|
|
17208
|
+
</div>
|
|
17209
|
+
`, isInline: true, styles: [".pq-editor{display:flex;flex-direction:column;gap:8px}.pq-field{display:flex;flex-direction:column;gap:4px}.pq-field label{font-weight:600;font-size:.85rem}.required{color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: 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: "ngmodule", type: DropDownsModule }, { 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: "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"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
17210
|
+
}
|
|
17211
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PersistentQueryCellEditorComponent, decorators: [{
|
|
17212
|
+
type: Component,
|
|
17213
|
+
args: [{ selector: 'mm-persistent-query-cell-editor', standalone: true, imports: [CommonModule, FormsModule, InputsModule, DropDownsModule, SdTimeFilterToggleComponent, EntitySelectorScopePickerComponent], changeDetection: ChangeDetectionStrategy.Eager, template: `
|
|
17214
|
+
<div class="pq-editor">
|
|
17215
|
+
<div class="pq-field">
|
|
17216
|
+
<label>Persistent Query <span class="required">*</span></label>
|
|
17217
|
+
<kendo-combobox
|
|
17218
|
+
[data]="queries"
|
|
17219
|
+
[textField]="'name'"
|
|
17220
|
+
[valueField]="'rtId'"
|
|
17221
|
+
[valuePrimitive]="false"
|
|
17222
|
+
[(ngModel)]="selectedQuery"
|
|
17223
|
+
[filterable]="true"
|
|
17224
|
+
(filterChange)="onFilter($event)"
|
|
17225
|
+
(valueChange)="onQuerySelected($event)"
|
|
17226
|
+
[loading]="isLoading"
|
|
17227
|
+
placeholder="Select a query...">
|
|
17228
|
+
</kendo-combobox>
|
|
17229
|
+
</div>
|
|
17230
|
+
|
|
17231
|
+
<!-- Stream-data-only options (hidden for runtime queries) -->
|
|
17232
|
+
<mm-sd-time-filter-toggle
|
|
17233
|
+
[family]="family"
|
|
17234
|
+
[(ignoreTimeFilter)]="ignoreTimeFilter"
|
|
17235
|
+
(ignoreTimeFilterChange)="emit()">
|
|
17236
|
+
</mm-sd-time-filter-toggle>
|
|
17237
|
+
<mm-entity-selector-scope-picker
|
|
17238
|
+
[family]="family"
|
|
17239
|
+
[selectors]="selectors"
|
|
17240
|
+
[(entitySelectorId)]="entitySelectorId"
|
|
17241
|
+
(entitySelectorIdChange)="emit()">
|
|
17242
|
+
</mm-entity-selector-scope-picker>
|
|
17243
|
+
|
|
17244
|
+
<div class="pq-field">
|
|
17245
|
+
<label>Query Mode</label>
|
|
17246
|
+
<kendo-dropdownlist
|
|
17247
|
+
[data]="queryModeOptions"
|
|
17248
|
+
textField="label"
|
|
17249
|
+
valueField="value"
|
|
17250
|
+
[valuePrimitive]="true"
|
|
17251
|
+
[(ngModel)]="queryMode"
|
|
17252
|
+
(valueChange)="emit()">
|
|
17253
|
+
</kendo-dropdownlist>
|
|
17254
|
+
</div>
|
|
17255
|
+
|
|
17256
|
+
@if (queryMode === 'aggregation' || queryMode === 'groupedAggregation') {
|
|
17257
|
+
<div class="pq-field">
|
|
17258
|
+
<label>Value Field</label>
|
|
17259
|
+
<kendo-textbox [(ngModel)]="queryValueField" (valueChange)="emit()" placeholder="e.g. amountvalue_sum"></kendo-textbox>
|
|
17260
|
+
</div>
|
|
17261
|
+
}
|
|
17262
|
+
|
|
17263
|
+
@if (queryMode === 'groupedAggregation') {
|
|
17264
|
+
<div class="pq-field">
|
|
17265
|
+
<label>Category Field</label>
|
|
17266
|
+
<kendo-textbox [(ngModel)]="queryCategoryField" (valueChange)="emit()" placeholder="e.g. operatingstatus"></kendo-textbox>
|
|
17267
|
+
</div>
|
|
17268
|
+
<div class="pq-field">
|
|
17269
|
+
<label>Category Value</label>
|
|
17270
|
+
<kendo-textbox [(ngModel)]="queryCategoryValue" (valueChange)="emit()" placeholder="value to match"></kendo-textbox>
|
|
17271
|
+
</div>
|
|
17272
|
+
}
|
|
17273
|
+
</div>
|
|
17274
|
+
`, styles: [".pq-editor{display:flex;flex-direction:column;gap:8px}.pq-field{display:flex;flex-direction:column;gap:4px}.pq-field label{font-weight:600;font-size:.85rem}.required{color:var(--kendo-color-error, #dc3545)}\n"] }]
|
|
17275
|
+
}], propDecorators: { initialSource: [{
|
|
17276
|
+
type: Input
|
|
17277
|
+
}], selectors: [{
|
|
17278
|
+
type: Input
|
|
17279
|
+
}], sourceChange: [{
|
|
17280
|
+
type: Output
|
|
17281
|
+
}] } });
|
|
17282
|
+
|
|
16529
17283
|
class StatsGridConfigDialogComponent {
|
|
16530
17284
|
windowRef = inject(WindowRef);
|
|
17285
|
+
stateService = inject(MeshBoardStateService);
|
|
16531
17286
|
initialStats;
|
|
16532
17287
|
initialQueries;
|
|
16533
17288
|
initialColumns;
|
|
17289
|
+
entitySelectors = [];
|
|
17290
|
+
sourceTypeOptions = [
|
|
17291
|
+
{ value: 'aggregation', label: 'Runtime aggregation' },
|
|
17292
|
+
{ value: 'persistentQuery', label: 'Persistent query' }
|
|
17293
|
+
];
|
|
16534
17294
|
colorOptions = [
|
|
16535
17295
|
{ value: 'mint', label: 'Mint', color: '#98d9c2' },
|
|
16536
17296
|
{ value: 'cyan', label: 'Cyan', color: '#5bc0be' },
|
|
@@ -16558,20 +17318,29 @@ class StatsGridConfigDialogComponent {
|
|
|
16558
17318
|
get isValid() {
|
|
16559
17319
|
if (this.form.stats.length === 0)
|
|
16560
17320
|
return false;
|
|
16561
|
-
return this.form.stats.every(stat =>
|
|
17321
|
+
return this.form.stats.every(stat => {
|
|
17322
|
+
if (!stat.label?.trim())
|
|
17323
|
+
return false;
|
|
17324
|
+
return stat.sourceType === 'persistentQuery'
|
|
17325
|
+
? !!stat.persistentQuerySource?.queryRtId
|
|
17326
|
+
: !!stat.ckTypeId?.trim();
|
|
17327
|
+
});
|
|
16562
17328
|
}
|
|
16563
17329
|
ngOnInit() {
|
|
16564
17330
|
this.filteredCkTypeOptions = [...this.ckTypeOptions];
|
|
16565
17331
|
this.form.columns = this.initialColumns ?? 3;
|
|
17332
|
+
this.entitySelectors = this.stateService.getEntitySelectors();
|
|
16566
17333
|
// Convert initial stats and queries to editable format
|
|
16567
|
-
if (this.initialStats
|
|
17334
|
+
if (this.initialStats) {
|
|
16568
17335
|
this.form.stats = this.initialStats.map(stat => {
|
|
16569
17336
|
const query = this.initialQueries?.find(q => q.id === stat.queryId);
|
|
16570
17337
|
return {
|
|
16571
17338
|
id: `stat-${this.nextId++}`,
|
|
16572
17339
|
label: stat.label,
|
|
17340
|
+
sourceType: stat.persistentQuerySource ? 'persistentQuery' : 'aggregation',
|
|
16573
17341
|
ckTypeId: query?.ckTypeId ?? '',
|
|
16574
17342
|
aggregation: query?.aggregation ?? 'count',
|
|
17343
|
+
persistentQuerySource: stat.persistentQuerySource,
|
|
16575
17344
|
color: stat.color ?? 'default',
|
|
16576
17345
|
prefix: stat.prefix,
|
|
16577
17346
|
suffix: stat.suffix
|
|
@@ -16588,6 +17357,7 @@ class StatsGridConfigDialogComponent {
|
|
|
16588
17357
|
this.form.stats.push({
|
|
16589
17358
|
id: `stat-${this.nextId++}`,
|
|
16590
17359
|
label: '',
|
|
17360
|
+
sourceType: 'aggregation',
|
|
16591
17361
|
ckTypeId: 'ConstructionKit/CkType',
|
|
16592
17362
|
aggregation: 'count',
|
|
16593
17363
|
color: 'default'
|
|
@@ -16602,6 +17372,18 @@ class StatsGridConfigDialogComponent {
|
|
|
16602
17372
|
const queries = [];
|
|
16603
17373
|
for (const stat of this.form.stats) {
|
|
16604
17374
|
const queryId = `query-${stat.id}`;
|
|
17375
|
+
if (stat.sourceType === 'persistentQuery') {
|
|
17376
|
+
// Persistent-query stat — value comes from the executor, no aggregation query.
|
|
17377
|
+
stats.push({
|
|
17378
|
+
label: stat.label,
|
|
17379
|
+
queryId,
|
|
17380
|
+
persistentQuerySource: stat.persistentQuerySource,
|
|
17381
|
+
color: stat.color,
|
|
17382
|
+
prefix: stat.prefix,
|
|
17383
|
+
suffix: stat.suffix
|
|
17384
|
+
});
|
|
17385
|
+
continue;
|
|
17386
|
+
}
|
|
16605
17387
|
stats.push({
|
|
16606
17388
|
label: stat.label,
|
|
16607
17389
|
queryId,
|
|
@@ -16705,28 +17487,53 @@ class StatsGridConfigDialogComponent {
|
|
|
16705
17487
|
</div>
|
|
16706
17488
|
|
|
16707
17489
|
<div class="form-row">
|
|
16708
|
-
<div class="form-field flex-
|
|
16709
|
-
<label>
|
|
17490
|
+
<div class="form-field flex-1">
|
|
17491
|
+
<label>Data Source</label>
|
|
16710
17492
|
<kendo-dropdownlist
|
|
16711
|
-
[data]="
|
|
17493
|
+
[data]="sourceTypeOptions"
|
|
16712
17494
|
textField="label"
|
|
16713
17495
|
valueField="value"
|
|
16714
17496
|
[valuePrimitive]="true"
|
|
16715
|
-
[(ngModel)]="stat.
|
|
16716
|
-
[filterable]="true"
|
|
16717
|
-
(filterChange)="onCkTypeFilter($event)">
|
|
16718
|
-
</kendo-dropdownlist>
|
|
16719
|
-
</div>
|
|
16720
|
-
<div class="form-field flex-1">
|
|
16721
|
-
<label>Aggregation</label>
|
|
16722
|
-
<kendo-dropdownlist
|
|
16723
|
-
[data]="aggregationOptions"
|
|
16724
|
-
[valuePrimitive]="true"
|
|
16725
|
-
[(ngModel)]="stat.aggregation">
|
|
17497
|
+
[(ngModel)]="stat.sourceType">
|
|
16726
17498
|
</kendo-dropdownlist>
|
|
16727
17499
|
</div>
|
|
16728
17500
|
</div>
|
|
16729
17501
|
|
|
17502
|
+
@if (stat.sourceType !== 'persistentQuery') {
|
|
17503
|
+
<div class="form-row">
|
|
17504
|
+
<div class="form-field flex-2">
|
|
17505
|
+
<label>CK Type <span class="required">*</span></label>
|
|
17506
|
+
<kendo-dropdownlist
|
|
17507
|
+
[data]="ckTypeOptions"
|
|
17508
|
+
textField="label"
|
|
17509
|
+
valueField="value"
|
|
17510
|
+
[valuePrimitive]="true"
|
|
17511
|
+
[(ngModel)]="stat.ckTypeId"
|
|
17512
|
+
[filterable]="true"
|
|
17513
|
+
(filterChange)="onCkTypeFilter($event)">
|
|
17514
|
+
</kendo-dropdownlist>
|
|
17515
|
+
</div>
|
|
17516
|
+
<div class="form-field flex-1">
|
|
17517
|
+
<label>Aggregation</label>
|
|
17518
|
+
<kendo-dropdownlist
|
|
17519
|
+
[data]="aggregationOptions"
|
|
17520
|
+
[valuePrimitive]="true"
|
|
17521
|
+
[(ngModel)]="stat.aggregation">
|
|
17522
|
+
</kendo-dropdownlist>
|
|
17523
|
+
</div>
|
|
17524
|
+
</div>
|
|
17525
|
+
} @else {
|
|
17526
|
+
<div class="form-row">
|
|
17527
|
+
<div class="form-field flex-1">
|
|
17528
|
+
<mm-persistent-query-cell-editor
|
|
17529
|
+
[initialSource]="stat.persistentQuerySource"
|
|
17530
|
+
[selectors]="entitySelectors"
|
|
17531
|
+
(sourceChange)="stat.persistentQuerySource = $event">
|
|
17532
|
+
</mm-persistent-query-cell-editor>
|
|
17533
|
+
</div>
|
|
17534
|
+
</div>
|
|
17535
|
+
}
|
|
17536
|
+
|
|
16730
17537
|
<div class="form-row">
|
|
16731
17538
|
<div class="form-field flex-1">
|
|
16732
17539
|
<label>Prefix</label>
|
|
@@ -16760,7 +17567,7 @@ class StatsGridConfigDialogComponent {
|
|
|
16760
17567
|
</button>
|
|
16761
17568
|
</div>
|
|
16762
17569
|
</div>
|
|
16763
|
-
`, 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;gap:20px;padding:16px;flex:1;overflow-y:auto}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.flex-1{flex:1}.form-field.flex-2{flex:2}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.section-header h4{margin:0;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.form-row{display:flex;gap:16px}.required{color:var(--kendo-color-error, #dc3545)}.empty-state{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.stat-item{padding:16px;margin-bottom:12px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.stat-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.stat-number{font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.stat-form{display:flex;flex-direction:column;gap:12px}.color-item{display:flex;align-items:center;gap:8px}.color-swatch{width:16px;height:16px;border-radius:3px;border:1px solid rgba(0,0,0,.1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { 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: "directive", type: i4.ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
17570
|
+
`, 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;gap:20px;padding:16px;flex:1;overflow-y:auto}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.flex-1{flex:1}.form-field.flex-2{flex:2}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.section-header h4{margin:0;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.form-row{display:flex;gap:16px}.required{color:var(--kendo-color-error, #dc3545)}.empty-state{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.stat-item{padding:16px;margin-bottom:12px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.stat-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.stat-number{font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.stat-form{display:flex;flex-direction:column;gap:12px}.color-item{display:flex;align-items:center;gap:8px}.color-swatch{width:16px;height:16px;border-radius:3px;border:1px solid rgba(0,0,0,.1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { 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: "directive", type: i4.ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "component", type: PersistentQueryCellEditorComponent, selector: "mm-persistent-query-cell-editor", inputs: ["initialSource", "selectors"], outputs: ["sourceChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
16764
17571
|
}
|
|
16765
17572
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: StatsGridConfigDialogComponent, decorators: [{
|
|
16766
17573
|
type: Component,
|
|
@@ -16769,7 +17576,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
16769
17576
|
FormsModule,
|
|
16770
17577
|
ButtonsModule,
|
|
16771
17578
|
InputsModule,
|
|
16772
|
-
DropDownsModule
|
|
17579
|
+
DropDownsModule,
|
|
17580
|
+
PersistentQueryCellEditorComponent
|
|
16773
17581
|
], template: `
|
|
16774
17582
|
<div class="config-container">
|
|
16775
17583
|
|
|
@@ -16849,28 +17657,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
16849
17657
|
</div>
|
|
16850
17658
|
|
|
16851
17659
|
<div class="form-row">
|
|
16852
|
-
<div class="form-field flex-
|
|
16853
|
-
<label>
|
|
17660
|
+
<div class="form-field flex-1">
|
|
17661
|
+
<label>Data Source</label>
|
|
16854
17662
|
<kendo-dropdownlist
|
|
16855
|
-
[data]="
|
|
17663
|
+
[data]="sourceTypeOptions"
|
|
16856
17664
|
textField="label"
|
|
16857
17665
|
valueField="value"
|
|
16858
17666
|
[valuePrimitive]="true"
|
|
16859
|
-
[(ngModel)]="stat.
|
|
16860
|
-
[filterable]="true"
|
|
16861
|
-
(filterChange)="onCkTypeFilter($event)">
|
|
16862
|
-
</kendo-dropdownlist>
|
|
16863
|
-
</div>
|
|
16864
|
-
<div class="form-field flex-1">
|
|
16865
|
-
<label>Aggregation</label>
|
|
16866
|
-
<kendo-dropdownlist
|
|
16867
|
-
[data]="aggregationOptions"
|
|
16868
|
-
[valuePrimitive]="true"
|
|
16869
|
-
[(ngModel)]="stat.aggregation">
|
|
17667
|
+
[(ngModel)]="stat.sourceType">
|
|
16870
17668
|
</kendo-dropdownlist>
|
|
16871
17669
|
</div>
|
|
16872
17670
|
</div>
|
|
16873
17671
|
|
|
17672
|
+
@if (stat.sourceType !== 'persistentQuery') {
|
|
17673
|
+
<div class="form-row">
|
|
17674
|
+
<div class="form-field flex-2">
|
|
17675
|
+
<label>CK Type <span class="required">*</span></label>
|
|
17676
|
+
<kendo-dropdownlist
|
|
17677
|
+
[data]="ckTypeOptions"
|
|
17678
|
+
textField="label"
|
|
17679
|
+
valueField="value"
|
|
17680
|
+
[valuePrimitive]="true"
|
|
17681
|
+
[(ngModel)]="stat.ckTypeId"
|
|
17682
|
+
[filterable]="true"
|
|
17683
|
+
(filterChange)="onCkTypeFilter($event)">
|
|
17684
|
+
</kendo-dropdownlist>
|
|
17685
|
+
</div>
|
|
17686
|
+
<div class="form-field flex-1">
|
|
17687
|
+
<label>Aggregation</label>
|
|
17688
|
+
<kendo-dropdownlist
|
|
17689
|
+
[data]="aggregationOptions"
|
|
17690
|
+
[valuePrimitive]="true"
|
|
17691
|
+
[(ngModel)]="stat.aggregation">
|
|
17692
|
+
</kendo-dropdownlist>
|
|
17693
|
+
</div>
|
|
17694
|
+
</div>
|
|
17695
|
+
} @else {
|
|
17696
|
+
<div class="form-row">
|
|
17697
|
+
<div class="form-field flex-1">
|
|
17698
|
+
<mm-persistent-query-cell-editor
|
|
17699
|
+
[initialSource]="stat.persistentQuerySource"
|
|
17700
|
+
[selectors]="entitySelectors"
|
|
17701
|
+
(sourceChange)="stat.persistentQuerySource = $event">
|
|
17702
|
+
</mm-persistent-query-cell-editor>
|
|
17703
|
+
</div>
|
|
17704
|
+
</div>
|
|
17705
|
+
}
|
|
17706
|
+
|
|
16874
17707
|
<div class="form-row">
|
|
16875
17708
|
<div class="form-field flex-1">
|
|
16876
17709
|
<label>Prefix</label>
|
|
@@ -19607,6 +20440,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
19607
20440
|
class SummaryCardWidgetComponent {
|
|
19608
20441
|
entityGQL = inject(GetDashboardEntityDtoGQL);
|
|
19609
20442
|
dataService = inject(DashboardDataService);
|
|
20443
|
+
queryExecutor = inject(QueryExecutorService);
|
|
20444
|
+
stateService = inject(MeshBoardStateService);
|
|
20445
|
+
variableService = inject(MeshBoardVariableService);
|
|
19610
20446
|
config;
|
|
19611
20447
|
_isLoading = signal(false, /* @ts-ignore */
|
|
19612
20448
|
...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -19660,6 +20496,9 @@ class SummaryCardWidgetComponent {
|
|
|
19660
20496
|
}
|
|
19661
20497
|
}
|
|
19662
20498
|
async fetchTileValue(tile, entityCache) {
|
|
20499
|
+
if (tile.persistentQuerySource) {
|
|
20500
|
+
return this.fetchPersistentQueryValue(tile.persistentQuerySource);
|
|
20501
|
+
}
|
|
19663
20502
|
if (tile.entitySource) {
|
|
19664
20503
|
const { rtId, ckTypeId, attributePath } = tile.entitySource;
|
|
19665
20504
|
if (!entityCache.has(rtId)) {
|
|
@@ -19681,6 +20520,25 @@ class SummaryCardWidgetComponent {
|
|
|
19681
20520
|
}
|
|
19682
20521
|
return null;
|
|
19683
20522
|
}
|
|
20523
|
+
async fetchPersistentQueryValue(source) {
|
|
20524
|
+
const result = await firstValueFrom(this.queryExecutor.execute(source.queryFamily, source.queryRtId, {
|
|
20525
|
+
fieldFilter: this.convertFiltersToDto(source.filters),
|
|
20526
|
+
streamDataArgs: this.buildStreamDataArgs(source)
|
|
20527
|
+
}));
|
|
20528
|
+
return extractPersistentQueryCellValue(result, source);
|
|
20529
|
+
}
|
|
20530
|
+
buildStreamDataArgs(source) {
|
|
20531
|
+
const timeArgs = this.stateService.resolveStreamDataTimeArgs(source.ignoreTimeFilter);
|
|
20532
|
+
const rtIds = this.stateService.resolveStreamDataRtIds(source.entitySelectorId);
|
|
20533
|
+
if (!timeArgs && !rtIds) {
|
|
20534
|
+
return undefined;
|
|
20535
|
+
}
|
|
20536
|
+
return { ...timeArgs, rtIds };
|
|
20537
|
+
}
|
|
20538
|
+
convertFiltersToDto(filters) {
|
|
20539
|
+
const variables = this.stateService.getVariables();
|
|
20540
|
+
return this.variableService.convertToFieldFilterDto(filters, variables) ?? undefined;
|
|
20541
|
+
}
|
|
19684
20542
|
async fetchEntityAttributes(rtId, ckTypeId) {
|
|
19685
20543
|
const result = await firstValueFrom(this.entityGQL.fetch({
|
|
19686
20544
|
variables: { rtId, ckTypeId }
|
|
@@ -19755,21 +20613,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
19755
20613
|
|
|
19756
20614
|
class SummaryCardConfigDialogComponent {
|
|
19757
20615
|
windowRef = inject(WindowRef);
|
|
20616
|
+
stateService = inject(MeshBoardStateService);
|
|
19758
20617
|
initialColumns;
|
|
19759
20618
|
initialTiles;
|
|
20619
|
+
entitySelectors = [];
|
|
19760
20620
|
form = {
|
|
19761
20621
|
columns: 2,
|
|
19762
20622
|
tiles: []
|
|
19763
20623
|
};
|
|
19764
20624
|
colorOptions = ['default', 'primary', 'success', 'warning', 'error'];
|
|
19765
20625
|
sizeOptions = ['normal', 'full'];
|
|
19766
|
-
sourceTypeOptions = ['entity', 'aggregation'];
|
|
20626
|
+
sourceTypeOptions = ['entity', 'aggregation', 'persistentQuery'];
|
|
19767
20627
|
aggregationOptions = ['count', 'sum', 'avg', 'min', 'max'];
|
|
19768
20628
|
get isValid() {
|
|
19769
|
-
return this.form.tiles.length > 0 && this.form.tiles.every(t =>
|
|
20629
|
+
return this.form.tiles.length > 0 && this.form.tiles.every(t => {
|
|
20630
|
+
if (!t.label)
|
|
20631
|
+
return false;
|
|
20632
|
+
return t.sourceType === 'persistentQuery' ? !!t.persistentQuerySource?.queryRtId : !!t.ckTypeId;
|
|
20633
|
+
});
|
|
19770
20634
|
}
|
|
19771
20635
|
ngOnInit() {
|
|
19772
20636
|
this.form.columns = this.initialColumns ?? 2;
|
|
20637
|
+
this.entitySelectors = this.stateService.getEntitySelectors();
|
|
19773
20638
|
if (this.initialTiles) {
|
|
19774
20639
|
this.form.tiles = this.initialTiles.map((t, i) => ({
|
|
19775
20640
|
id: t.id || `tile-${i}`,
|
|
@@ -19778,12 +20643,13 @@ class SummaryCardConfigDialogComponent {
|
|
|
19778
20643
|
suffix: t.suffix ?? '',
|
|
19779
20644
|
color: t.color ?? 'default',
|
|
19780
20645
|
size: t.size ?? 'normal',
|
|
19781
|
-
sourceType: t.entitySource ? 'entity' : 'aggregation',
|
|
20646
|
+
sourceType: t.persistentQuerySource ? 'persistentQuery' : t.entitySource ? 'entity' : 'aggregation',
|
|
19782
20647
|
rtId: t.entitySource?.rtId ?? '',
|
|
19783
20648
|
ckTypeId: t.entitySource?.ckTypeId ?? t.aggregationSource?.ckTypeId ?? '',
|
|
19784
20649
|
attributePath: t.entitySource?.attributePath ?? '',
|
|
19785
20650
|
aggregation: t.aggregationSource?.aggregation ?? 'count',
|
|
19786
|
-
aggAttribute: t.aggregationSource?.attribute ?? ''
|
|
20651
|
+
aggAttribute: t.aggregationSource?.attribute ?? '',
|
|
20652
|
+
persistentQuerySource: t.persistentQuerySource
|
|
19787
20653
|
}));
|
|
19788
20654
|
}
|
|
19789
20655
|
}
|
|
@@ -19810,7 +20676,10 @@ class SummaryCardConfigDialogComponent {
|
|
|
19810
20676
|
color: t.color || undefined,
|
|
19811
20677
|
size: t.size || undefined
|
|
19812
20678
|
};
|
|
19813
|
-
if (t.sourceType === '
|
|
20679
|
+
if (t.sourceType === 'persistentQuery') {
|
|
20680
|
+
tile.persistentQuerySource = t.persistentQuerySource;
|
|
20681
|
+
}
|
|
20682
|
+
else if (t.sourceType === 'entity') {
|
|
19814
20683
|
tile.entitySource = { rtId: t.rtId, ckTypeId: t.ckTypeId, attributePath: t.attributePath };
|
|
19815
20684
|
}
|
|
19816
20685
|
else {
|
|
@@ -19871,6 +20740,16 @@ class SummaryCardConfigDialogComponent {
|
|
|
19871
20740
|
<kendo-textbox [(ngModel)]="tile.aggAttribute" placeholder="Attribute (for sum/avg)" style="flex: 1;"></kendo-textbox>
|
|
19872
20741
|
</div>
|
|
19873
20742
|
}
|
|
20743
|
+
@if (tile.sourceType === 'persistentQuery') {
|
|
20744
|
+
<div class="tile-row">
|
|
20745
|
+
<mm-persistent-query-cell-editor
|
|
20746
|
+
style="flex: 1;"
|
|
20747
|
+
[initialSource]="tile.persistentQuerySource"
|
|
20748
|
+
[selectors]="entitySelectors"
|
|
20749
|
+
(sourceChange)="tile.persistentQuerySource = $event">
|
|
20750
|
+
</mm-persistent-query-cell-editor>
|
|
20751
|
+
</div>
|
|
20752
|
+
}
|
|
19874
20753
|
</div>
|
|
19875
20754
|
}
|
|
19876
20755
|
<button kendoButton fillMode="flat" (click)="addTile()">+ Add Tile</button>
|
|
@@ -19881,11 +20760,11 @@ class SummaryCardConfigDialogComponent {
|
|
|
19881
20760
|
<button kendoButton themeColor="primary" [disabled]="!isValid" (click)="onSave()">Save</button>
|
|
19882
20761
|
</div>
|
|
19883
20762
|
</div>
|
|
19884
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}h4{margin:8px 0 4px;font-size:.95rem}.tile-config{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;padding:10px;margin-bottom:8px;display:flex;flex-direction:column;gap:6px}.tile-header{display:flex;justify-content:space-between;align-items:center}.tile-row{display:flex;gap:6px;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: DropDownsModule }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
20763
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}h4{margin:8px 0 4px;font-size:.95rem}.tile-config{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;padding:10px;margin-bottom:8px;display:flex;flex-direction:column;gap:6px}.tile-header{display:flex;justify-content:space-between;align-items:center}.tile-row{display:flex;gap:6px;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: DropDownsModule }, { 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: "component", type: PersistentQueryCellEditorComponent, selector: "mm-persistent-query-cell-editor", inputs: ["initialSource", "selectors"], outputs: ["sourceChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
19885
20764
|
}
|
|
19886
20765
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SummaryCardConfigDialogComponent, decorators: [{
|
|
19887
20766
|
type: Component,
|
|
19888
|
-
args: [{ selector: 'mm-summary-card-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, DropDownsModule], template: `
|
|
20767
|
+
args: [{ selector: 'mm-summary-card-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, DropDownsModule, PersistentQueryCellEditorComponent], template: `
|
|
19889
20768
|
<div class="config-container">
|
|
19890
20769
|
<div class="config-form">
|
|
19891
20770
|
<div class="form-field">
|
|
@@ -19924,6 +20803,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
19924
20803
|
<kendo-textbox [(ngModel)]="tile.aggAttribute" placeholder="Attribute (for sum/avg)" style="flex: 1;"></kendo-textbox>
|
|
19925
20804
|
</div>
|
|
19926
20805
|
}
|
|
20806
|
+
@if (tile.sourceType === 'persistentQuery') {
|
|
20807
|
+
<div class="tile-row">
|
|
20808
|
+
<mm-persistent-query-cell-editor
|
|
20809
|
+
style="flex: 1;"
|
|
20810
|
+
[initialSource]="tile.persistentQuerySource"
|
|
20811
|
+
[selectors]="entitySelectors"
|
|
20812
|
+
(sourceChange)="tile.persistentQuerySource = $event">
|
|
20813
|
+
</mm-persistent-query-cell-editor>
|
|
20814
|
+
</div>
|
|
20815
|
+
}
|
|
19927
20816
|
</div>
|
|
19928
20817
|
}
|
|
19929
20818
|
<button kendoButton fillMode="flat" (click)="addTile()">+ Add Tile</button>
|
|
@@ -20219,6 +21108,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
20219
21108
|
|
|
20220
21109
|
class AlertListWidgetComponent {
|
|
20221
21110
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
21111
|
+
stateService = inject(MeshBoardStateService);
|
|
20222
21112
|
config;
|
|
20223
21113
|
_isLoading = signal(false, /* @ts-ignore */
|
|
20224
21114
|
...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
|
|
@@ -20251,13 +21141,9 @@ class AlertListWidgetComponent {
|
|
|
20251
21141
|
return getAlertSeverityIcon(level);
|
|
20252
21142
|
}
|
|
20253
21143
|
formatTime(timestamp) {
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
}
|
|
20258
|
-
catch {
|
|
20259
|
-
return '';
|
|
20260
|
-
}
|
|
21144
|
+
return formatInstant(timestamp, this.stateService.timeZoneMode(), {
|
|
21145
|
+
day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'
|
|
21146
|
+
}) ?? '';
|
|
20261
21147
|
}
|
|
20262
21148
|
async loadData() {
|
|
20263
21149
|
this._isLoading.set(true);
|
|
@@ -20831,7 +21717,7 @@ class ProcessDataService {
|
|
|
20831
21717
|
getProcessDiagramsGQL = inject(GetProcessDiagramsDtoGQL);
|
|
20832
21718
|
createProcessDiagramGQL = inject(CreateProcessDiagramDtoGQL);
|
|
20833
21719
|
updateProcessDiagramGQL = inject(UpdateProcessDiagramDtoGQL);
|
|
20834
|
-
|
|
21720
|
+
queryExecutor = inject(QueryExecutorService);
|
|
20835
21721
|
/**
|
|
20836
21722
|
* Loads a list of available process diagrams from the backend
|
|
20837
21723
|
*
|
|
@@ -21145,10 +22031,20 @@ class ProcessDataService {
|
|
|
21145
22031
|
if (!queryRtId) {
|
|
21146
22032
|
throw new Error('Persistent query binding requires queryRtId');
|
|
21147
22033
|
}
|
|
21148
|
-
//
|
|
21149
|
-
//
|
|
21150
|
-
|
|
21151
|
-
|
|
22034
|
+
// Execute via the unified executor (family resolved lazily). The active time
|
|
22035
|
+
// filter is applied by default for stream-data queries; runtime queries
|
|
22036
|
+
// ignore it. The element binding has no asset-scope, so no rtIds override.
|
|
22037
|
+
const result = await firstValueFrom(this.queryExecutor.execute(undefined, queryRtId, {
|
|
22038
|
+
first: 1,
|
|
22039
|
+
streamDataArgs: this.stateService.resolveStreamDataTimeArgs()
|
|
22040
|
+
}));
|
|
22041
|
+
// Extract the bound column from the first row.
|
|
22042
|
+
const firstRow = result.rows[0];
|
|
22043
|
+
if (!firstRow) {
|
|
22044
|
+
return undefined;
|
|
22045
|
+
}
|
|
22046
|
+
const cell = firstRow.cells.find(c => c.attributePath === binding.attributePath);
|
|
22047
|
+
return cell?.value;
|
|
21152
22048
|
}
|
|
21153
22049
|
/**
|
|
21154
22050
|
* Resolves variable references in a string
|
|
@@ -21327,60 +22223,67 @@ class ProcessDataService {
|
|
|
21327
22223
|
* Loads data from a persistent query binding
|
|
21328
22224
|
*/
|
|
21329
22225
|
async loadBoundQueryData(config) {
|
|
21330
|
-
const { bindingQueryRtId, bindingFilters } = config;
|
|
22226
|
+
const { bindingQueryRtId, bindingFilters, bindingQueryFamily } = config;
|
|
21331
22227
|
if (!bindingQueryRtId) {
|
|
21332
22228
|
throw new Error('Persistent query binding requires a Query rtId');
|
|
21333
22229
|
}
|
|
21334
22230
|
// Convert widget filters to GraphQL format with variable resolution
|
|
21335
22231
|
const fieldFilters = this.convertFiltersToDto(bindingFilters);
|
|
21336
|
-
// Execute the
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
|
|
22232
|
+
// Execute via the unified executor — it routes runtime vs stream-data from
|
|
22233
|
+
// the persisted family (or a one-time lookup when absent). Stream-data
|
|
22234
|
+
// queries additionally consume the time-filter / asset-scope overrides built
|
|
22235
|
+
// below; runtime queries ignore `streamDataArgs`.
|
|
22236
|
+
const result = await firstValueFrom(this.queryExecutor.execute(bindingQueryFamily, bindingQueryRtId, {
|
|
22237
|
+
first: 1000, // Reasonable limit for process diagram data
|
|
22238
|
+
fieldFilter: fieldFilters,
|
|
22239
|
+
streamDataArgs: this.buildBindingStreamDataArgs(config)
|
|
21343
22240
|
}));
|
|
21344
|
-
// Parse the query result
|
|
21345
|
-
const queryData = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
21346
|
-
if (!queryData) {
|
|
21347
|
-
throw new Error(`Query not found or returned no data: ${bindingQueryRtId}`);
|
|
21348
|
-
}
|
|
21349
|
-
// Map the result to our format
|
|
21350
|
-
const queryResult = this.mapQueryResult(queryData);
|
|
21351
22241
|
return {
|
|
21352
22242
|
type: 'query',
|
|
21353
|
-
queryResult,
|
|
22243
|
+
queryResult: this.mapExecutionResult(result),
|
|
21354
22244
|
loadedAt: new Date()
|
|
21355
22245
|
};
|
|
21356
22246
|
}
|
|
21357
22247
|
/**
|
|
21358
|
-
*
|
|
22248
|
+
* Builds the stream-data overrides (time range + asset scope) for the bound
|
|
22249
|
+
* persistent query from the active MeshBoard time filter and entity selector.
|
|
22250
|
+
* Returns `undefined` when neither override applies (e.g. runtime query, no
|
|
22251
|
+
* time filter, opted out). Runtime queries ignore the result.
|
|
21359
22252
|
*/
|
|
21360
|
-
|
|
21361
|
-
const
|
|
22253
|
+
buildBindingStreamDataArgs(config) {
|
|
22254
|
+
const timeArgs = this.stateService.resolveStreamDataTimeArgs(config.bindingIgnoreTimeFilter);
|
|
22255
|
+
const rtIds = this.stateService.resolveStreamDataRtIds(config.bindingEntitySelectorId);
|
|
22256
|
+
if (!timeArgs && !rtIds) {
|
|
22257
|
+
return undefined;
|
|
22258
|
+
}
|
|
22259
|
+
return { ...timeArgs, rtIds };
|
|
22260
|
+
}
|
|
22261
|
+
/**
|
|
22262
|
+
* Maps a unified {@link QueryExecutionResult} (runtime or stream-data) to the
|
|
22263
|
+
* Process Widget's {@link QueryResultData} shape used by column mappings.
|
|
22264
|
+
*/
|
|
22265
|
+
mapExecutionResult(result) {
|
|
22266
|
+
const columns = result.columns.map(col => ({
|
|
21362
22267
|
attributePath: col.attributePath ?? '',
|
|
21363
22268
|
attributeValueType: col.attributeValueType ?? undefined
|
|
21364
22269
|
}));
|
|
21365
|
-
const rows =
|
|
22270
|
+
const rows = result.rows.map(row => {
|
|
21366
22271
|
const cells = new Map();
|
|
21367
|
-
for (const cell of row
|
|
21368
|
-
if (cell
|
|
22272
|
+
for (const cell of row.cells) {
|
|
22273
|
+
if (cell.attributePath) {
|
|
21369
22274
|
cells.set(cell.attributePath, cell.value);
|
|
21370
22275
|
}
|
|
21371
22276
|
}
|
|
21372
|
-
// Handle different row types
|
|
21373
|
-
const typedRow = row;
|
|
21374
22277
|
return {
|
|
21375
|
-
rtId:
|
|
21376
|
-
ckTypeId:
|
|
22278
|
+
rtId: row.rtId ?? undefined,
|
|
22279
|
+
ckTypeId: row.ckTypeId ?? undefined,
|
|
21377
22280
|
cells
|
|
21378
22281
|
};
|
|
21379
22282
|
});
|
|
21380
22283
|
return {
|
|
21381
22284
|
columns,
|
|
21382
22285
|
rows,
|
|
21383
|
-
totalCount:
|
|
22286
|
+
totalCount: result.totalCount
|
|
21384
22287
|
};
|
|
21385
22288
|
}
|
|
21386
22289
|
/**
|
|
@@ -25121,6 +26024,9 @@ class ProcessConfigDialogComponent {
|
|
|
25121
26024
|
initialBindingRtId;
|
|
25122
26025
|
initialBindingQueryRtId;
|
|
25123
26026
|
initialBindingQueryName;
|
|
26027
|
+
initialBindingQueryFamily;
|
|
26028
|
+
initialBindingIgnoreTimeFilter;
|
|
26029
|
+
initialBindingEntitySelectorId;
|
|
25124
26030
|
initialBindingFilters;
|
|
25125
26031
|
initialPropertyMappings;
|
|
25126
26032
|
// State - Diagram
|
|
@@ -25137,6 +26043,14 @@ class ProcessConfigDialogComponent {
|
|
|
25137
26043
|
// State - Persistent Query Binding
|
|
25138
26044
|
persistentQueries = [];
|
|
25139
26045
|
selectedPersistentQuery = null;
|
|
26046
|
+
// Stream-data binding state (only relevant when the picked query is a stream-data query)
|
|
26047
|
+
bindingIgnoreTimeFilter = false;
|
|
26048
|
+
bindingEntitySelectorId;
|
|
26049
|
+
entitySelectors = [];
|
|
26050
|
+
/** Family of the picked persistent query, derived from its own CK type. */
|
|
26051
|
+
get selectedQueryFamily() {
|
|
26052
|
+
return queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? undefined;
|
|
26053
|
+
}
|
|
25140
26054
|
isLoadingQueries = false;
|
|
25141
26055
|
// State - Filters
|
|
25142
26056
|
filters = [];
|
|
@@ -25196,6 +26110,10 @@ class ProcessConfigDialogComponent {
|
|
|
25196
26110
|
this.form.initialZoom = this.initialInitialZoom ?? 1;
|
|
25197
26111
|
// Initialize data binding mode
|
|
25198
26112
|
this.dataBindingMode = this.initialDataBindingMode ?? 'none';
|
|
26113
|
+
// Initialize stream-data binding state + available entity selectors (for scope picker)
|
|
26114
|
+
this.bindingIgnoreTimeFilter = this.initialBindingIgnoreTimeFilter ?? false;
|
|
26115
|
+
this.bindingEntitySelectorId = this.initialBindingEntitySelectorId;
|
|
26116
|
+
this.entitySelectors = this.stateService.getEntitySelectors();
|
|
25199
26117
|
// Initialize filter variables from MeshBoard
|
|
25200
26118
|
this.initFilterVariables();
|
|
25201
26119
|
// Load data in parallel
|
|
@@ -25425,6 +26343,12 @@ class ProcessConfigDialogComponent {
|
|
|
25425
26343
|
}
|
|
25426
26344
|
onQuerySelected(query) {
|
|
25427
26345
|
this.selectedPersistentQuery = query;
|
|
26346
|
+
// Reset stream-data-only binding state when the picked query is not a
|
|
26347
|
+
// stream-data query (the toggle/scope picker are hidden in that case).
|
|
26348
|
+
if (this.selectedQueryFamily !== 'streamData') {
|
|
26349
|
+
this.bindingIgnoreTimeFilter = false;
|
|
26350
|
+
this.bindingEntitySelectorId = undefined;
|
|
26351
|
+
}
|
|
25428
26352
|
// Load attributes if query has associated CK type
|
|
25429
26353
|
if (query?.queryCkTypeId) {
|
|
25430
26354
|
this.loadAttributesForCkType(query.queryCkTypeId);
|
|
@@ -25447,6 +26371,7 @@ class ProcessConfigDialogComponent {
|
|
|
25447
26371
|
rtId: item.rtId,
|
|
25448
26372
|
name: item.name,
|
|
25449
26373
|
description: item.description,
|
|
26374
|
+
ckTypeId: item.ckTypeId,
|
|
25450
26375
|
queryCkTypeId: item.queryCkTypeId
|
|
25451
26376
|
}));
|
|
25452
26377
|
}
|
|
@@ -25518,6 +26443,17 @@ class ProcessConfigDialogComponent {
|
|
|
25518
26443
|
if (this.dataBindingMode === 'persistentQuery' && this.selectedPersistentQuery) {
|
|
25519
26444
|
result.bindingQueryRtId = this.selectedPersistentQuery.rtId;
|
|
25520
26445
|
result.bindingQueryName = this.selectedPersistentQuery.name;
|
|
26446
|
+
const family = this.selectedQueryFamily;
|
|
26447
|
+
result.bindingQueryFamily = family;
|
|
26448
|
+
// Stream-data-only overrides — persisted only when they apply.
|
|
26449
|
+
if (family === 'streamData') {
|
|
26450
|
+
if (this.bindingIgnoreTimeFilter) {
|
|
26451
|
+
result.bindingIgnoreTimeFilter = true;
|
|
26452
|
+
}
|
|
26453
|
+
if (this.bindingEntitySelectorId) {
|
|
26454
|
+
result.bindingEntitySelectorId = this.bindingEntitySelectorId;
|
|
26455
|
+
}
|
|
26456
|
+
}
|
|
25521
26457
|
}
|
|
25522
26458
|
// Filters
|
|
25523
26459
|
if (this.filters.length > 0 && this.dataBindingMode !== 'none') {
|
|
@@ -25540,7 +26476,7 @@ class ProcessConfigDialogComponent {
|
|
|
25540
26476
|
this.windowRef.close();
|
|
25541
26477
|
}
|
|
25542
26478
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ProcessConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
25543
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ProcessConfigDialogComponent, isStandalone: true, selector: "mm-process-config-dialog", inputs: { initialProcessDiagramRtId: "initialProcessDiagramRtId", initialFitToBounds: "initialFitToBounds", initialAllowZoom: "initialAllowZoom", initialAllowPan: "initialAllowPan", initialShowToolbar: "initialShowToolbar", initialInitialZoom: "initialInitialZoom", initialDataBindingMode: "initialDataBindingMode", initialBindingCkTypeId: "initialBindingCkTypeId", initialBindingRtId: "initialBindingRtId", initialBindingQueryRtId: "initialBindingQueryRtId", initialBindingQueryName: "initialBindingQueryName", initialBindingFilters: "initialBindingFilters", initialPropertyMappings: "initialPropertyMappings" }, providers: [ProcessDataService], ngImport: i0, template: `
|
|
26479
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ProcessConfigDialogComponent, isStandalone: true, selector: "mm-process-config-dialog", inputs: { initialProcessDiagramRtId: "initialProcessDiagramRtId", initialFitToBounds: "initialFitToBounds", initialAllowZoom: "initialAllowZoom", initialAllowPan: "initialAllowPan", initialShowToolbar: "initialShowToolbar", initialInitialZoom: "initialInitialZoom", initialDataBindingMode: "initialDataBindingMode", initialBindingCkTypeId: "initialBindingCkTypeId", initialBindingRtId: "initialBindingRtId", initialBindingQueryRtId: "initialBindingQueryRtId", initialBindingQueryName: "initialBindingQueryName", initialBindingQueryFamily: "initialBindingQueryFamily", initialBindingIgnoreTimeFilter: "initialBindingIgnoreTimeFilter", initialBindingEntitySelectorId: "initialBindingEntitySelectorId", initialBindingFilters: "initialBindingFilters", initialPropertyMappings: "initialPropertyMappings" }, providers: [ProcessDataService], ngImport: i0, template: `
|
|
25544
26480
|
<div class="config-container">
|
|
25545
26481
|
|
|
25546
26482
|
<div class="config-form">
|
|
@@ -25706,6 +26642,17 @@ class ProcessConfigDialogComponent {
|
|
|
25706
26642
|
<p class="field-hint warning">No queries found.</p>
|
|
25707
26643
|
}
|
|
25708
26644
|
</div>
|
|
26645
|
+
|
|
26646
|
+
<!-- Stream-data-only options (hidden for runtime queries) -->
|
|
26647
|
+
<mm-sd-time-filter-toggle
|
|
26648
|
+
[family]="selectedQueryFamily"
|
|
26649
|
+
[(ignoreTimeFilter)]="bindingIgnoreTimeFilter">
|
|
26650
|
+
</mm-sd-time-filter-toggle>
|
|
26651
|
+
<mm-entity-selector-scope-picker
|
|
26652
|
+
[family]="selectedQueryFamily"
|
|
26653
|
+
[selectors]="entitySelectors"
|
|
26654
|
+
[(entitySelectorId)]="bindingEntitySelectorId">
|
|
26655
|
+
</mm-entity-selector-scope-picker>
|
|
25709
26656
|
}
|
|
25710
26657
|
|
|
25711
26658
|
<!-- Filters Section -->
|
|
@@ -25860,7 +26807,7 @@ class ProcessConfigDialogComponent {
|
|
|
25860
26807
|
</button>
|
|
25861
26808
|
</div>
|
|
25862
26809
|
</div>
|
|
25863
|
-
`, 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{flex:1;overflow-y:auto;padding:16px}.tab-content{padding:16px;min-height:200px;position:relative}.tab-content.loading{pointer-events:none}.section-hint{margin:0 0 16px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.empty-hint{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.form-field:last-child{margin-bottom:0}.form-field.disabled{opacity:.6}.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)}.field-hint.warning{color:var(--kendo-color-warning, #ffc107)}.form-row{display:flex;gap:24px;margin-bottom:12px}.form-row:last-child{margin-bottom:0}.mode-toggle{display:flex;gap:8px}.checkbox-field{flex-direction:row;align-items:flex-start}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.checkbox-field .field-hint{margin-top:4px;margin-left:28px}.preview-section{margin-top:16px;padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.preview-info{display:flex;flex-direction:column;gap:6px}.preview-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border-radius:4px}.preview-label{font-weight:500;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.preview-value{font-weight:600;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529)}.diagram-item{display:flex;justify-content:space-between;gap:16px}.diagram-name{font-weight:500}.diagram-size{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.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)}.property-mappings-table{display:flex;flex-direction:column;gap:6px}.mapping-header{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:4px;font-weight:600;font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);text-transform:uppercase}.mapping-row{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;align-items:center}.col-property{font-weight:500;font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-type.type-badge{font-size:.65rem;padding:2px 4px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:10px;text-align:center;text-transform:uppercase;color:var(--kendo-color-subtle, #6c757d)}.col-expression kendo-textbox{font-family:Consolas,Monaco,monospace;font-size:.8rem}.expression-help{margin-top:12px}.expression-help code{background:var(--kendo-color-surface-alt, #f8f9fa);padding:1px 4px;border-radius:3px;font-size:.75rem}.loading-hint{font-style:italic;color:var(--kendo-color-subtle, #6c757d);padding:8px;text-align:center}:host ::ng-deep kendo-tabstrip{border:none}:host ::ng-deep .k-tabstrip-items-wrapper{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}:host ::ng-deep .k-tabstrip-content{border:none;padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { 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: LayoutModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
26810
|
+
`, 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{flex:1;overflow-y:auto;padding:16px}.tab-content{padding:16px;min-height:200px;position:relative}.tab-content.loading{pointer-events:none}.section-hint{margin:0 0 16px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.empty-hint{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.form-field:last-child{margin-bottom:0}.form-field.disabled{opacity:.6}.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)}.field-hint.warning{color:var(--kendo-color-warning, #ffc107)}.form-row{display:flex;gap:24px;margin-bottom:12px}.form-row:last-child{margin-bottom:0}.mode-toggle{display:flex;gap:8px}.checkbox-field{flex-direction:row;align-items:flex-start}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.checkbox-field .field-hint{margin-top:4px;margin-left:28px}.preview-section{margin-top:16px;padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.preview-info{display:flex;flex-direction:column;gap:6px}.preview-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border-radius:4px}.preview-label{font-weight:500;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.preview-value{font-weight:600;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529)}.diagram-item{display:flex;justify-content:space-between;gap:16px}.diagram-name{font-weight:500}.diagram-size{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.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)}.property-mappings-table{display:flex;flex-direction:column;gap:6px}.mapping-header{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:4px;font-weight:600;font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);text-transform:uppercase}.mapping-row{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;align-items:center}.col-property{font-weight:500;font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-type.type-badge{font-size:.65rem;padding:2px 4px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:10px;text-align:center;text-transform:uppercase;color:var(--kendo-color-subtle, #6c757d)}.col-expression kendo-textbox{font-family:Consolas,Monaco,monospace;font-size:.8rem}.expression-help{margin-top:12px}.expression-help code{background:var(--kendo-color-surface-alt, #f8f9fa);padding:1px 4px;border-radius:3px;font-size:.75rem}.loading-hint{font-style:italic;color:var(--kendo-color-subtle, #6c757d);padding:8px;text-align:center}:host ::ng-deep kendo-tabstrip{border:none}:host ::ng-deep .k-tabstrip-items-wrapper{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}:host ::ng-deep .k-tabstrip-content{border:none;padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { 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: LayoutModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
25864
26811
|
}
|
|
25865
26812
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ProcessConfigDialogComponent, decorators: [{
|
|
25866
26813
|
type: Component,
|
|
@@ -25874,7 +26821,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
25874
26821
|
CkTypeSelectorInputComponent,
|
|
25875
26822
|
EntitySelectInputComponent,
|
|
25876
26823
|
FieldFilterEditorComponent,
|
|
25877
|
-
LoadingOverlayComponent
|
|
26824
|
+
LoadingOverlayComponent,
|
|
26825
|
+
SdTimeFilterToggleComponent,
|
|
26826
|
+
EntitySelectorScopePickerComponent
|
|
25878
26827
|
], providers: [ProcessDataService], template: `
|
|
25879
26828
|
<div class="config-container">
|
|
25880
26829
|
|
|
@@ -26041,6 +26990,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
26041
26990
|
<p class="field-hint warning">No queries found.</p>
|
|
26042
26991
|
}
|
|
26043
26992
|
</div>
|
|
26993
|
+
|
|
26994
|
+
<!-- Stream-data-only options (hidden for runtime queries) -->
|
|
26995
|
+
<mm-sd-time-filter-toggle
|
|
26996
|
+
[family]="selectedQueryFamily"
|
|
26997
|
+
[(ignoreTimeFilter)]="bindingIgnoreTimeFilter">
|
|
26998
|
+
</mm-sd-time-filter-toggle>
|
|
26999
|
+
<mm-entity-selector-scope-picker
|
|
27000
|
+
[family]="selectedQueryFamily"
|
|
27001
|
+
[selectors]="entitySelectors"
|
|
27002
|
+
[(entitySelectorId)]="bindingEntitySelectorId">
|
|
27003
|
+
</mm-entity-selector-scope-picker>
|
|
26044
27004
|
}
|
|
26045
27005
|
|
|
26046
27006
|
<!-- Filters Section -->
|
|
@@ -26218,6 +27178,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
26218
27178
|
type: Input
|
|
26219
27179
|
}], initialBindingQueryName: [{
|
|
26220
27180
|
type: Input
|
|
27181
|
+
}], initialBindingQueryFamily: [{
|
|
27182
|
+
type: Input
|
|
27183
|
+
}], initialBindingIgnoreTimeFilter: [{
|
|
27184
|
+
type: Input
|
|
27185
|
+
}], initialBindingEntitySelectorId: [{
|
|
27186
|
+
type: Input
|
|
26221
27187
|
}], initialBindingFilters: [{
|
|
26222
27188
|
type: Input
|
|
26223
27189
|
}], initialPropertyMappings: [{
|
|
@@ -26283,6 +27249,9 @@ function registerProcessWidget(registry) {
|
|
|
26283
27249
|
initialBindingRtId: widget.bindingRtId,
|
|
26284
27250
|
initialBindingQueryRtId: widget.bindingQueryRtId,
|
|
26285
27251
|
initialBindingQueryName: widget.bindingQueryName,
|
|
27252
|
+
initialBindingQueryFamily: widget.bindingQueryFamily,
|
|
27253
|
+
initialBindingIgnoreTimeFilter: widget.bindingIgnoreTimeFilter,
|
|
27254
|
+
initialBindingEntitySelectorId: widget.bindingEntitySelectorId,
|
|
26286
27255
|
initialBindingFilters: widget.bindingFilters,
|
|
26287
27256
|
initialPropertyMappings: widget.propertyMappings
|
|
26288
27257
|
}),
|
|
@@ -26303,6 +27272,9 @@ function registerProcessWidget(registry) {
|
|
|
26303
27272
|
bindingRtId: result.bindingRtId,
|
|
26304
27273
|
bindingQueryRtId: result.bindingQueryRtId,
|
|
26305
27274
|
bindingQueryName: result.bindingQueryName,
|
|
27275
|
+
bindingQueryFamily: result.bindingQueryFamily,
|
|
27276
|
+
bindingIgnoreTimeFilter: result.bindingIgnoreTimeFilter,
|
|
27277
|
+
bindingEntitySelectorId: result.bindingEntitySelectorId,
|
|
26306
27278
|
bindingFilters: result.bindingFilters,
|
|
26307
27279
|
propertyMappings: result.propertyMappings
|
|
26308
27280
|
}),
|
|
@@ -26341,6 +27313,10 @@ function registerProcessWidget(registry) {
|
|
|
26341
27313
|
bindingRtId: widget.bindingRtId,
|
|
26342
27314
|
bindingQueryRtId: widget.bindingQueryRtId,
|
|
26343
27315
|
bindingQueryName: widget.bindingQueryName,
|
|
27316
|
+
// Stream-data binding — written only when set.
|
|
27317
|
+
...(widget.bindingQueryFamily && { bindingQueryFamily: widget.bindingQueryFamily }),
|
|
27318
|
+
...(widget.bindingIgnoreTimeFilter && { bindingIgnoreTimeFilter: true }),
|
|
27319
|
+
...(widget.bindingEntitySelectorId && { bindingEntitySelectorId: widget.bindingEntitySelectorId }),
|
|
26344
27320
|
bindingFilters: widget.bindingFilters,
|
|
26345
27321
|
propertyMappings: widget.propertyMappings
|
|
26346
27322
|
}
|
|
@@ -26371,6 +27347,9 @@ function registerProcessWidget(registry) {
|
|
|
26371
27347
|
bindingRtId: config['bindingRtId'],
|
|
26372
27348
|
bindingQueryRtId: config['bindingQueryRtId'],
|
|
26373
27349
|
bindingQueryName: config['bindingQueryName'],
|
|
27350
|
+
bindingQueryFamily: config['bindingQueryFamily'],
|
|
27351
|
+
bindingIgnoreTimeFilter: config['bindingIgnoreTimeFilter'],
|
|
27352
|
+
bindingEntitySelectorId: config['bindingEntitySelectorId'],
|
|
26374
27353
|
bindingFilters: config['bindingFilters'],
|
|
26375
27354
|
propertyMappings: config['propertyMappings']
|
|
26376
27355
|
};
|
|
@@ -26498,14 +27477,25 @@ function registerDefaultWidgets(registry) {
|
|
|
26498
27477
|
configDialogTitle: 'Entity Configuration',
|
|
26499
27478
|
defaultSize: { colSpan: 2, rowSpan: 2 },
|
|
26500
27479
|
supportedDataSources: ['runtimeEntity'],
|
|
26501
|
-
getInitialConfig: (widget) =>
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
|
|
26505
|
-
|
|
27480
|
+
getInitialConfig: (widget) => {
|
|
27481
|
+
const ds = widget.dataSource;
|
|
27482
|
+
const isRuntime = ds.type === 'runtimeEntity';
|
|
27483
|
+
return {
|
|
27484
|
+
initialCkTypeId: isRuntime ? ds.ckTypeId : undefined,
|
|
27485
|
+
initialRtId: isRuntime ? ds.rtId : undefined,
|
|
27486
|
+
initialEntitySelectorId: isRuntime ? ds.entitySelectorId : undefined,
|
|
27487
|
+
initialHideEmptyAttributes: widget.hideEmptyAttributes ?? false
|
|
27488
|
+
};
|
|
27489
|
+
},
|
|
26506
27490
|
applyConfigResult: (widget, result) => ({
|
|
26507
27491
|
...widget,
|
|
26508
|
-
dataSource:
|
|
27492
|
+
dataSource: {
|
|
27493
|
+
type: 'runtimeEntity',
|
|
27494
|
+
ckTypeId: result.ckTypeId || undefined,
|
|
27495
|
+
rtId: result.rtId || undefined,
|
|
27496
|
+
entitySelectorId: result.entitySelectorId || undefined,
|
|
27497
|
+
includeAssociations: true
|
|
27498
|
+
},
|
|
26509
27499
|
hideEmptyAttributes: result.hideEmptyAttributes
|
|
26510
27500
|
}),
|
|
26511
27501
|
// SOLID: Factory function
|
|
@@ -26527,7 +27517,8 @@ function registerDefaultWidgets(registry) {
|
|
|
26527
27517
|
showHeader: widget.showHeader,
|
|
26528
27518
|
showAttributes: widget.showAttributes,
|
|
26529
27519
|
attributeFilter: widget.attributeFilter,
|
|
26530
|
-
hideEmptyAttributes: widget.hideEmptyAttributes
|
|
27520
|
+
hideEmptyAttributes: widget.hideEmptyAttributes,
|
|
27521
|
+
entitySelectorId: widget.dataSource.type === 'runtimeEntity' ? widget.dataSource.entitySelectorId : undefined
|
|
26531
27522
|
}
|
|
26532
27523
|
}),
|
|
26533
27524
|
// SOLID: Deserialization from persistence
|
|
@@ -26540,7 +27531,8 @@ function registerDefaultWidgets(registry) {
|
|
|
26540
27531
|
dataSource: {
|
|
26541
27532
|
type: 'runtimeEntity',
|
|
26542
27533
|
ckTypeId: data.dataSourceCkTypeId ?? undefined,
|
|
26543
|
-
rtId: data.dataSourceRtId ?? undefined
|
|
27534
|
+
rtId: data.dataSourceRtId ?? undefined,
|
|
27535
|
+
entitySelectorId: config['entitySelectorId'] || undefined
|
|
26544
27536
|
},
|
|
26545
27537
|
showHeader: config['showHeader'] ?? true,
|
|
26546
27538
|
showAttributes: config['showAttributes'] ?? true,
|
|
@@ -29144,7 +30136,8 @@ class MeshBoardSettingsResult {
|
|
|
29144
30136
|
rtWellKnownName;
|
|
29145
30137
|
entitySelectors;
|
|
29146
30138
|
autoRefreshSeconds;
|
|
29147
|
-
|
|
30139
|
+
timeZoneMode;
|
|
30140
|
+
constructor(name, description, columns, rowHeight, gap, variables, timeFilter, rtWellKnownName, entitySelectors, autoRefreshSeconds, timeZoneMode) {
|
|
29148
30141
|
this.name = name;
|
|
29149
30142
|
this.description = description;
|
|
29150
30143
|
this.columns = columns;
|
|
@@ -29155,6 +30148,7 @@ class MeshBoardSettingsResult {
|
|
|
29155
30148
|
this.rtWellKnownName = rtWellKnownName;
|
|
29156
30149
|
this.entitySelectors = entitySelectors;
|
|
29157
30150
|
this.autoRefreshSeconds = autoRefreshSeconds;
|
|
30151
|
+
this.timeZoneMode = timeZoneMode;
|
|
29158
30152
|
}
|
|
29159
30153
|
}
|
|
29160
30154
|
/**
|
|
@@ -29182,6 +30176,11 @@ class MeshBoardSettingsDialogComponent {
|
|
|
29182
30176
|
timeFilterEnabled = false;
|
|
29183
30177
|
defaultSelection;
|
|
29184
30178
|
initialDefaultSelection;
|
|
30179
|
+
/**
|
|
30180
|
+
* Timezone basis for time-filter boundaries and datetime display across all
|
|
30181
|
+
* widgets. Defaults to `'local'` (browser timezone).
|
|
30182
|
+
*/
|
|
30183
|
+
timeZoneMode = DEFAULT_TIME_ZONE_MODE;
|
|
29185
30184
|
/** Static and time filter variable names for duplicate detection in entity selector editor */
|
|
29186
30185
|
get staticVariableNames() {
|
|
29187
30186
|
return this.variables
|
|
@@ -29207,6 +30206,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
29207
30206
|
this.rowHeight = settings.rowHeight;
|
|
29208
30207
|
this.gap = settings.gap;
|
|
29209
30208
|
this.autoRefreshSeconds = settings.autoRefreshSeconds ?? 0;
|
|
30209
|
+
this.timeZoneMode = settings.timeZoneMode ?? DEFAULT_TIME_ZONE_MODE;
|
|
29210
30210
|
this.variables = settings.variables ? [...settings.variables] : [];
|
|
29211
30211
|
this.entitySelectors = settings.entitySelectors ? settings.entitySelectors.map(es => ({ ...es })) : [];
|
|
29212
30212
|
this.timeFilterEnabled = settings.timeFilter?.enabled ?? false;
|
|
@@ -29248,7 +30248,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
29248
30248
|
enabled: this.timeFilterEnabled,
|
|
29249
30249
|
defaultSelection: this.timeFilterEnabled ? this.defaultSelection : undefined
|
|
29250
30250
|
};
|
|
29251
|
-
const result = new MeshBoardSettingsResult(this.name.trim(), this.description.trim(), this.columns, this.rowHeight, this.gap, this.variables, timeFilter, this.rtWellKnownName.trim() || undefined, this.entitySelectors.length > 0 ? this.entitySelectors : undefined, this.autoRefreshSeconds > 0 ? this.autoRefreshSeconds : undefined);
|
|
30251
|
+
const result = new MeshBoardSettingsResult(this.name.trim(), this.description.trim(), this.columns, this.rowHeight, this.gap, this.variables, timeFilter, this.rtWellKnownName.trim() || undefined, this.entitySelectors.length > 0 ? this.entitySelectors : undefined, this.autoRefreshSeconds > 0 ? this.autoRefreshSeconds : undefined, this.timeZoneMode);
|
|
29252
30252
|
this.windowRef.close(result);
|
|
29253
30253
|
}
|
|
29254
30254
|
/**
|
|
@@ -29258,7 +30258,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
29258
30258
|
this.windowRef.close();
|
|
29259
30259
|
}
|
|
29260
30260
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MeshBoardSettingsDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
29261
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { 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: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }, { kind: "component", type: EntitySelectorEditorComponent, selector: "mm-entity-selector-editor", inputs: ["entitySelectors", "existingVariableNames"], outputs: ["entitySelectorsChange", "editingStateChange"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
30261
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <kendo-label text=\"Timezone\"></kendo-label>\n <div class=\"radio-group\">\n <div class=\"radio-wrapper\">\n <input\n type=\"radio\"\n kendoRadioButton\n #tzLocal\n [(ngModel)]=\"timeZoneMode\"\n name=\"timeZoneMode\"\n value=\"local\"\n id=\"timeZoneModeLocal\"/>\n <kendo-label [for]=\"tzLocal\" text=\"Local time (browser)\" class=\"radio-label\"></kendo-label>\n </div>\n <div class=\"radio-wrapper\">\n <input\n type=\"radio\"\n kendoRadioButton\n #tzUtc\n [(ngModel)]=\"timeZoneMode\"\n name=\"timeZoneMode\"\n value=\"utc\"\n id=\"timeZoneModeUtc\"/>\n <kendo-label [for]=\"tzUtc\" text=\"UTC\" class=\"radio-label\"></kendo-label>\n </div>\n </div>\n <kendo-formhint>\n Basis for the time filter and for how all widgets display dates and times. Local time is the default.\n </kendo-formhint>\n </kendo-formfield>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper,.meshboard-settings-dialog .settings-form .radio-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label,.meshboard-settings-dialog .settings-form .checkbox-wrapper .radio-label,.meshboard-settings-dialog .settings-form .radio-wrapper .checkbox-label,.meshboard-settings-dialog .settings-form .radio-wrapper .radio-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .settings-form .radio-group{display:flex;flex-direction:column;gap:.5rem;margin-top:.25rem}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { 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: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }, { kind: "component", type: EntitySelectorEditorComponent, selector: "mm-entity-selector-editor", inputs: ["entitySelectors", "existingVariableNames"], outputs: ["entitySelectorsChange", "editingStateChange"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
29262
30262
|
}
|
|
29263
30263
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MeshBoardSettingsDialogComponent, decorators: [{
|
|
29264
30264
|
type: Component,
|
|
@@ -29274,7 +30274,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
29274
30274
|
VariablesEditorComponent,
|
|
29275
30275
|
EntitySelectorEditorComponent,
|
|
29276
30276
|
TimeRangePickerComponent
|
|
29277
|
-
], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
|
|
30277
|
+
], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <kendo-label text=\"Timezone\"></kendo-label>\n <div class=\"radio-group\">\n <div class=\"radio-wrapper\">\n <input\n type=\"radio\"\n kendoRadioButton\n #tzLocal\n [(ngModel)]=\"timeZoneMode\"\n name=\"timeZoneMode\"\n value=\"local\"\n id=\"timeZoneModeLocal\"/>\n <kendo-label [for]=\"tzLocal\" text=\"Local time (browser)\" class=\"radio-label\"></kendo-label>\n </div>\n <div class=\"radio-wrapper\">\n <input\n type=\"radio\"\n kendoRadioButton\n #tzUtc\n [(ngModel)]=\"timeZoneMode\"\n name=\"timeZoneMode\"\n value=\"utc\"\n id=\"timeZoneModeUtc\"/>\n <kendo-label [for]=\"tzUtc\" text=\"UTC\" class=\"radio-label\"></kendo-label>\n </div>\n </div>\n <kendo-formhint>\n Basis for the time filter and for how all widgets display dates and times. Local time is the default.\n </kendo-formhint>\n </kendo-formfield>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper,.meshboard-settings-dialog .settings-form .radio-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label,.meshboard-settings-dialog .settings-form .checkbox-wrapper .radio-label,.meshboard-settings-dialog .settings-form .radio-wrapper .checkbox-label,.meshboard-settings-dialog .settings-form .radio-wrapper .radio-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .settings-form .radio-group{display:flex;flex-direction:column;gap:.5rem;margin-top:.25rem}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
|
|
29278
30278
|
}] });
|
|
29279
30279
|
|
|
29280
30280
|
/**
|
|
@@ -30444,7 +31444,7 @@ class MeshBoardViewComponent {
|
|
|
30444
31444
|
// Update state so widgets use the URL-derived time range
|
|
30445
31445
|
const sharedSelection = this.toSharedSelection(urlSelection);
|
|
30446
31446
|
const showTime = timeFilter.pickerConfig?.showTime ?? false;
|
|
30447
|
-
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
31447
|
+
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.stateService.timeZoneMode());
|
|
30448
31448
|
if (range) {
|
|
30449
31449
|
const rangeISO = TimeRangeUtils.toISO(range);
|
|
30450
31450
|
this.stateService.updateTimeFilterSelection(urlSelection, rangeISO.from, rangeISO.to);
|
|
@@ -30461,7 +31461,7 @@ class MeshBoardViewComponent {
|
|
|
30461
31461
|
this._urlTimeSelection.set(selection);
|
|
30462
31462
|
const sharedSelection = this.toSharedSelection(selection);
|
|
30463
31463
|
const showTime = timeFilter.pickerConfig?.showTime ?? false;
|
|
30464
|
-
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
31464
|
+
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.stateService.timeZoneMode());
|
|
30465
31465
|
if (range) {
|
|
30466
31466
|
const rangeISO = TimeRangeUtils.toISO(range);
|
|
30467
31467
|
this.stateService.updateTimeFilterSelection(selection, rangeISO.from, rangeISO.to);
|
|
@@ -30470,7 +31470,21 @@ class MeshBoardViewComponent {
|
|
|
30470
31470
|
}
|
|
30471
31471
|
const sharedSelection = this.toSharedSelection(selection);
|
|
30472
31472
|
const showTime = timeFilter.pickerConfig?.showTime ?? false;
|
|
30473
|
-
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
31473
|
+
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.stateService.timeZoneMode());
|
|
31474
|
+
if (!range) {
|
|
31475
|
+
return;
|
|
31476
|
+
}
|
|
31477
|
+
const rangeISO = TimeRangeUtils.toISO(range);
|
|
31478
|
+
this.stateService.setTimeFilterVariables(rangeISO.from, rangeISO.to);
|
|
31479
|
+
}
|
|
31480
|
+
/**
|
|
31481
|
+
* Recomputes the active time-filter range from the current selection on the
|
|
31482
|
+
* board's (possibly just-changed) timezone mode and republishes the
|
|
31483
|
+
* `$timeRangeFrom`/`$timeRangeTo` variables. No-op when the filter is
|
|
31484
|
+
* disabled or no selection is active.
|
|
31485
|
+
*/
|
|
31486
|
+
reapplyTimeFilterRange() {
|
|
31487
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
30474
31488
|
if (!range) {
|
|
30475
31489
|
return;
|
|
30476
31490
|
}
|
|
@@ -30540,6 +31554,11 @@ class MeshBoardViewComponent {
|
|
|
30540
31554
|
...settingsResult,
|
|
30541
31555
|
entitySelectors: settingsResult.entitySelectors
|
|
30542
31556
|
});
|
|
31557
|
+
// The timezone mode and/or default selection may have changed — recompute
|
|
31558
|
+
// the active time-filter range on the new basis so $timeRangeFrom/To and
|
|
31559
|
+
// every widget's display stay consistent, then reload widget data.
|
|
31560
|
+
this.reapplyTimeFilterRange();
|
|
31561
|
+
await this.refresh();
|
|
30543
31562
|
// Enter edit mode if not already in it
|
|
30544
31563
|
if (!this.isEditMode()) {
|
|
30545
31564
|
this.editModeService.enterEditMode(this.stateService.getConfig());
|
|
@@ -30730,7 +31749,7 @@ class MeshBoardViewComponent {
|
|
|
30730
31749
|
}
|
|
30731
31750
|
// Calculate the time range from the selection
|
|
30732
31751
|
const showTime = this.stateService.getTimeFilterConfig()?.pickerConfig?.showTime ?? false;
|
|
30733
|
-
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
31752
|
+
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.stateService.timeZoneMode());
|
|
30734
31753
|
if (!range)
|
|
30735
31754
|
return;
|
|
30736
31755
|
// Convert to ISO strings
|
|
@@ -30756,7 +31775,7 @@ class MeshBoardViewComponent {
|
|
|
30756
31775
|
const defaultSel = config.defaultSelection;
|
|
30757
31776
|
const sharedSelection = this.toSharedSelection(defaultSel);
|
|
30758
31777
|
const showTime = config.pickerConfig?.showTime ?? false;
|
|
30759
|
-
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
31778
|
+
const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime, this.stateService.timeZoneMode());
|
|
30760
31779
|
if (!range)
|
|
30761
31780
|
return;
|
|
30762
31781
|
const rangeISO = TimeRangeUtils.toISO(range);
|
|
@@ -31096,7 +32115,9 @@ class MeshBoardViewComponent {
|
|
|
31096
32115
|
isWidgetUnconfigured(widget) {
|
|
31097
32116
|
const dataSource = widget.dataSource;
|
|
31098
32117
|
if (dataSource.type === 'runtimeEntity') {
|
|
31099
|
-
|
|
32118
|
+
// An entity-selector binding (or a variable-bearing rtId/ckTypeId) counts
|
|
32119
|
+
// as configured — the entity is resolved live from a board-level selection.
|
|
32120
|
+
return !dataSource.entitySelectorId && !dataSource.rtId && !dataSource.ckTypeId;
|
|
31100
32121
|
}
|
|
31101
32122
|
if (dataSource.type === 'persistentQuery') {
|
|
31102
32123
|
return !dataSource.queryRtId;
|
|
@@ -31240,6 +32261,16 @@ class MeshBoardViewComponent {
|
|
|
31240
32261
|
if (!values.some(v => v.name === rtIdVariableName)) {
|
|
31241
32262
|
values.push({ name: rtIdVariableName, value: rtId, type: 'string' });
|
|
31242
32263
|
}
|
|
32264
|
+
// Auto-expose the selected entity's CK type as `$<selectorId>_rtCkTypeId`
|
|
32265
|
+
// so entity-display widgets (e.g. the entity card) can resolve both the
|
|
32266
|
+
// rtId and its type from a single board-level selection. Prefer the picked
|
|
32267
|
+
// entity's actual type (it may be a subtype of the selector's configured
|
|
32268
|
+
// `ckTypeId`); fall back to the selector's type. An explicit mapping to the
|
|
32269
|
+
// same variable name wins.
|
|
32270
|
+
const rtCkTypeIdVariableName = `${selector.id}_rtCkTypeId`;
|
|
32271
|
+
if (!values.some(v => v.name === rtCkTypeIdVariableName)) {
|
|
32272
|
+
values.push({ name: rtCkTypeIdVariableName, value: entity.ckTypeId ?? selector.ckTypeId, type: 'string' });
|
|
32273
|
+
}
|
|
31243
32274
|
this.stateService.setEntitySelectorVariables(selector.id, values);
|
|
31244
32275
|
// Resolve the stream-data scope rtIds for this selection (one-hop
|
|
31245
32276
|
// childScope traversal, or the picked entity itself). Runs alongside the
|
|
@@ -31377,5 +32408,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
31377
32408
|
* Generated bundle index. Do not edit.
|
|
31378
32409
|
*/
|
|
31379
32410
|
|
|
31380
|
-
export { AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, AutoRefreshTimerService, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QueryExecutorService, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, classifyQuery, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, queryFamily, registerDefaultWidgets, registerProcessWidget };
|
|
32411
|
+
export { AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, AutoRefreshTimerService, BarChartConfigDialogComponent, BarChartWidgetComponent, DEFAULT_TIME_ZONE_MODE, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QueryExecutorService, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, classifyQuery, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, queryFamily, registerDefaultWidgets, registerProcessWidget };
|
|
31381
32412
|
//# sourceMappingURL=meshmakers-octo-meshboard.mjs.map
|