@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
461 lines (453 loc) • 20.6 kB
JavaScript
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