@c8y/ngx-components 1023.63.0 → 1023.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/asset-properties/index.d.ts +2 -1
- package/asset-properties/index.d.ts.map +1 -1
- package/datapoints-export-selector/index.d.ts +213 -48
- package/datapoints-export-selector/index.d.ts.map +1 -1
- package/echart/index.d.ts +1 -0
- package/echart/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-asset-properties.mjs +22 -5
- package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs +568 -138
- package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart.mjs +6 -3
- package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-global-context.mjs +36 -1
- package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-definitions-datapoints-list.mjs +80 -0
- package/fesm2022/c8y-ngx-components-widgets-definitions-datapoints-list.mjs.map +1 -0
- package/fesm2022/c8y-ngx-components-widgets-definitions.mjs +1 -0
- package/fesm2022/c8y-ngx-components-widgets-definitions.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-exports.mjs +8 -1
- package/fesm2022/c8y-ngx-components-widgets-exports.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs +702 -0
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map +1 -0
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs +3 -110
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components.mjs +116 -6
- package/fesm2022/c8y-ngx-components.mjs.map +1 -1
- package/global-context/index.d.ts +2 -0
- package/global-context/index.d.ts.map +1 -1
- package/index.d.ts +59 -2
- package/index.d.ts.map +1 -1
- package/locales/de.po +94 -23
- package/locales/es.po +96 -23
- package/locales/fr.po +95 -23
- package/locales/ja_JP.po +82 -23
- package/locales/ko.po +97 -23
- package/locales/locales.pot +62 -11
- package/locales/nl.po +94 -23
- package/locales/pl.po +98 -23
- package/locales/pt_BR.po +97 -23
- package/locales/zh_CN.po +98 -23
- package/locales/zh_TW.po +98 -23
- package/package.json +1 -1
- package/widgets/cockpit-exports/index.d.ts +6 -0
- package/widgets/cockpit-exports/index.d.ts.map +1 -1
- package/widgets/definitions/datapoints-list/index.d.ts +51 -0
- package/widgets/definitions/datapoints-list/index.d.ts.map +1 -0
- package/widgets/definitions/index.d.ts +1 -0
- package/widgets/definitions/index.d.ts.map +1 -1
- package/widgets/device-management-exports/index.d.ts +6 -0
- package/widgets/device-management-exports/index.d.ts.map +1 -1
- package/widgets/exports/index.d.ts +8 -1
- package/widgets/exports/index.d.ts.map +1 -1
- package/widgets/implementations/alarms/index.d.ts +2 -0
- package/widgets/implementations/alarms/index.d.ts.map +1 -1
- package/widgets/implementations/datapoints-list/index.d.ts +286 -0
- package/widgets/implementations/datapoints-list/index.d.ts.map +1 -0
- package/widgets/implementations/datapoints-table/index.d.ts +5 -66
- package/widgets/implementations/datapoints-table/index.d.ts.map +1 -1
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { NgTemplateOutlet, DecimalPipe, AsyncPipe } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { Injectable, inject, input, linkedSignal, signal, computed, ChangeDetectionStrategy, Component, viewChild, DestroyRef } from '@angular/core';
|
|
4
|
+
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { Router } from '@angular/router';
|
|
6
|
+
import { MeasurementService, InventoryService } from '@c8y/client';
|
|
7
|
+
import * as i1 from '@c8y/ngx-components';
|
|
8
|
+
import { AlertService, DashboardChildComponent, GroupService, DynamicComponentAlertAggregator, DynamicComponentAlert, C8yTranslateDirective, DeviceStatusComponent, DynamicComponentModule, EmptyStateComponent, ForOfDirective, GuideDocsComponent, GuideHrefDirective, IconDirective, ListGroupModule, LoadingComponent, VirtualScrollListenerDirective, WidgetActionWrapperComponent, ApplyRangeClassPipe, C8yTranslatePipe, DatePipe, FormGroupComponent } from '@c8y/ngx-components';
|
|
9
|
+
import { DatapointsExportSelectorComponent } from '@c8y/ngx-components/datapoints-export-selector';
|
|
10
|
+
import { gettext } from '@c8y/ngx-components/gettext';
|
|
11
|
+
import { WidgetConfigMigrationService, CONTEXT_FEATURE, GLOBAL_CONTEXT_DISPLAY_MODE, PRESET_NAME, REFRESH_OPTION, GlobalContextConnectorComponent, LocalControlsComponent } from '@c8y/ngx-components/global-context';
|
|
12
|
+
import { merge, isEqual } from 'lodash-es';
|
|
13
|
+
import { pairwise, filter, debounceTime, take, distinctUntilChanged } from 'rxjs';
|
|
14
|
+
import * as i1$1 from '@angular/cdk/drag-drop';
|
|
15
|
+
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
16
|
+
import * as i3 from '@angular/forms';
|
|
17
|
+
import { NgForm, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
18
|
+
import { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';
|
|
19
|
+
import * as i4 from 'ngx-bootstrap/popover';
|
|
20
|
+
import { PopoverModule } from 'ngx-bootstrap/popover';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default column configuration for datapoints list widget
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_DATAPOINTS_LIST_COLUMNS = [
|
|
26
|
+
{ id: 'kpi', label: gettext('Label'), visible: true, order: 0 },
|
|
27
|
+
{ id: 'target', label: gettext('Target'), visible: true, order: 1 },
|
|
28
|
+
{ id: 'current', label: gettext('Current'), visible: true, order: 2 },
|
|
29
|
+
{ id: 'diff', label: gettext('Diff'), visible: true, order: 3 },
|
|
30
|
+
{ id: 'diffPercentage', label: gettext('Diff %'), visible: true, order: 4 },
|
|
31
|
+
{ id: 'asset', label: gettext('Asset'), visible: true, order: 5 }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
class DatapointsListService {
|
|
35
|
+
/**
|
|
36
|
+
* Calculate difference between current value and target
|
|
37
|
+
* @param datapoint - Datapoint record
|
|
38
|
+
* @returns Difference value or null if value/target is undefined
|
|
39
|
+
*/
|
|
40
|
+
diff(datapoint) {
|
|
41
|
+
const { currentValue, target } = datapoint;
|
|
42
|
+
// != checks both null and undefined
|
|
43
|
+
if (currentValue != null && target != null) {
|
|
44
|
+
return currentValue - target;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Calculate percentage difference between current value and target
|
|
50
|
+
* @param datapoint - Datapoint record
|
|
51
|
+
* @returns Percentage difference or null if target is undefined
|
|
52
|
+
*/
|
|
53
|
+
diffPercent(datapoint) {
|
|
54
|
+
const target = datapoint.target;
|
|
55
|
+
if (target !== null && target !== undefined) {
|
|
56
|
+
const _diff = this.diff(datapoint);
|
|
57
|
+
if (_diff !== null) {
|
|
58
|
+
// Intentionally allows division by zero to return Infinity
|
|
59
|
+
// when target is 0, representing mathematically undefined percentage.
|
|
60
|
+
// This follows the previous AngularJS implementation behavior.
|
|
61
|
+
return (_diff / target) * 100;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get fraction size format based on whether the value is an integer
|
|
68
|
+
* @param value - Number to check
|
|
69
|
+
* @param defaultFractionSize - Default fraction size format to use for non-integers
|
|
70
|
+
* @returns Fraction size format ('1.0-0' for integers, defaultFractionSize for decimals)
|
|
71
|
+
*/
|
|
72
|
+
getFractionSize(value, defaultFractionSize) {
|
|
73
|
+
if (value === null || value === undefined) {
|
|
74
|
+
return defaultFractionSize;
|
|
75
|
+
}
|
|
76
|
+
return value % 1 === 0 ? '1.0-0' : defaultFractionSize;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extract current value and timestamp from a measurement
|
|
80
|
+
* @param datapoint - Datapoint configuration (contains fragment and series)
|
|
81
|
+
* @param measurement - Measurement to extract value from
|
|
82
|
+
* @returns Object containing extracted value and timestamp (null if not found)
|
|
83
|
+
*/
|
|
84
|
+
extractMeasurementValue(datapoint, measurement) {
|
|
85
|
+
// Return null values if measurement is missing
|
|
86
|
+
if (!measurement) {
|
|
87
|
+
return { value: null, timestamp: null };
|
|
88
|
+
}
|
|
89
|
+
// Return null values if datapoint configuration is incomplete
|
|
90
|
+
if (!datapoint.fragment || !datapoint.series) {
|
|
91
|
+
return { value: null, timestamp: null };
|
|
92
|
+
}
|
|
93
|
+
// Access the fragment (e.g., "c8y_Steam")
|
|
94
|
+
const fragmentData = measurement[datapoint.fragment];
|
|
95
|
+
if (!fragmentData) {
|
|
96
|
+
return { value: null, timestamp: null };
|
|
97
|
+
}
|
|
98
|
+
// Access the series within the fragment (e.g., "Temperature")
|
|
99
|
+
const seriesData = fragmentData[datapoint.series];
|
|
100
|
+
if (!seriesData || seriesData.value === undefined) {
|
|
101
|
+
return { value: null, timestamp: null };
|
|
102
|
+
}
|
|
103
|
+
// Extract and return the value and timestamp
|
|
104
|
+
return {
|
|
105
|
+
value: seriesData.value,
|
|
106
|
+
timestamp: new Date(measurement.time)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
110
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListService, providedIn: 'root' }); }
|
|
111
|
+
}
|
|
112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListService, decorators: [{
|
|
113
|
+
type: Injectable,
|
|
114
|
+
args: [{
|
|
115
|
+
providedIn: 'root'
|
|
116
|
+
}]
|
|
117
|
+
}] });
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Service that handles all data-fetching operations for the datapoints list widget.
|
|
121
|
+
* Encapsulates measurement fetching, data enrichment, and error handling.
|
|
122
|
+
*/
|
|
123
|
+
class DatapointsListFetchService {
|
|
124
|
+
constructor() {
|
|
125
|
+
this.alertService = inject(AlertService);
|
|
126
|
+
this.measurementService = inject(MeasurementService);
|
|
127
|
+
this.inventoryService = inject(InventoryService);
|
|
128
|
+
this.datapointsListService = inject(DatapointsListService);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Fetch measurements for all active datapoints and enrich them with calculated values.
|
|
132
|
+
*/
|
|
133
|
+
async fetchDatapointsWithMeasurements(datapoints, config) {
|
|
134
|
+
const targetManagedObjects = new Map();
|
|
135
|
+
const results = await this.fetchAllDatapoints(datapoints, config);
|
|
136
|
+
const enrichedDataPoints = results.map(r => r.datapoint);
|
|
137
|
+
const seriesWithoutPermissionCount = results.filter(r => r.hasPermissionError).length;
|
|
138
|
+
await this.fetchTargetManagedObjects(enrichedDataPoints, targetManagedObjects);
|
|
139
|
+
return {
|
|
140
|
+
dataPoints: enrichedDataPoints,
|
|
141
|
+
seriesWithoutPermissionCount,
|
|
142
|
+
targetManagedObjects
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Fetch and enrich all datapoints in parallel.
|
|
147
|
+
* Each datapoint fetch is independent - one failure doesn't affect others.
|
|
148
|
+
*/
|
|
149
|
+
fetchAllDatapoints(datapoints, config) {
|
|
150
|
+
return Promise.all(datapoints.map((datapoint, index) => this.fetchSingleDatapoint(datapoint, index, config)));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Fetch measurement for a single datapoint and return enriched result.
|
|
154
|
+
* Handles errors gracefully - returns empty datapoint on failure.
|
|
155
|
+
*/
|
|
156
|
+
async fetchSingleDatapoint(datapoint, index, config) {
|
|
157
|
+
try {
|
|
158
|
+
const measurement = await this.getMeasurementForDatapoint(datapoint, config);
|
|
159
|
+
const measurementValue = this.datapointsListService.extractMeasurementValue(datapoint, measurement);
|
|
160
|
+
return {
|
|
161
|
+
datapoint: this.createEnrichedDatapoint(datapoint, index, measurementValue, config),
|
|
162
|
+
hasPermissionError: false
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return this.handleFetchError(error, datapoint, index, config);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle fetch error and return appropriate result.
|
|
171
|
+
*/
|
|
172
|
+
handleFetchError(error, datapoint, index, config) {
|
|
173
|
+
const isPermissionError = error?.status === 403;
|
|
174
|
+
if (!isPermissionError) {
|
|
175
|
+
this.alertService.addServerFailure(error);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
datapoint: this.createEnrichedDatapoint(datapoint, index, { value: null, timestamp: null }, config),
|
|
179
|
+
hasPermissionError: isPermissionError
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Create an enriched datapoint with measurement value and all calculated fields.
|
|
184
|
+
*/
|
|
185
|
+
createEnrichedDatapoint(datapoint, index, measurementValue, config) {
|
|
186
|
+
const enrichedDatapoint = {
|
|
187
|
+
...datapoint,
|
|
188
|
+
id: this.getDatapointId(datapoint, index),
|
|
189
|
+
currentValue: measurementValue.value,
|
|
190
|
+
timestamp: measurementValue.timestamp
|
|
191
|
+
};
|
|
192
|
+
this.calculateDerivedFields(enrichedDatapoint, config.fractionSize);
|
|
193
|
+
return enrichedDatapoint;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Calculate and set all derived fields on a datapoint.
|
|
197
|
+
* Includes diff, diffPercent, and fraction sizes for display formatting.
|
|
198
|
+
*/
|
|
199
|
+
calculateDerivedFields(datapoint, fractionSize) {
|
|
200
|
+
datapoint.diffValue = this.datapointsListService.diff(datapoint);
|
|
201
|
+
datapoint.diffPercentValue = this.datapointsListService.diffPercent(datapoint);
|
|
202
|
+
datapoint.currentFractionSize = this.datapointsListService.getFractionSize(datapoint.currentValue, fractionSize);
|
|
203
|
+
datapoint.diffFractionSize = this.datapointsListService.getFractionSize(datapoint.diffValue, fractionSize);
|
|
204
|
+
datapoint.diffPercentFractionSize = this.datapointsListService.getFractionSize(datapoint.diffPercentValue, fractionSize);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Fetch managed objects for device status display.
|
|
208
|
+
*/
|
|
209
|
+
async fetchTargetManagedObjects(dataPoints, targetManagedObjects) {
|
|
210
|
+
const uniqueTargetIds = this.getUniqueTargetIds(dataPoints);
|
|
211
|
+
if (uniqueTargetIds.size === 0) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await Promise.all(Array.from(uniqueTargetIds).map(targetId => this.fetchManagedObject(targetId, targetManagedObjects)));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get unique target IDs from datapoints.
|
|
218
|
+
*/
|
|
219
|
+
getUniqueTargetIds(dataPoints) {
|
|
220
|
+
const uniqueTargetIds = new Set();
|
|
221
|
+
for (const dp of dataPoints) {
|
|
222
|
+
if (dp.__target?.id) {
|
|
223
|
+
uniqueTargetIds.add(dp.__target.id.toString());
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return uniqueTargetIds;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Fetch a single managed object and add to the map.
|
|
230
|
+
*/
|
|
231
|
+
async fetchManagedObject(targetId, targetManagedObjects) {
|
|
232
|
+
try {
|
|
233
|
+
const { data } = await this.inventoryService.detail(targetId);
|
|
234
|
+
targetManagedObjects.set(targetId, data);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.alertService.addServerFailure(error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Fetch the most recent measurement for a datapoint within the specified date range.
|
|
242
|
+
*/
|
|
243
|
+
async getMeasurementForDatapoint(datapoint, config) {
|
|
244
|
+
const sourceId = datapoint.__target?.id;
|
|
245
|
+
if (!sourceId || !datapoint.fragment || !datapoint.series) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const filter = {
|
|
249
|
+
dateFrom: config.dateFrom || '1970-01-01',
|
|
250
|
+
dateTo: config.dateTo || new Date().toISOString(),
|
|
251
|
+
source: sourceId,
|
|
252
|
+
valueFragmentSeries: datapoint.series,
|
|
253
|
+
valueFragmentType: datapoint.fragment,
|
|
254
|
+
pageSize: 1,
|
|
255
|
+
revert: true
|
|
256
|
+
};
|
|
257
|
+
const { data } = await this.measurementService.list(filter);
|
|
258
|
+
return data?.[0] ?? null;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get unique ID for a datapoint.
|
|
262
|
+
* Uses target ID if available, otherwise generates fallback based on index.
|
|
263
|
+
*/
|
|
264
|
+
getDatapointId(datapoint, fallbackIndex) {
|
|
265
|
+
return datapoint.__target?.id?.toString() || `dp-${fallbackIndex}`;
|
|
266
|
+
}
|
|
267
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListFetchService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
268
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListFetchService, providedIn: 'root' }); }
|
|
269
|
+
}
|
|
270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListFetchService, decorators: [{
|
|
271
|
+
type: Injectable,
|
|
272
|
+
args: [{
|
|
273
|
+
providedIn: 'root'
|
|
274
|
+
}]
|
|
275
|
+
}] });
|
|
276
|
+
|
|
277
|
+
class DatapointsListViewComponent {
|
|
278
|
+
constructor() {
|
|
279
|
+
this.config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
280
|
+
this.isInPreviewMode = input(false, ...(ngDevMode ? [{ debugName: "isInPreviewMode" }] : []));
|
|
281
|
+
this.alertService = inject(AlertService);
|
|
282
|
+
this.dashboardChild = inject(DashboardChildComponent, { optional: true });
|
|
283
|
+
this.defaultColumns = DEFAULT_DATAPOINTS_LIST_COLUMNS;
|
|
284
|
+
this.fetchService = inject(DatapointsListFetchService);
|
|
285
|
+
this.groupService = inject(GroupService);
|
|
286
|
+
this.inventoryService = inject(InventoryService);
|
|
287
|
+
this.router = inject(Router);
|
|
288
|
+
this.widgetConfigMigrationService = inject(WidgetConfigMigrationService);
|
|
289
|
+
this.CONTEXT_FEATURE = CONTEXT_FEATURE;
|
|
290
|
+
this.GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE;
|
|
291
|
+
this.missingAllPermissionsAlert = new DynamicComponentAlertAggregator();
|
|
292
|
+
this.targetManagedObjects = new Map();
|
|
293
|
+
this.configSignal = linkedSignal(() => {
|
|
294
|
+
const raw = this.config();
|
|
295
|
+
const migrated = this.widgetConfigMigrationService.migrateWidgetConfig(raw);
|
|
296
|
+
return merge({}, raw, migrated);
|
|
297
|
+
}, ...(ngDevMode ? [{ debugName: "configSignal" }] : []));
|
|
298
|
+
this.contextConfig = signal({}, ...(ngDevMode ? [{ debugName: "contextConfig" }] : []));
|
|
299
|
+
this.dataPoints = signal([], ...(ngDevMode ? [{ debugName: "dataPoints" }] : []));
|
|
300
|
+
this.displayMode = signal(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD, ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
|
|
301
|
+
this.hasLoadedOnce = signal(false, ...(ngDevMode ? [{ debugName: "hasLoadedOnce" }] : []));
|
|
302
|
+
this.hasNoPermissionsToReadAnyMeasurement = signal(false, ...(ngDevMode ? [{ debugName: "hasNoPermissionsToReadAnyMeasurement" }] : []));
|
|
303
|
+
this.isLinkedToGlobal = signal(undefined, ...(ngDevMode ? [{ debugName: "isLinkedToGlobal" }] : []));
|
|
304
|
+
this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
305
|
+
this.widgetControls = signal(PRESET_NAME.DATA_POINTS_LIST, ...(ngDevMode ? [{ debugName: "widgetControls" }] : []));
|
|
306
|
+
this.fractionSize = computed(() => {
|
|
307
|
+
const decimalPlaces = this.configSignal()?.decimalPlaces;
|
|
308
|
+
if (typeof decimalPlaces === 'number' && !Number.isNaN(decimalPlaces)) {
|
|
309
|
+
return `1.${decimalPlaces}-${decimalPlaces}`;
|
|
310
|
+
}
|
|
311
|
+
return '1.2-2';
|
|
312
|
+
}, ...(ngDevMode ? [{ debugName: "fractionSize" }] : []));
|
|
313
|
+
this.columns = computed(() => {
|
|
314
|
+
const options = this.configSignal()?.options;
|
|
315
|
+
if (options?.columns?.length > 0) {
|
|
316
|
+
return [...options.columns].sort((a, b) => a.order - b.order);
|
|
317
|
+
}
|
|
318
|
+
return this.defaultColumns.map(defaultCol => ({
|
|
319
|
+
...defaultCol,
|
|
320
|
+
visible: options?.[defaultCol.id] ?? defaultCol.visible
|
|
321
|
+
}));
|
|
322
|
+
}, ...(ngDevMode ? [{ debugName: "columns" }] : []));
|
|
323
|
+
this.visibleColumns = computed(() => {
|
|
324
|
+
return this.columns().filter(col => col.visible !== false);
|
|
325
|
+
}, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : []));
|
|
326
|
+
this.exportConfig = computed(() => {
|
|
327
|
+
const effectiveConfig = { ...this.configSignal(), ...this.contextConfig() };
|
|
328
|
+
const dateFrom = effectiveConfig.dateTimeContext?.dateFrom;
|
|
329
|
+
const dateTo = effectiveConfig.dateTimeContext?.dateTo;
|
|
330
|
+
if (!dateFrom || !dateTo || (this.isLoading() && !this.hasLoadedOnce())) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
exportType: 'latestWithDetails',
|
|
335
|
+
datapointDetails: effectiveConfig.datapoints
|
|
336
|
+
?.filter(dp => dp.__active === true)
|
|
337
|
+
.map(dp => ({
|
|
338
|
+
deviceName: dp.__target?.name || '',
|
|
339
|
+
source: dp.__target?.id || '',
|
|
340
|
+
valueFragmentSeries: dp.series,
|
|
341
|
+
valueFragmentType: dp.fragment,
|
|
342
|
+
target: dp.target,
|
|
343
|
+
label: dp.label
|
|
344
|
+
})) || [],
|
|
345
|
+
dateFrom: dateFrom instanceof Date ? dateFrom.toISOString() : dateFrom,
|
|
346
|
+
dateTo: dateTo instanceof Date ? dateTo.toISOString() : dateTo,
|
|
347
|
+
columns: this.columns()
|
|
348
|
+
.filter(col => col.visible)
|
|
349
|
+
.map(col => ({
|
|
350
|
+
id: col.id,
|
|
351
|
+
label: col.label,
|
|
352
|
+
visible: col.visible,
|
|
353
|
+
order: col.order
|
|
354
|
+
}))
|
|
355
|
+
};
|
|
356
|
+
}, ...(ngDevMode ? [{ debugName: "exportConfig" }] : []));
|
|
357
|
+
this.activeDataPoints = computed(() => {
|
|
358
|
+
return this.configSignal()?.datapoints?.filter(dp => dp.__active === true) ?? [];
|
|
359
|
+
}, ...(ngDevMode ? [{ debugName: "activeDataPoints" }] : []));
|
|
360
|
+
this.loadRequestId = 0;
|
|
361
|
+
this.seriesWithoutPermissionToReadCount = signal(0, ...(ngDevMode ? [{ debugName: "seriesWithoutPermissionToReadCount" }] : []));
|
|
362
|
+
toObservable(this.config)
|
|
363
|
+
.pipe(pairwise(), takeUntilDestroyed())
|
|
364
|
+
.subscribe(([prevConfig, currentConfig]) => {
|
|
365
|
+
if (!this.isInPreviewMode()) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const prevContext = this.extractContextState(prevConfig);
|
|
369
|
+
const newContext = this.extractContextState(currentConfig);
|
|
370
|
+
this.contextConfig.set(newContext);
|
|
371
|
+
const datapointsChanged = !isEqual(prevConfig.datapoints, currentConfig.datapoints);
|
|
372
|
+
const decimalPlacesChanged = prevConfig.decimalPlaces !== currentConfig.decimalPlaces;
|
|
373
|
+
const contextChanged = !isEqual(prevContext, newContext);
|
|
374
|
+
if (datapointsChanged || decimalPlacesChanged || contextChanged) {
|
|
375
|
+
this.isLoading.set(true);
|
|
376
|
+
this.loadDatapoints();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
ngOnInit() {
|
|
381
|
+
const config = this.configSignal();
|
|
382
|
+
const displayMode = config.displayMode || GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD;
|
|
383
|
+
this.displayMode.set(displayMode);
|
|
384
|
+
this.contextConfig.set(this.extractContextState(config));
|
|
385
|
+
if (displayMode !== GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD || this.isInPreviewMode()) {
|
|
386
|
+
this.loadDatapoints();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
onContextChange(event) {
|
|
390
|
+
const { diff, context } = event;
|
|
391
|
+
if (diff.isAutoRefreshEnabled === false &&
|
|
392
|
+
Object.keys(diff).length === 1 &&
|
|
393
|
+
context.refreshOption === REFRESH_OPTION.LIVE) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
this.contextConfig.set(context);
|
|
397
|
+
this.isLoading.set(true);
|
|
398
|
+
this.loadDatapoints();
|
|
399
|
+
}
|
|
400
|
+
onRefresh() {
|
|
401
|
+
this.isLoading.set(true);
|
|
402
|
+
this.loadDatapoints();
|
|
403
|
+
}
|
|
404
|
+
onExportModalOpen(isOpened) {
|
|
405
|
+
this.setAutoRefreshPaused(isOpened);
|
|
406
|
+
}
|
|
407
|
+
async redirectToAsset(assetId) {
|
|
408
|
+
if (this.isInPreviewMode() || !assetId) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const { data: mo } = await this.inventoryService.detail(assetId);
|
|
413
|
+
if (mo) {
|
|
414
|
+
const assetPath = this.groupService.getAssetPath(mo);
|
|
415
|
+
this.router.navigateByUrl(`/${assetPath}/${mo.id}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
this.alertService.addServerFailure(error);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
getTargetManagedObject(targetId) {
|
|
423
|
+
return this.targetManagedObjects.get(targetId.toString());
|
|
424
|
+
}
|
|
425
|
+
getDashboardChild() {
|
|
426
|
+
return this.dashboardChild ?? null;
|
|
427
|
+
}
|
|
428
|
+
getRangeValues(dp) {
|
|
429
|
+
return {
|
|
430
|
+
yellowRangeMin: dp.yellowRangeMin,
|
|
431
|
+
yellowRangeMax: dp.yellowRangeMax,
|
|
432
|
+
redRangeMin: dp.redRangeMin,
|
|
433
|
+
redRangeMax: dp.redRangeMax
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
onListScrolled() {
|
|
437
|
+
this.setAutoRefreshPaused(true);
|
|
438
|
+
}
|
|
439
|
+
onListScrolledToTop() {
|
|
440
|
+
this.setAutoRefreshPaused(false);
|
|
441
|
+
}
|
|
442
|
+
extractContextState(config) {
|
|
443
|
+
return {
|
|
444
|
+
dateTimeContext: config.dateTimeContext,
|
|
445
|
+
aggregation: config.aggregation,
|
|
446
|
+
isAutoRefreshEnabled: config.isAutoRefreshEnabled,
|
|
447
|
+
refreshInterval: config.refreshInterval,
|
|
448
|
+
refreshOption: config.refreshOption
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
setAutoRefreshPaused(paused) {
|
|
452
|
+
if (this.isInPreviewMode()) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const current = this.contextConfig();
|
|
456
|
+
if (current.refreshOption === REFRESH_OPTION.HISTORY) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
this.contextConfig.set({
|
|
460
|
+
...current,
|
|
461
|
+
isAutoRefreshEnabled: !paused
|
|
462
|
+
});
|
|
463
|
+
this.isLinkedToGlobal.set(!paused);
|
|
464
|
+
}
|
|
465
|
+
loadDatapoints() {
|
|
466
|
+
if (this.activeDataPoints().length > 0) {
|
|
467
|
+
this.fetchMeasurements();
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
this.dataPoints.set([]);
|
|
471
|
+
this.hasLoadedOnce.set(true);
|
|
472
|
+
this.isLoading.set(false);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async fetchMeasurements() {
|
|
476
|
+
const requestId = ++this.loadRequestId;
|
|
477
|
+
try {
|
|
478
|
+
this.isLoading.set(true);
|
|
479
|
+
const effectiveConfig = {
|
|
480
|
+
...this.configSignal(),
|
|
481
|
+
...this.contextConfig()
|
|
482
|
+
};
|
|
483
|
+
const dateFrom = effectiveConfig.dateTimeContext?.dateFrom;
|
|
484
|
+
const dateTo = effectiveConfig.dateTimeContext?.dateTo;
|
|
485
|
+
const dateFromStr = dateFrom instanceof Date ? dateFrom.toISOString() : dateFrom;
|
|
486
|
+
const dateToStr = dateTo instanceof Date ? dateTo.toISOString() : dateTo;
|
|
487
|
+
const result = await this.fetchService.fetchDatapointsWithMeasurements(this.activeDataPoints(), { fractionSize: this.fractionSize(), dateFrom: dateFromStr, dateTo: dateToStr });
|
|
488
|
+
if (requestId === this.loadRequestId) {
|
|
489
|
+
this.dataPoints.set(result.dataPoints);
|
|
490
|
+
this.targetManagedObjects = result.targetManagedObjects;
|
|
491
|
+
this.hasLoadedOnce.set(true);
|
|
492
|
+
this.seriesWithoutPermissionToReadCount.set(result.seriesWithoutPermissionCount);
|
|
493
|
+
this.checkAndDisplayPermissionErrors();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
finally {
|
|
497
|
+
if (requestId === this.loadRequestId) {
|
|
498
|
+
this.isLoading.set(false);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
checkAndDisplayPermissionErrors() {
|
|
503
|
+
if (this.seriesWithoutPermissionToReadCount()) {
|
|
504
|
+
this.missingAllPermissionsAlert.clear();
|
|
505
|
+
this.handleNoPermissionErrorMessage();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
handleNoPermissionErrorMessage() {
|
|
509
|
+
const noPermissions = this.seriesWithoutPermissionToReadCount() === this.activeDataPoints().length;
|
|
510
|
+
this.hasNoPermissionsToReadAnyMeasurement.set(noPermissions);
|
|
511
|
+
if (noPermissions) {
|
|
512
|
+
this.showMessageForMissingPermissionsForAllSeries();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
showMessageForMissingPermissionsForAllSeries() {
|
|
516
|
+
this.missingAllPermissionsAlert.addAlerts(new DynamicComponentAlert({
|
|
517
|
+
allowHtml: true,
|
|
518
|
+
text: gettext(`<p>To view data, you must meet at least one of these criteria:</p>
|
|
519
|
+
<ul>
|
|
520
|
+
<li>
|
|
521
|
+
Have
|
|
522
|
+
<b>READ permission for "Measurements" permission type</b>
|
|
523
|
+
(either as a global role or for the specific source)
|
|
524
|
+
</li>
|
|
525
|
+
<li>
|
|
526
|
+
Be the
|
|
527
|
+
<b>owner of the source</b>
|
|
528
|
+
you want to export data from
|
|
529
|
+
</li>
|
|
530
|
+
</ul>
|
|
531
|
+
<p>Don't meet these requirements? Contact your system administrator for assistance.</p>`),
|
|
532
|
+
type: 'system'
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
536
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: DatapointsListViewComponent, isStandalone: true, selector: "c8y-datapoints-list", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, isInPreviewMode: { classPropertyName: "isInPreviewMode", publicName: "isInPreviewMode", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-col fit-h" }, ngImport: i0, template: "@if (!isInPreviewMode()) {\n <div class=\"d-flex gap-16 p-r-16 inner-scroll h-auto min-width-0\">\n @if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {\n <c8y-global-context-connector\n [controls]=\"widgetControls()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading()\"\n [dashboardChild]=\"getDashboardChild()!\"\n [linked]=\"isLinkedToGlobal()\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-global-context-connector>\n } @else {\n <c8y-local-controls\n [controls]=\"widgetControls()\"\n [displayMode]=\"displayMode()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading()\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-local-controls>\n }\n </div>\n}\n\n@if (!isInPreviewMode() && exportConfig(); as config) {\n <c8y-widget-action>\n <c8y-datapoints-export-selector\n [displayMode]=\"'icon-only'\"\n [exportConfig]=\"config\"\n [containerClass]=\"'d-contents'\"\n (isOpen)=\"onExportModalOpen($event)\"\n ></c8y-datapoints-export-selector>\n </c8y-widget-action>\n}\n\n@if (!hasNoPermissionsToReadAnyMeasurement()) {\n <!-- the .page-sticky-header -->\n @if (dataPoints().length > 0) {\n <div class=\"hidden-xs hidden-sm c8y-list__item\">\n <div class=\"c8y-list__item__block flex-grow min-width-0\">\n <div class=\"c8y-list__item__icon\">\n <i style=\"width: 22px\"></i>\n </div>\n <div class=\"c8y-list__item__body\">\n <div class=\"d-flex-md row\">\n @for (column of visibleColumns(); track column.id) {\n <div\n [class]=\"\n column.id === 'kpi' || column.id === 'asset'\n ? 'col-md-3 flex-grow min-width-0'\n : column.id == 'diff' || column.id == 'diffPercentage'\n ? 'col-md-1 flex-grow min-width-0'\n : 'col-md-2 flex-grow min-width-0'\n \"\n [class.text-right]=\"column.id !== 'asset' && column.id !== 'kpi'\"\n >\n <span\n class=\"text-medium text-truncate\"\n translate\n >\n {{ column.label }}\n </span>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <!-- The record list -->\n @if (isLoading() && !hasLoadedOnce()) {\n <!-- Initial load: full spinner -->\n <ng-container [ngTemplateOutlet]=\"loading\"></ng-container>\n } @else {\n @if (isLoading()) {\n <!-- Refresh: inline loading overlay -->\n <div class=\"p-absolute fit-w overflow-hidden p-b-4\">\n <c8y-loading [layout]=\"'page'\"></c8y-loading>\n </div>\n }\n @if (dataPoints().length) {\n <c8y-list-group\n class=\"flex-grow\"\n role=\"list\"\n c8yVirtualScrollListener\n (scrolled)=\"onListScrolled()\"\n (scrolledToTop)=\"onListScrolledToTop()\"\n >\n <c8y-li\n role=\"listitem\"\n *c8yFor=\"\n let dp of { data: dataPoints(), res: null! };\n enableVirtualScroll: true;\n virtualScrollElementSize: 48;\n virtualScrollStrategy: 'fixed'\n \"\n >\n <c8y-li-icon>\n <i\n c8yIcon=\"circle\"\n [style.color]=\"dp.color\"\n ></i>\n </c8y-li-icon>\n <c8y-li-body>\n <div class=\"d-flex-md row\">\n @for (column of visibleColumns(); track column.id) {\n @switch (column.id) {\n @case ('kpi') {\n <div\n class=\"col-md-3 flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n {{ dp.label | translate }}\n @if (dp.unit) {\n <small class=\"text-muted\">{{ dp.unit }}</small>\n }\n </div>\n </div>\n }\n @case ('target') {\n <div\n class=\"col-md-2 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>{{ dp.target }}</span>\n </div>\n </div>\n }\n @case ('current') {\n <div\n class=\"col-md-2 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span\n [class]=\"dp.currentValue | applyRangeClass: getRangeValues(dp)\"\n [title]=\"dp.timestamp | c8yDate: 'medium'\"\n [attr.aria-label]=\"\n ('Last updated: ' | translate) + (dp.timestamp | c8yDate: 'medium')\n \"\n >\n {{ dp.currentValue | number: dp.currentFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('diff') {\n <div\n class=\"col-md-1 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>\n {{ dp.diffValue | number: dp.diffFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('diffPercentage') {\n <div\n class=\"col-md-1 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>\n {{ dp.diffPercentValue | number: dp.diffPercentFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('asset') {\n <div\n class=\"col-md-3 flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <button\n class=\"btn-clean d-flex a-i-center gap-4 text-muted\"\n [attr.aria-label]=\"('Navigate to ' | translate) + dp.__target?.name\"\n type=\"button\"\n (click)=\"redirectToAsset(dp.__target?.id)\"\n >\n @if (dp.__target?.id && getTargetManagedObject(dp.__target.id); as mo) {\n <c8y-device-status\n [mo]=\"mo\"\n [size]=\"16\"\n ></c8y-device-status>\n }\n <small\n class=\"text-truncate\"\n [title]=\"dp.__target?.name\"\n >{{ dp.__target?.name }}</small\n >\n </button>\n </div>\n </div>\n }\n }\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n } @else {\n <ng-container [ngTemplateOutlet]=\"emptyState\"></ng-container>\n }\n }\n} @else {\n <div class=\"p-t-24 p-r-16 p-l-16 p-b-16 d-flex\">\n <div class=\"center-block\">\n <c8y-dynamic-component-alerts\n [alerts]=\"missingAllPermissionsAlert\"\n ></c8y-dynamic-component-alerts>\n </div>\n </div>\n}\n\n<ng-template #loading>\n <c8y-loading></c8y-loading>\n</ng-template>\n\n<ng-template #emptyState>\n <div class=\"p-relative p-l-24\">\n <c8y-ui-empty-state\n [icon]=\"'c8y-alert-idle'\"\n [title]=\"'No data to display.' | translate\"\n [horizontal]=\"true\"\n data-cy=\"datapoints-list--empty-state\"\n >\n <p c8y-guide-docs>\n <small translate>\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#data-point-list\">\n user documentation\n </a>\n .\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: DatapointsExportSelectorComponent, selector: "c8y-datapoints-export-selector", inputs: ["displayMode", "containerClass", "exportConfig"], outputs: ["isOpen"] }, { kind: "component", type: DeviceStatusComponent, selector: "device-status, c8y-device-status", inputs: ["mo", "size"] }, { kind: "ngmodule", type: DynamicComponentModule }, { kind: "component", type: i1.DynamicComponentAlertsComponent, selector: "c8y-dynamic-component-alerts", inputs: ["alerts"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: GlobalContextConnectorComponent, selector: "c8y-global-context-connector", inputs: ["controls", "config", "isLoading", "dashboardChild", "linked", "emitRefresh"], outputs: ["configChange", "refresh", "linkedChange"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: ListGroupModule }, { kind: "component", type: i1.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i1.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i1.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: LocalControlsComponent, selector: "c8y-local-controls", inputs: ["controls", "displayMode", "config", "isLoading", "disabled"], outputs: ["configChange", "refresh"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: VirtualScrollListenerDirective, selector: "[c8yVirtualScrollListener]", inputs: ["scrollThreshold"], outputs: ["scrolled", "scrolledToTop"] }, { kind: "component", type: WidgetActionWrapperComponent, selector: "c8y-widget-action" }, { kind: "pipe", type: ApplyRangeClassPipe, name: "applyRangeClass" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
537
|
+
}
|
|
538
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListViewComponent, decorators: [{
|
|
539
|
+
type: Component,
|
|
540
|
+
args: [{ selector: 'c8y-datapoints-list', host: { class: 'd-col fit-h' }, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
541
|
+
ApplyRangeClassPipe,
|
|
542
|
+
C8yTranslateDirective,
|
|
543
|
+
C8yTranslatePipe,
|
|
544
|
+
DatapointsExportSelectorComponent,
|
|
545
|
+
DatePipe,
|
|
546
|
+
DecimalPipe,
|
|
547
|
+
DeviceStatusComponent,
|
|
548
|
+
DynamicComponentModule,
|
|
549
|
+
EmptyStateComponent,
|
|
550
|
+
ForOfDirective,
|
|
551
|
+
GlobalContextConnectorComponent,
|
|
552
|
+
GuideDocsComponent,
|
|
553
|
+
GuideHrefDirective,
|
|
554
|
+
IconDirective,
|
|
555
|
+
ListGroupModule,
|
|
556
|
+
LoadingComponent,
|
|
557
|
+
LocalControlsComponent,
|
|
558
|
+
NgTemplateOutlet,
|
|
559
|
+
VirtualScrollListenerDirective,
|
|
560
|
+
WidgetActionWrapperComponent
|
|
561
|
+
], template: "@if (!isInPreviewMode()) {\n <div class=\"d-flex gap-16 p-r-16 inner-scroll h-auto min-width-0\">\n @if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {\n <c8y-global-context-connector\n [controls]=\"widgetControls()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading()\"\n [dashboardChild]=\"getDashboardChild()!\"\n [linked]=\"isLinkedToGlobal()\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-global-context-connector>\n } @else {\n <c8y-local-controls\n [controls]=\"widgetControls()\"\n [displayMode]=\"displayMode()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading()\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-local-controls>\n }\n </div>\n}\n\n@if (!isInPreviewMode() && exportConfig(); as config) {\n <c8y-widget-action>\n <c8y-datapoints-export-selector\n [displayMode]=\"'icon-only'\"\n [exportConfig]=\"config\"\n [containerClass]=\"'d-contents'\"\n (isOpen)=\"onExportModalOpen($event)\"\n ></c8y-datapoints-export-selector>\n </c8y-widget-action>\n}\n\n@if (!hasNoPermissionsToReadAnyMeasurement()) {\n <!-- the .page-sticky-header -->\n @if (dataPoints().length > 0) {\n <div class=\"hidden-xs hidden-sm c8y-list__item\">\n <div class=\"c8y-list__item__block flex-grow min-width-0\">\n <div class=\"c8y-list__item__icon\">\n <i style=\"width: 22px\"></i>\n </div>\n <div class=\"c8y-list__item__body\">\n <div class=\"d-flex-md row\">\n @for (column of visibleColumns(); track column.id) {\n <div\n [class]=\"\n column.id === 'kpi' || column.id === 'asset'\n ? 'col-md-3 flex-grow min-width-0'\n : column.id == 'diff' || column.id == 'diffPercentage'\n ? 'col-md-1 flex-grow min-width-0'\n : 'col-md-2 flex-grow min-width-0'\n \"\n [class.text-right]=\"column.id !== 'asset' && column.id !== 'kpi'\"\n >\n <span\n class=\"text-medium text-truncate\"\n translate\n >\n {{ column.label }}\n </span>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <!-- The record list -->\n @if (isLoading() && !hasLoadedOnce()) {\n <!-- Initial load: full spinner -->\n <ng-container [ngTemplateOutlet]=\"loading\"></ng-container>\n } @else {\n @if (isLoading()) {\n <!-- Refresh: inline loading overlay -->\n <div class=\"p-absolute fit-w overflow-hidden p-b-4\">\n <c8y-loading [layout]=\"'page'\"></c8y-loading>\n </div>\n }\n @if (dataPoints().length) {\n <c8y-list-group\n class=\"flex-grow\"\n role=\"list\"\n c8yVirtualScrollListener\n (scrolled)=\"onListScrolled()\"\n (scrolledToTop)=\"onListScrolledToTop()\"\n >\n <c8y-li\n role=\"listitem\"\n *c8yFor=\"\n let dp of { data: dataPoints(), res: null! };\n enableVirtualScroll: true;\n virtualScrollElementSize: 48;\n virtualScrollStrategy: 'fixed'\n \"\n >\n <c8y-li-icon>\n <i\n c8yIcon=\"circle\"\n [style.color]=\"dp.color\"\n ></i>\n </c8y-li-icon>\n <c8y-li-body>\n <div class=\"d-flex-md row\">\n @for (column of visibleColumns(); track column.id) {\n @switch (column.id) {\n @case ('kpi') {\n <div\n class=\"col-md-3 flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n {{ dp.label | translate }}\n @if (dp.unit) {\n <small class=\"text-muted\">{{ dp.unit }}</small>\n }\n </div>\n </div>\n }\n @case ('target') {\n <div\n class=\"col-md-2 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>{{ dp.target }}</span>\n </div>\n </div>\n }\n @case ('current') {\n <div\n class=\"col-md-2 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span\n [class]=\"dp.currentValue | applyRangeClass: getRangeValues(dp)\"\n [title]=\"dp.timestamp | c8yDate: 'medium'\"\n [attr.aria-label]=\"\n ('Last updated: ' | translate) + (dp.timestamp | c8yDate: 'medium')\n \"\n >\n {{ dp.currentValue | number: dp.currentFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('diff') {\n <div\n class=\"col-md-1 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>\n {{ dp.diffValue | number: dp.diffFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('diffPercentage') {\n <div\n class=\"col-md-1 text-right-md flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <span>\n {{ dp.diffPercentValue | number: dp.diffPercentFractionSize }}\n </span>\n </div>\n </div>\n }\n @case ('asset') {\n <div\n class=\"col-md-3 flex-grow\"\n [attr.data-cy]=\"'datapointlist-' + column.id\"\n >\n <div class=\"d-flex a-i-center d-contents-md p-t-4 p-b-4 separator-bottom\">\n <small\n class=\"text-label-small flex-grow visible-xs-inline-block visible-sm-inline-block\"\n >{{ column.label }}</small\n >\n <button\n class=\"btn-clean d-flex a-i-center gap-4 text-muted\"\n [attr.aria-label]=\"('Navigate to ' | translate) + dp.__target?.name\"\n type=\"button\"\n (click)=\"redirectToAsset(dp.__target?.id)\"\n >\n @if (dp.__target?.id && getTargetManagedObject(dp.__target.id); as mo) {\n <c8y-device-status\n [mo]=\"mo\"\n [size]=\"16\"\n ></c8y-device-status>\n }\n <small\n class=\"text-truncate\"\n [title]=\"dp.__target?.name\"\n >{{ dp.__target?.name }}</small\n >\n </button>\n </div>\n </div>\n }\n }\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n } @else {\n <ng-container [ngTemplateOutlet]=\"emptyState\"></ng-container>\n }\n }\n} @else {\n <div class=\"p-t-24 p-r-16 p-l-16 p-b-16 d-flex\">\n <div class=\"center-block\">\n <c8y-dynamic-component-alerts\n [alerts]=\"missingAllPermissionsAlert\"\n ></c8y-dynamic-component-alerts>\n </div>\n </div>\n}\n\n<ng-template #loading>\n <c8y-loading></c8y-loading>\n</ng-template>\n\n<ng-template #emptyState>\n <div class=\"p-relative p-l-24\">\n <c8y-ui-empty-state\n [icon]=\"'c8y-alert-idle'\"\n [title]=\"'No data to display.' | translate\"\n [horizontal]=\"true\"\n data-cy=\"datapoints-list--empty-state\"\n >\n <p c8y-guide-docs>\n <small translate>\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#data-point-list\">\n user documentation\n </a>\n .\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n</ng-template>\n" }]
|
|
562
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], isInPreviewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInPreviewMode", required: false }] }] } });
|
|
563
|
+
|
|
564
|
+
const DEFAULT_DECIMAL_PLACES = 2;
|
|
565
|
+
const MIN_DECIMAL_PLACES = 0;
|
|
566
|
+
const MAX_DECIMAL_PLACES = 10;
|
|
567
|
+
class DatapointsListConfigComponent {
|
|
568
|
+
constructor() {
|
|
569
|
+
this.config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
570
|
+
this.previewTemplate = viewChild('dataPointsListPreview', ...(ngDevMode ? [{ debugName: "previewTemplate" }] : []));
|
|
571
|
+
this.defaultColumns = DEFAULT_DATAPOINTS_LIST_COLUMNS;
|
|
572
|
+
this.destroyRef = inject(DestroyRef);
|
|
573
|
+
this.form = inject(NgForm);
|
|
574
|
+
this.formBuilder = inject(FormBuilder);
|
|
575
|
+
this.widgetConfigService = inject(WidgetConfigService);
|
|
576
|
+
this.controls = PRESET_NAME.DATA_POINTS_LIST;
|
|
577
|
+
this.minDecimalPlaces = MIN_DECIMAL_PLACES;
|
|
578
|
+
this.maxDecimalPlaces = MAX_DECIMAL_PLACES;
|
|
579
|
+
this.configForm = signal(undefined, ...(ngDevMode ? [{ debugName: "configForm" }] : []));
|
|
580
|
+
this.columnsFormArray = computed(() => this.configForm()?.get('columns'), ...(ngDevMode ? [{ debugName: "columnsFormArray" }] : []));
|
|
581
|
+
/**
|
|
582
|
+
* Debounced config for preview to prevent multiple series requests on initial load.
|
|
583
|
+
* Uses debounceTime to batch rapid emissions (e.g., from initConfig + GlobalContext processing).
|
|
584
|
+
*/
|
|
585
|
+
this.previewConfig$ = this.widgetConfigService.currentConfig$.pipe(filter(config => !!config?.dateTimeContext), debounceTime(300));
|
|
586
|
+
}
|
|
587
|
+
ngOnInit() {
|
|
588
|
+
this.widgetConfigService.currentConfig$
|
|
589
|
+
.pipe(filter(config => !!config?.dateTimeContext), take(1), takeUntilDestroyed(this.destroyRef))
|
|
590
|
+
.subscribe(() => this.initForm());
|
|
591
|
+
}
|
|
592
|
+
ngAfterViewInit() {
|
|
593
|
+
this.widgetConfigService.setPreview(this.previewTemplate() ?? null);
|
|
594
|
+
}
|
|
595
|
+
onColumnDrop(event) {
|
|
596
|
+
const columnsArray = this.columnsFormArray();
|
|
597
|
+
const item = columnsArray.at(event.previousIndex);
|
|
598
|
+
columnsArray.removeAt(event.previousIndex);
|
|
599
|
+
columnsArray.insert(event.currentIndex, item);
|
|
600
|
+
columnsArray.controls.forEach((control, index) => control.patchValue({ order: index }));
|
|
601
|
+
}
|
|
602
|
+
initForm() {
|
|
603
|
+
const form = this.createForm();
|
|
604
|
+
this.form.form.addControl('config', form);
|
|
605
|
+
form.patchValue(this.config(), { emitEvent: false });
|
|
606
|
+
this.pushFormToService(form.value);
|
|
607
|
+
form.valueChanges
|
|
608
|
+
.pipe(distinctUntilChanged((prev, curr) => isEqual(prev, curr)), debounceTime(300), takeUntilDestroyed(this.destroyRef))
|
|
609
|
+
.subscribe(formValue => {
|
|
610
|
+
if (form.invalid) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const { decimalPlaces } = formValue;
|
|
614
|
+
if (typeof decimalPlaces === 'number' &&
|
|
615
|
+
(decimalPlaces < MIN_DECIMAL_PLACES || decimalPlaces > MAX_DECIMAL_PLACES)) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.pushFormToService(formValue);
|
|
619
|
+
});
|
|
620
|
+
this.configForm.set(form);
|
|
621
|
+
}
|
|
622
|
+
pushFormToService(formValue) {
|
|
623
|
+
const formData = formValue;
|
|
624
|
+
const { columns, ...formValueWithoutColumns } = formData;
|
|
625
|
+
const currentOptions = this.widgetConfigService.currentConfig?.options;
|
|
626
|
+
this.widgetConfigService.updateConfig({
|
|
627
|
+
...formValueWithoutColumns,
|
|
628
|
+
options: {
|
|
629
|
+
...currentOptions,
|
|
630
|
+
columns: columns || []
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
createForm() {
|
|
635
|
+
const currentConfig = this.config();
|
|
636
|
+
const columns = this.migrateColumnsConfig(currentConfig);
|
|
637
|
+
return this.formBuilder.group({
|
|
638
|
+
decimalPlaces: [
|
|
639
|
+
currentConfig?.decimalPlaces ?? DEFAULT_DECIMAL_PLACES,
|
|
640
|
+
[
|
|
641
|
+
Validators.required,
|
|
642
|
+
Validators.min(MIN_DECIMAL_PLACES),
|
|
643
|
+
Validators.max(MAX_DECIMAL_PLACES),
|
|
644
|
+
Validators.pattern('^[0-9]+$')
|
|
645
|
+
]
|
|
646
|
+
],
|
|
647
|
+
columns: this.formBuilder.array(columns.map(col => this.createColumnFormGroup(col)), [Validators.required, Validators.minLength(1), this.minOneColumnVisible()])
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
createColumnFormGroup(column) {
|
|
651
|
+
return this.formBuilder.group({
|
|
652
|
+
id: [column.id, Validators.required],
|
|
653
|
+
label: [column.label, Validators.required],
|
|
654
|
+
visible: [column.visible],
|
|
655
|
+
order: [column.order]
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
migrateColumnsConfig(config) {
|
|
659
|
+
if (config?.options?.columns?.length > 0) {
|
|
660
|
+
return [...config.options.columns].sort((a, b) => a.order - b.order);
|
|
661
|
+
}
|
|
662
|
+
return this.defaultColumns.map(defaultCol => ({
|
|
663
|
+
...defaultCol,
|
|
664
|
+
visible: config?.options?.[defaultCol.id] ?? defaultCol.visible
|
|
665
|
+
}));
|
|
666
|
+
}
|
|
667
|
+
minOneColumnVisible() {
|
|
668
|
+
return (control) => {
|
|
669
|
+
const columns = control.value;
|
|
670
|
+
if (!columns?.length) {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
const visibleColumns = columns.filter(column => column.visible);
|
|
674
|
+
return visibleColumns.length >= 1 ? null : { atLeastOneColumnMustBeVisible: true };
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
678
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: DatapointsListConfigComponent, isStandalone: true, selector: "c8y-datapoints-list-view-config", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "previewTemplate", first: true, predicate: ["dataPointsListPreview"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (configForm(); as form) {\n <form\n class=\"no-card-context\"\n [formGroup]=\"form\"\n >\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Columns (drag to reorder)' | translate }}</legend>\n <c8y-list-group\n formArrayName=\"columns\"\n cdkDropList\n (cdkDropListDropped)=\"onColumnDrop($event)\"\n >\n @if (columnsFormArray().errors?.atLeastOneColumnMustBeVisible) {\n <div\n class=\"alert alert-warning m-t-8\"\n role=\"alert\"\n >\n {{ 'At least 1 column must be visible.' | translate }}\n </div>\n }\n\n @for (column of columnsFormArray().controls; track column.value.id; let i = $index) {\n <c8y-li\n class=\"c8y-list__item__collapse--container-small\"\n [formGroupName]=\"i\"\n cdkDrag\n >\n <c8y-li-drag-handle\n [title]=\"'Click and drag to reorder' | translate\"\n cdkDragHandle\n >\n <i c8yIcon=\"drag-reorder\"></i>\n </c8y-li-drag-handle>\n <c8y-li-checkbox\n class=\"a-s-center p-r-0\"\n [displayAsSwitch]=\"true\"\n formControlName=\"visible\"\n (click)=\"$event.stopPropagation()\"\n ></c8y-li-checkbox>\n <c8y-li-body>\n <div class=\"d-flex a-i-center\">\n <span class=\"text-truncate\">{{ column.value.label | translate }}</span>\n @switch (column.value.label) {\n @case ('Target') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Target column shows the value set on the target field of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n @case ('Diff') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Diff column shows the difference between the current value and the target value of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n }\n </c8y-list-group>\n </fieldset>\n\n <!-- decimal input -->\n <fieldset class=\"c8y-fieldset\">\n <legend>\n {{ 'Decimal places' | translate }}\n </legend>\n <c8y-form-group class=\"p-t-8\">\n <input\n class=\"form-control\"\n name=\"decimalPlaces\"\n type=\"number\"\n formControlName=\"decimalPlaces\"\n step=\"1\"\n [min]=\"minDecimalPlaces\"\n [max]=\"maxDecimalPlaces\"\n />\n </c8y-form-group>\n </fieldset>\n </form>\n}\n\n<ng-template #dataPointsListPreview>\n @let previewConfig = previewConfig$ | async;\n @if (previewConfig && previewConfig.displayMode !== 'dashboard') {\n <c8y-local-controls\n [controls]=\"controls\"\n [displayMode]=\"previewConfig.displayMode!\"\n [config]=\"previewConfig\"\n [disabled]=\"true\"\n ></c8y-local-controls>\n }\n\n @if (previewConfig) {\n <c8y-datapoints-list\n [config]=\"previewConfig\"\n [isInPreviewMode]=\"true\"\n data-cy=\"c8y-datapoints-list-widget-config--preview-datapoints-list\"\n ></c8y-datapoints-list>\n }\n</ng-template>\n", dependencies: [{ kind: "component", type: DatapointsListViewComponent, selector: "c8y-datapoints-list", inputs: ["config", "isInPreviewMode"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: DynamicComponentModule }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: ListGroupModule }, { kind: "component", type: i1.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i1.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: i1.ListItemCheckboxComponent, selector: "c8y-list-item-checkbox, c8y-li-checkbox", inputs: ["selected", "indeterminate", "disabled", "displayAsSwitch"], outputs: ["onSelect"] }, { kind: "component", type: i1.ListItemDragHandleComponent, selector: "c8y-list-item-drag-handle, c8y-li-drag-handle" }, { kind: "component", type: LocalControlsComponent, selector: "c8y-local-controls", inputs: ["controls", "displayMode", "config", "isLoading", "disabled"], outputs: ["configChange", "refresh"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i3.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
679
|
+
}
|
|
680
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DatapointsListConfigComponent, decorators: [{
|
|
681
|
+
type: Component,
|
|
682
|
+
args: [{ selector: 'c8y-datapoints-list-view-config', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
683
|
+
AsyncPipe,
|
|
684
|
+
C8yTranslatePipe,
|
|
685
|
+
DatapointsListViewComponent,
|
|
686
|
+
DragDropModule,
|
|
687
|
+
DynamicComponentModule,
|
|
688
|
+
FormGroupComponent,
|
|
689
|
+
IconDirective,
|
|
690
|
+
ListGroupModule,
|
|
691
|
+
LocalControlsComponent,
|
|
692
|
+
ReactiveFormsModule,
|
|
693
|
+
PopoverModule
|
|
694
|
+
], template: "@if (configForm(); as form) {\n <form\n class=\"no-card-context\"\n [formGroup]=\"form\"\n >\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Columns (drag to reorder)' | translate }}</legend>\n <c8y-list-group\n formArrayName=\"columns\"\n cdkDropList\n (cdkDropListDropped)=\"onColumnDrop($event)\"\n >\n @if (columnsFormArray().errors?.atLeastOneColumnMustBeVisible) {\n <div\n class=\"alert alert-warning m-t-8\"\n role=\"alert\"\n >\n {{ 'At least 1 column must be visible.' | translate }}\n </div>\n }\n\n @for (column of columnsFormArray().controls; track column.value.id; let i = $index) {\n <c8y-li\n class=\"c8y-list__item__collapse--container-small\"\n [formGroupName]=\"i\"\n cdkDrag\n >\n <c8y-li-drag-handle\n [title]=\"'Click and drag to reorder' | translate\"\n cdkDragHandle\n >\n <i c8yIcon=\"drag-reorder\"></i>\n </c8y-li-drag-handle>\n <c8y-li-checkbox\n class=\"a-s-center p-r-0\"\n [displayAsSwitch]=\"true\"\n formControlName=\"visible\"\n (click)=\"$event.stopPropagation()\"\n ></c8y-li-checkbox>\n <c8y-li-body>\n <div class=\"d-flex a-i-center\">\n <span class=\"text-truncate\">{{ column.value.label | translate }}</span>\n @switch (column.value.label) {\n @case ('Target') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Target column shows the value set on the target field of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n @case ('Diff') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Diff column shows the difference between the current value and the target value of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n }\n </c8y-list-group>\n </fieldset>\n\n <!-- decimal input -->\n <fieldset class=\"c8y-fieldset\">\n <legend>\n {{ 'Decimal places' | translate }}\n </legend>\n <c8y-form-group class=\"p-t-8\">\n <input\n class=\"form-control\"\n name=\"decimalPlaces\"\n type=\"number\"\n formControlName=\"decimalPlaces\"\n step=\"1\"\n [min]=\"minDecimalPlaces\"\n [max]=\"maxDecimalPlaces\"\n />\n </c8y-form-group>\n </fieldset>\n </form>\n}\n\n<ng-template #dataPointsListPreview>\n @let previewConfig = previewConfig$ | async;\n @if (previewConfig && previewConfig.displayMode !== 'dashboard') {\n <c8y-local-controls\n [controls]=\"controls\"\n [displayMode]=\"previewConfig.displayMode!\"\n [config]=\"previewConfig\"\n [disabled]=\"true\"\n ></c8y-local-controls>\n }\n\n @if (previewConfig) {\n <c8y-datapoints-list\n [config]=\"previewConfig\"\n [isInPreviewMode]=\"true\"\n data-cy=\"c8y-datapoints-list-widget-config--preview-datapoints-list\"\n ></c8y-datapoints-list>\n }\n</ng-template>\n" }]
|
|
695
|
+
}], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], previewTemplate: [{ type: i0.ViewChild, args: ['dataPointsListPreview', { isSignal: true }] }] } });
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Generated bundle index. Do not edit.
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
export { DatapointsListConfigComponent, DatapointsListFetchService, DatapointsListService, DatapointsListViewComponent };
|
|
702
|
+
//# sourceMappingURL=c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map
|