UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

461 lines (453 loc) 20.6 kB
import * as i0 from '@angular/core'; import { inject, Injectable, NgModule, signal } from '@angular/core'; import { gettext } from '@c8y/ngx-components/gettext'; import { PreviewService, NavigatorNode, hookRoute, ViewContext, hookNavigator, hookPreview, AlertService, MOChunkLoaderService, DatapointSyncService } from '@c8y/ngx-components'; import { map, first, firstValueFrom } from 'rxjs'; import { InventoryService } from '@c8y/client'; import { ContextDashboardService, ContextDashboardType } from '@c8y/ngx-components/context-dashboard'; import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string'; import { Router } from '@angular/router'; class DatapointExplorerNavigationFactory { constructor() { this.previewFeatureService = inject(PreviewService); } get() { return this.previewFeatureService.getState$('ui.datapoint-explorer.v2').pipe(map(state => { if (state) { return [ new NavigatorNode({ label: gettext('Data explorer'), featureId: 'dataExplorer', path: 'datapointexplorer-v2', icon: 'c8y-data-explorer', routerLinkExact: false, preventDuplicates: true }) ]; } return []; })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerNavigationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerNavigationFactory }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerNavigationFactory, decorators: [{ type: Injectable }] }); function canActivateDatapointExplorer() { const previewFeatureService = inject(PreviewService); return previewFeatureService.getState$('ui.datapoint-explorer.v2').pipe(first()); } class DatapointExplorerModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerModule, providers: [ hookRoute([ { path: 'datapointexplorer-v2', loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), canActivate: [canActivateDatapointExplorer] }, { context: ViewContext.Group, path: 'data_explorer-v2', priority: 800, icon: 'c8y-data-explorer', label: gettext('Data Explorer'), loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), featureId: 'dataExplorer', canActivate: [canActivateDatapointExplorer] }, { context: ViewContext.Device, path: 'data_explorer-v2', priority: 800, icon: 'c8y-data-explorer', label: gettext('Data Explorer'), loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), featureId: 'dataExplorer', canActivate: [canActivateDatapointExplorer] } ]), hookNavigator(DatapointExplorerNavigationFactory), hookPreview({ key: 'ui.datapoint-explorer.v2', label: 'Data point explorer', description: () => import('@c8y/style/markdown-files/datapoint-explorer-preview.md').then(m => m.default), settings: { reload: true } }) ] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointExplorerModule, decorators: [{ type: NgModule, args: [{ providers: [ hookRoute([ { path: 'datapointexplorer-v2', loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), canActivate: [canActivateDatapointExplorer] }, { context: ViewContext.Group, path: 'data_explorer-v2', priority: 800, icon: 'c8y-data-explorer', label: gettext('Data Explorer'), loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), featureId: 'dataExplorer', canActivate: [canActivateDatapointExplorer] }, { context: ViewContext.Device, path: 'data_explorer-v2', priority: 800, icon: 'c8y-data-explorer', label: gettext('Data Explorer'), loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent), featureId: 'dataExplorer', canActivate: [canActivateDatapointExplorer] } ]), hookNavigator(DatapointExplorerNavigationFactory), hookPreview({ key: 'ui.datapoint-explorer.v2', label: 'Data point explorer', description: () => import('@c8y/style/markdown-files/datapoint-explorer-preview.md').then(m => m.default), settings: { reload: true } }) ] }] }] }); const DATA_EXPLORER_BASE_CONFIG = { datapoints: [], alarmsEventsConfigs: [], dateFrom: null, dateTo: null, interval: 'hours', aggregation: null, realtime: false, isAutoRefreshEnabled: false, refreshInterval: 0, displayMarkedLine: true, displayMarkedPoint: true, mergeMatchingDatapoints: false, forceMergeDatapoints: false, showLabelAndUnit: true, showSlider: true, displayDateSelection: false, yAxisSplitLines: false, xAxisSplitLines: false, numberOfDecimalPlaces: 2 }; const REVERSE_KEY_MAP = { d: 'datapoints', a: 'alarmsEventsConfigs', f: 'fragment', s: 'series', t: '__target', m: '__template', l: 'label', r: 'filters', y: 'timelineType', df: 'dateFrom', dt: 'dateTo', ac: '__active', c: 'color', i: 'id', tp: 'type' }; const KEY_MAP = Object.fromEntries(Object.entries(REVERSE_KEY_MAP).map(([shortKey, longKey]) => [longKey, shortKey])); class WorkspaceConfigurationService { constructor() { this.baseKey = 'c8y-configs'; this.baseDefaultKey = 'c8y-default-config-id'; this.contextIdSignal = signal(null, ...(ngDevMode ? [{ debugName: "contextIdSignal" }] : [])); this.alertService = inject(AlertService); } get LOCAL_STORAGE_KEY() { const id = this.contextIdSignal(); return id !== null ? `${this.baseKey}-${id}` : this.baseKey; } get LOCAL_STORAGE_DEFAULT_ID_KEY() { const id = this.contextIdSignal(); return id !== null ? `${this.baseDefaultKey}-${id}` : this.baseDefaultKey; } /** * Generates a full datapoint explorer link from a bare config */ generateExplorerLink(config, label, id) { const diffed = this.removeDefaults(config); const minified = this.minifyKeys(diffed); const compressed = compressToEncodedURIComponent(JSON.stringify(minified)); const url = `/datapointexplorer-v2?id=${id}&label=${encodeURIComponent(label)}&config=${compressed}`; return url; } /** Load workspace configs from localStorage */ getConfigurations() { const configurations = localStorage.getItem(this.LOCAL_STORAGE_KEY); if (configurations) { const parsedConfigs = JSON.parse(configurations); return parsedConfigs.map(workspaceConfig => { if (typeof workspaceConfig.config === 'string') { return { ...workspaceConfig, config: this.decodeConfig(workspaceConfig.config) }; } return workspaceConfig; }); } return []; } getDefaultConfigurationId() { return localStorage.getItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY); } /** Save workspace configs in localStorage */ saveConfigurations(configurations, id) { // Before saving, we need to clean up the config objects to remove any unnecessary properties in the __target object configurations = configurations.map(workspace => ({ ...workspace, config: this.cleanUpTargetObject(workspace.config) })); localStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(configurations)); localStorage.setItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY, id); } /** * * @param urlConfig - configuration from the URL, either compressed string or already decoded object * @returns */ getConfigurationFromUrl(urlConfig) { if (typeof urlConfig === 'string') { const decodedConfig = this.decodeConfig(urlConfig); const expandedConfig = this.expandKeys(decodedConfig); return expandedConfig; } return this.expandKeys(urlConfig); } /** * Encode a config for the URL: * - Cleanup the __target objects to contain only name and id * - Remove default values (diff from base) * - Minify keys * - Compress with lz-string */ encodeConfig(config) { const normalized = this.cleanUpTargetObject(config); const diffed = this.removeDefaults(normalized); const minified = this.minifyKeys(diffed); return compressToEncodedURIComponent(JSON.stringify(minified)); } /** * Decode a config from the URL: * - Decompress * - Expand keys * - Merge with base config */ decodeConfig(urlConfig) { if (!urlConfig) return null; try { const decompressed = decompressFromEncodedURIComponent(urlConfig); if (!decompressed) return null; const parsed = JSON.parse(decompressed); const expanded = this.expandKeys(parsed); return { ...DATA_EXPLORER_BASE_CONFIG, ...expanded }; } catch { this.alertService.danger(gettext('The decoded configuration is invalid and could not be loaded.')); } } /** Minify keys recursively using KEY_MAP */ minifyKeys(config) { if (Array.isArray(config)) return config.map(object => this.minifyKeys(object)); if (config !== Object(config)) return config; return Object.fromEntries(Object.entries(config).map(([originalKey, originalValue]) => [ KEY_MAP[originalKey] || originalKey, this.minifyKeys(originalValue) ])); } /** Expand keys recursively using REVERSE_KEY_MAP */ expandKeys(config) { if (Array.isArray(config)) return config.map(o => this.expandKeys(o)); if (config !== Object(config)) return config; return Object.fromEntries(Object.entries(config).map(([originalKey, originalValue]) => [ REVERSE_KEY_MAP[originalKey] || originalKey, this.expandKeys(originalValue) ])); } /** * Remove properties from `config` that match `base` so only changed values remain. */ removeDefaults(config) { const result = {}; for (const key in config) { if (!(key in DATA_EXPLORER_BASE_CONFIG) || DATA_EXPLORER_BASE_CONFIG[key] !== config[key]) { result[key] = config[key]; } } return result; } /** * Cleans up __target objects to only keep id and name and remove the rest. */ cleanUpTargetObject(config) { const configCopy = { ...config }; // Cleanup the datapoints array if (Array.isArray(configCopy.datapoints)) { configCopy.datapoints = configCopy.datapoints.map((dp) => { if (dp && typeof dp === 'object' && dp.__target && typeof dp.__target === 'object') { const { id, name } = dp.__target; const target = {}; if (id !== undefined) target.id = id; if (name !== undefined) target.name = name; return { ...dp, __target: target }; } return dp; }); } // Cleanup the alarmEventsConfig array if (Array.isArray(configCopy.alarmsEventsConfigs)) { configCopy.alarmsEventsConfigs = configCopy.alarmsEventsConfigs.map((a) => { if (a && typeof a === 'object' && a.__target && typeof a.__target === 'object') { const { id, name } = a.__target; const target = {}; if (id !== undefined) target.id = id; if (name !== undefined) target.name = name; return { ...a, __target: target }; } return a; }); } return configCopy; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WorkspaceConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WorkspaceConfigurationService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WorkspaceConfigurationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class DataExplorerService { constructor() { this.inventory = inject(InventoryService); this.contextDashboardService = inject(ContextDashboardService); this.workspaceConfigurationService = inject(WorkspaceConfigurationService); this.moChunkLoader = inject(MOChunkLoaderService); this.datapointSyncService = inject(DatapointSyncService); this.router = inject(Router); this.maxNumberOfManagedObjectsPerRequest = 50; } async fetchReportDashboard(reportId) { return (await this.inventory.list({ pageSize: 1, query: `has('c8y_Dashboard!name!report_${reportId}')` })).data[0]; } async fetchContextDashboard(dashboardId, contextAsset) { const context = contextAsset.c8y_isDevice ? ContextDashboardType.Device : ContextDashboardType.Group; return firstValueFrom(this.contextDashboardService.getDashboard$(dashboardId, [context])); } async loadManagedObjectsInChunks(uniqIds) { const { results, errors } = await this.moChunkLoader.processInChunks(uniqIds, this.maxNumberOfManagedObjectsPerRequest, ids => this.loadAChunkOfManagedObjects(ids)); return { result: results, errors }; } /** * Navigate to datapoint explorer with given config. * The goal of this method is to navigate to the data explorer with a provided config from any other application. * @param config Configuration to use * @param label Label to be displayed for the configuration * @param id ID for the configuration */ navigateToDataExplorer(config, label, id) { const url = this.getUrlForConfig(config, label, id); this.router.navigateByUrl(url); } /** * Generate a URL for the datapoint explorer with the given config. * The goal of this method is to generate a shareable link to the data explorer. * @param config Configuration to use * @param label Label to be displayed for the configuration * @param id ID for the configuration * @returns The generated URL */ getUrlForConfig(config, label, id) { return this.workspaceConfigurationService.generateExplorerLink(config, label, id); } processAlarmEventConfigs(config) { const firstTarget = config.alarmsEventsConfigs.find(ae => ae.__target)?.__target; config.alarmsEventsConfigs = config.alarmsEventsConfigs.map((ae, index) => { if (ae.__active === undefined) ae.__active = true; if (!ae.__target && firstTarget) ae.__target = firstTarget; if (!ae.color) ae.color = this.generateColor(index); return ae; }); } processDatapoints(config) { const firstTarget = config.datapoints.find(dp => dp.__target)?.__target; config.datapoints = config.datapoints.map((dp, index) => { // Default __active if (dp.__active === undefined) dp.__active = true; if (!dp.__target && firstTarget) dp.__target = firstTarget; if (!dp.color && !dp.__template) dp.color = this.generateColor(index); if (!dp.label) dp.label = `${dp.fragment} -> ${dp.series}`; return dp; }); } /** * Generates a color from a fixed palette based on the index. * Used to assign colors to alarm/event configs in the UI. */ generateColor(index) { // Simple palette, can expand const palette = ['#c87d33', '#8c145f', '#8cd7fd', '#59a036', '#fb00ff', '#8d4c22', '#fbb2d7']; return palette[index % palette.length]; } async loadAndAssignManagedObjects(config, uniqueIds) { const managedObjectsResult = await this.loadManagedObjectsInChunks([...uniqueIds]); const managedObjects = managedObjectsResult.result; const errors = managedObjectsResult.errors; config.datapoints = this.datapointSyncService.assignUpdatedValues(config.datapoints, managedObjects, errors); } async loadAChunkOfManagedObjects(uniqIds) { return this.moChunkLoader.loadAChunkOfManagedObjectsBase(uniqIds, this.inventory, this.maxNumberOfManagedObjectsPerRequest, id => this.moChunkLoader.getStatusDetails(id)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataExplorerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataExplorerService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataExplorerService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * Generated bundle index. Do not edit. */ export { DataExplorerService, DatapointExplorerModule, canActivateDatapointExplorer }; //# sourceMappingURL=c8y-ngx-components-datapoint-explorer.mjs.map