UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines • 76.7 kB
{"version":3,"file":"c8y-ngx-components-datapoint-explorer-view.mjs","sources":["../../datapoint-explorer/view/send-as-widget-to-report-modal/send-as-widget-to-report-modal.component.ts","../../datapoint-explorer/view/send-as-widget-to-report-modal/send-as-widget-to-report-modal.component.html","../../datapoint-explorer/view/create-new-report-modal/create-new-report-modal.component.ts","../../datapoint-explorer/view/create-new-report-modal/create-new-report-modal.component.html","../../datapoint-explorer/view/datapoint-explorer.service.ts","../../datapoint-explorer/view/configuration/workspace-configuration.service.ts","../../datapoint-explorer/view/configuration/naming-dictionary.ts","../../datapoint-explorer/view/configuration/name-generator.service.ts","../../datapoint-explorer/view/configuration/workspace-configuration.component.ts","../../datapoint-explorer/view/configuration/workspace-configuration.component.html","../../datapoint-explorer/view/datapoint-explorer.component.ts","../../datapoint-explorer/view/datapoint-explorer.component.html","../../datapoint-explorer/view/c8y-ngx-components-datapoint-explorer-view.ts"],"sourcesContent":["import { Component, inject } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { CoreModule, gettext, ModalLabels } from '@c8y/ngx-components';\nimport { Observable, merge, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport {\n ReportDashboardModule,\n ReportDashboardService\n} from '@c8y/ngx-components/report-dashboard';\nimport { IManagedObject } from '@c8y/client';\n@Component({\n selector: 'c8y-apply-branding-to-app-modal',\n templateUrl: './send-as-widget-to-report-modal.component.html',\n standalone: true,\n imports: [ReactiveFormsModule, CoreModule, ReportDashboardModule]\n})\nexport class SendAsWidgetToReportModal {\n reports: IManagedObject[] = [];\n numberOfSelectedReports$: Observable<number>;\n form: ReturnType<typeof this.initForm>;\n result = new Promise<ReturnType<typeof this.initForm>['value']>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n labels: ModalLabels = { cancel: gettext('Cancel'), ok: gettext('Send') };\n private reportDashboardService = inject(ReportDashboardService);\n private _resolve: (value: ReturnType<typeof this.initForm>['value']) => void;\n private _reject: (reason?: any) => void;\n async ngOnInit() {\n this.reports = (await this.reportDashboardService.listReports({ pageSize: 2000 })).data;\n if (this.reports.length > 0) {\n this.form = this.initForm();\n }\n this.numberOfSelectedReports$ = merge(this.form.valueChanges, of(this.form.value)).pipe(\n map(value => Object.values(value).filter(Boolean).length)\n );\n }\n save() {\n this._resolve(this.form.value);\n }\n cancel() {\n this._reject();\n }\n private initForm(): FormGroup<Record<string, FormControl<boolean>>> {\n const controls = this.reports.reduce(\n (acc, report) => ({\n ...acc,\n [report.id]: new FormControl(false)\n }),\n {}\n );\n return new FormGroup(controls);\n }\n}\n","<c8y-modal\n [title]=\"'Send as widget to reports' | translate\"\n [disabled]=\"!form || form.invalid\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"labels\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"c8y-reports\"></span>\n </ng-container>\n\n <c8y-list-group\n class=\"m-b-0 no-border-last\"\n *ngIf=\"form\"\n [formGroup]=\"form\"\n >\n <c8y-li>\n <p\n class=\"text-center text-medium\"\n *ngIf=\"numberOfSelectedReports$ | async as numberOfReports; else noSelectedReports\"\n translate\n [translateParams]=\"{ numberOfReports: numberOfReports }\"\n ngNonBindable\n >\n {{ numberOfReports }} reports selected for widget\n </p>\n <ng-template #noSelectedReports>\n <p\n class=\"text-center text-medium\"\n translate\n >\n Select one or more reports to send the Data points graph widget with the current configuration\n </p>\n </ng-template>\n </c8y-li>\n <c8y-li *ngFor=\"let report of reports\">\n <c8y-li-checkbox\n [attr.data-cy]=\"'branding-apply-branding-to-app-checkbox-' + report.id\"\n [formControlName]=\"report.id\"\n ></c8y-li-checkbox>\n <c8y-li-icon class=\"p-l-0\">\n <i c8yIcon=\"{{ report.icon }}\"></i>\n </c8y-li-icon>\n <div class=\"text-truncate\" title=\"{{report.name}}\">{{ report.name }}</div>\n </c8y-li>\n </c8y-list-group>\n\n <c8y-ui-empty-state\n [icon]=\"'c8y-reports'\"\n [title]=\"'There are no reports defined.' | translate\"\n [subtitle]=\"'Add a report first.' | translate\"\n *ngIf=\"reports.length === 0\"\n ></c8y-ui-empty-state>\n</c8y-modal>\n","import { Component } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { CoreModule, gettext, ModalLabels } from '@c8y/ngx-components';\nimport { ReportDashboardModule } from '@c8y/ngx-components/report-dashboard';\nimport { ContextDashboardService } from '@c8y/ngx-components/context-dashboard';\nimport { IManagedObject } from '@c8y/client';\n\n@Component({\n selector: 'c8y-create-new-report-modal',\n templateUrl: './create-new-report-modal.component.html',\n standalone: true,\n imports: [ReactiveFormsModule, CoreModule, ReportDashboardModule]\n})\nexport class CreateNewReportModalComponent {\n reportName = '';\n labels: ModalLabels = { cancel: gettext('Cancel'), ok: gettext('Send') };\n\n result = new Promise<IManagedObject>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n\n styling = {\n themeClass: 'dashboard-theme-light',\n headerClass: 'panel-title-regular'\n };\n\n readonly DEFAULT_DASHBOARD_ICON = 'th';\n readonly DEFAULT_DASHBOARD_PRIORITY = 5000;\n readonly DEFAULT_DASHBOARD_MARGIN = 12;\n private _resolve: (value: IManagedObject) => void;\n private _reject: (reason?: any) => void;\n constructor(private contextDashboardService: ContextDashboardService) {}\n\n async save() {\n const dashboard = {\n name: this.reportName,\n icon: this.DEFAULT_DASHBOARD_ICON,\n c8y_IsNavigatorNode: null,\n priority: this.DEFAULT_DASHBOARD_PRIORITY,\n description: '',\n widgetMargin: this.DEFAULT_DASHBOARD_MARGIN,\n classes: { [this.styling.headerClass]: true },\n widgetClasses: { [this.styling.headerClass]: true }\n };\n try {\n const { name, icon, c8y_IsNavigatorNode, priority, description } = dashboard;\n const report = (\n await this.contextDashboardService.createReport({\n name,\n icon,\n c8y_IsNavigatorNode,\n priority,\n description\n } as Partial<IManagedObject>)\n ).data;\n\n await this.contextDashboardService.create(\n dashboard,\n undefined,\n `${this.contextDashboardService.REPORT_PARTIAL_NAME}${report.id}`\n );\n\n this._resolve(report);\n } catch (ex) {\n this._reject(ex);\n }\n }\n\n cancel() {\n this._reject();\n }\n}\n","<c8y-modal\n [title]=\"'Create new report with widget' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [disabled]=\"reportName === ''\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"labels\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"c8y-reports\"></span>\n </ng-container>\n\n <p class=\"text-center bg-component text-balance sticky-top p-l-24 p-r-24 p-t-8 p-b-8 separator-bottom\">\n {{' Create a new report with the Data points graph widget using the current configuration.' | translate}}\n </p>\n <div class=\"p-24 p-t-8\">\n <c8y-form-group>\n <label\n for=\"reportName\"\n translate\n >\n Report name\n </label>\n <input\n class=\"form-control\"\n id=\"reportName\"\n placeholder=\"{{ 'e.g. My data point Report' }}\"\n name=\"name\"\n type=\"text\"\n autocomplete=\"off\"\n required\n [(ngModel)]=\"reportName\"\n />\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </div>\n</c8y-modal>\n","import { inject, Injectable } from '@angular/core';\nimport { IManagedObject, InventoryService } from '@c8y/client';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class DataExplorerService {\n private readonly inventory = inject(InventoryService);\n\n async fetchReportDashboard(reportId: string): Promise<IManagedObject> {\n return (\n await this.inventory.list({\n pageSize: 1,\n query: `has('c8y_Dashboard!name!report_${reportId}')`\n })\n ).data[0];\n }\n}\n","import { effect, Injectable, signal, WritableSignal } from '@angular/core';\nimport { WorkspaceConfiguration } from './workspace-configuration.model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class WorkspaceConfigurationService {\n LOCAL_STORAGE_KEY = 'c8y-configs';\n LOCAL_STORAGE_DEFAULT_ID_KEY = 'c8y-default-config-id';\n contextIdSignal: WritableSignal<number | string | null> = signal(null);\n\n constructor() {\n effect(() => {\n const signalValue = this.contextIdSignal();\n if (signalValue !== null) {\n this.LOCAL_STORAGE_KEY = `c8y-configs-${signalValue}`;\n this.LOCAL_STORAGE_DEFAULT_ID_KEY = `c8y-default-config-id-${signalValue}`;\n }\n if (signalValue == null) {\n this.LOCAL_STORAGE_KEY = 'c8y-configs';\n this.LOCAL_STORAGE_DEFAULT_ID_KEY = 'c8y-default-config-id';\n }\n });\n }\n\n getConfigurations(): WorkspaceConfiguration[] {\n const configurations = localStorage.getItem(this.LOCAL_STORAGE_KEY);\n return configurations ? JSON.parse(configurations) : [];\n }\n\n getDefaultConfigurationId(): string | null {\n return localStorage.getItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY);\n }\n\n saveConfigurations(configurations: WorkspaceConfiguration[], id: string): void {\n localStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(configurations));\n localStorage.setItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY, id);\n }\n}\n","export const adjectives = [\n 'caffeinated',\n 'sleepy',\n 'hungry',\n 'puzzled',\n 'overexcited',\n 'daydreaming',\n 'chocolate-loving',\n 'coffee-powered',\n 'cookie-craving',\n 'disco-ready',\n 'weekend-mode',\n 'pizza-powered',\n 'nap-seeking',\n 'wifi-hunting',\n 'battery-hungry',\n 'donut-powered',\n 'tea-sipping',\n 'keyboard-loving',\n 'screen-staring',\n 'mouse-chasing',\n 'code-dreaming',\n 'pixel-perfect',\n 'bug-finding',\n 'zoom-tired',\n 'meeting-dodging',\n 'deadline-racing',\n 'coffee-seeking',\n 'sandwich-craving',\n 'debug-ready',\n 'rest-needing'\n];\nexport const nouns = [\n 'sensor',\n 'robot',\n 'thermostat',\n 'gateway',\n 'dashboard',\n 'widget',\n 'gadget',\n 'button',\n 'antenna',\n 'beacon',\n 'adapter',\n 'gizmo',\n 'hub',\n 'switch',\n 'chip',\n 'controller',\n 'display',\n 'terminal',\n 'processor',\n 'transmitter',\n 'receiver',\n 'pod',\n 'device',\n 'module',\n 'relay',\n 'node',\n 'bridge',\n 'screen',\n 'router',\n 'box'\n];\n","import { Injectable } from '@angular/core';\nimport { adjectives, nouns } from './naming-dictionary';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class NameGeneratorService {\n generateName(): string {\n const getRandomElement = (arr: string[]) => arr[Math.floor(Math.random() * arr.length)];\n const randomAdjective = getRandomElement(adjectives);\n const randomNoun = getRandomElement(nouns);\n\n return `${randomAdjective}_${randomNoun}`;\n }\n}\n","import { A11yModule } from '@angular/cdk/a11y';\nimport {\n Component,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnInit,\n Output,\n SimpleChanges\n} from '@angular/core';\nimport { FormArray, FormBuilder, FormGroup, FormsModule } from '@angular/forms';\nimport {\n ClipboardService,\n ContextRouteService,\n CoreModule,\n gettext,\n ViewContext\n} from '@c8y/ngx-components';\nimport { BsDropdownModule } from 'ngx-bootstrap/dropdown';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\nimport { WorkspaceConfiguration } from './workspace-configuration.model';\nimport { DatapointsGraphWidgetConfig } from '@c8y/ngx-components/echart';\nimport { WorkspaceConfigurationService } from './workspace-configuration.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { NameGeneratorService } from './name-generator.service';\n\n@Component({\n selector: 'c8y-workspace-config',\n templateUrl: './workspace-configuration.component.html',\n standalone: true,\n imports: [CoreModule, BsDropdownModule, FormsModule, A11yModule, TooltipModule]\n})\nexport class WorkspaceConfigComponent implements OnInit, OnChanges {\n @Input() updatedConfig: DatapointsGraphWidgetConfig;\n @Output() onConfigurationChange = new EventEmitter<DatapointsGraphWidgetConfig>();\n\n currentConfiguration: WorkspaceConfiguration;\n configurations: WorkspaceConfiguration[] = [];\n configurationsFormGroup: FormGroup;\n activeConfigTooltip = gettext('Active configuration cannot be removed');\n removeConfigTooltip = gettext('Remove configuration');\n\n private readonly formBuilder = inject(FormBuilder);\n private readonly workspaceConfigurationService = inject(WorkspaceConfigurationService);\n private readonly contextRouteService = inject(ContextRouteService);\n private readonly clipboardService = inject(ClipboardService);\n private readonly activatedRoute = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly nameGeneratorService = inject(NameGeneratorService);\n\n ngOnInit(): void {\n this.initializeContextSourceId();\n this.initializeConfigurations();\n this.initWorkspaceForm();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.updatedConfig && !changes.updatedConfig.firstChange) {\n this.updateConfigurations();\n }\n }\n\n addConfig(duplicatedConfig?: DatapointsGraphWidgetConfig): void {\n const name = this.nameGeneratorService.generateName();\n const workspace: WorkspaceConfiguration = {\n id: new Date().toISOString(),\n label: name,\n config: { datapoints: [], alarmsEventsConfigs: [] }\n };\n if (duplicatedConfig) {\n workspace.config = duplicatedConfig;\n }\n this.configurations = [workspace, ...this.configurations];\n this.initWorkspaceForm();\n this.changeConfiguration(true, workspace);\n }\n\n changeConfiguration(selected: boolean, configuration: WorkspaceConfiguration): void {\n if (!selected) {\n return;\n }\n this.currentConfiguration = configuration;\n localStorage.setItem(\n this.workspaceConfigurationService.LOCAL_STORAGE_DEFAULT_ID_KEY,\n this.currentConfiguration.id\n );\n this.onConfigurationChange.emit(configuration.config);\n }\n\n updateConfigurationLabel(configuration: WorkspaceConfiguration): void {\n this.configurations = this.configurations.map(c =>\n c.id === configuration.id ? configuration : c\n );\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n this.currentConfiguration = configuration;\n this.onConfigurationChange.emit(configuration.config);\n }\n\n deleteConfiguration(configuration: WorkspaceConfiguration): void {\n this.configurations = this.configurations.filter(c => c.id !== configuration.id);\n this.initWorkspaceForm();\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n }\n\n clearAll(): void {\n this.configurations = [this.currentConfiguration];\n this.initWorkspaceForm();\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n }\n\n async shareConfig(configuration: WorkspaceConfiguration): Promise<void> {\n await this.clipboardService.writeText(JSON.stringify(configuration.config));\n }\n\n private addConfigFromUrl(queryParams: any): void {\n this.addConfig();\n const config = JSON.parse(queryParams.config);\n this.onConfigurationChange.emit(config);\n }\n\n private updateConfigurations(): void {\n this.currentConfiguration.config = this.updatedConfig;\n this.configurations = this.configurations.map(currentConfig =>\n currentConfig.id === this.currentConfiguration.id ? this.currentConfiguration : currentConfig\n );\n const queryParams = {\n configId: this.currentConfiguration.id,\n label: this.currentConfiguration.label,\n config: JSON.stringify(this.updatedConfig)\n };\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration.id\n );\n const control = <FormArray>this.configurationsFormGroup.controls['configurations'];\n const index = this.configurations.findIndex(c => c.id === this.currentConfiguration.id);\n if (index !== -1) {\n control.at(index).patchValue({\n label: this.currentConfiguration.label,\n config: this.currentConfiguration.config,\n id: this.currentConfiguration.id\n });\n }\n this.router.navigate([], { queryParams });\n }\n\n private initializeConfigurations(): void {\n const configurations = this.workspaceConfigurationService.getConfigurations();\n const defaultId = this.workspaceConfigurationService.getDefaultConfigurationId();\n const queryParams = this.router.parseUrl(this.router.url).queryParams;\n if (configurations.length) {\n this.configurations = configurations;\n this.currentConfiguration =\n this.configurations.find(c => c.id === defaultId) || this.configurations[0];\n\n this.onConfigurationChange.emit(this.currentConfiguration.config);\n if (queryParams?.configId && !this.configurations.find(c => c.id === queryParams.configId)) {\n this.addConfigFromUrl(queryParams);\n } else if (\n queryParams?.configId &&\n this.configurations.find(c => c.id === queryParams.configId) &&\n this.currentConfiguration.id !== queryParams.configId\n ) {\n this.currentConfiguration = this.configurations.find(c => c.id === queryParams.configId);\n this.changeConfiguration(true, this.currentConfiguration);\n }\n }\n\n if (!this.currentConfiguration) {\n if (Object.keys(queryParams).length === 0) {\n this.addConfig();\n return;\n }\n this.addConfigFromUrl(queryParams);\n }\n }\n\n private initWorkspaceForm(): void {\n this.configurationsFormGroup = this.formBuilder.group({\n configurations: this.formBuilder.array([])\n });\n\n this.patchForm();\n }\n\n private patchForm(): void {\n const control = <FormArray>this.configurationsFormGroup.controls['configurations'];\n this.configurations.forEach(workspace => {\n control.push(this.patchValues(workspace));\n });\n }\n\n private patchValues(workspace: WorkspaceConfiguration): FormGroup {\n return this.formBuilder.group({\n label: [workspace.label],\n config: [workspace.config],\n id: [workspace.id]\n });\n }\n\n private initializeContextSourceId(): void {\n const routeContext = this.contextRouteService.getContextData(this.activatedRoute);\n if (!routeContext) {\n return;\n }\n const { context, contextData } = routeContext;\n if ([ViewContext.Device, ViewContext.Group].includes(context)) {\n this.workspaceConfigurationService.contextIdSignal.set(contextData?.id);\n }\n }\n}\n","<div\n class=\"dropdown\"\n #actionbar_dropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"actionbar_dropdown.isOpen\"\n dropdown\n [insideClick]=\"true\"\n>\n\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{ currentConfiguration.label }}\"\n tooltip=\"{{ 'Selected configuration' | translate }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"current-configuration-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"list\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span class=\"text-12\">\n {{ 'Configuration' | translate }}\n </span>\n <span class=\"text-10 text-muted text-truncate\">\n {{ currentConfiguration.label }}\n </span>\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n <div\n class=\"dropdown-menu dropdown-menu-wide dropdown-menu-action-bar\"\n *dropdownMenu\n >\n <div class=\"sticky-top separator-bottom p-t-8 p-b-8 p-l-16 p-r-16\">\n <p>\n <strong>{{ 'Data explorer configurations' | translate }}</strong>\n </p>\n <p>\n <small>{{ 'Easily switch and manage configurations.' | translate }}</small>\n </p>\n </div>\n <c8y-list-group class=\"no-border-last\">\n <form [formGroup]=\"configurationsFormGroup\">\n <div formArrayName=\"configurations\">\n <c8y-li\n class=\"p-0\"\n *ngFor=\"\n let configuration of configurationsFormGroup.controls.configurations['controls'];\n let i = index\n \"\n [dense]=\"true\"\n >\n <c8y-li-radio\n [selected]=\"configuration.value.id === currentConfiguration.id\"\n (onSelect)=\"changeConfiguration($event, configuration.value)\"\n ></c8y-li-radio>\n <div class=\"d-flex a-i-center gap-8\">\n <div\n class=\"flex-grow min-width-0\"\n [formGroupName]=\"i\"\n >\n <label\n class=\"editable\"\n [ngClass]=\"{\n updated:\n configuration.controls.label.touched && configuration.controls.label.dirty\n }\"\n >\n <input\n class=\"form-control\"\n [style.width.ch]=\"configuration.value.label || 25\"\n [attr.aria-label]=\"'Configuration label' | translate\"\n placeholder=\"{{ 'Configuration 1' | translate }}\"\n type=\"text\"\n autocomplete=\"off\"\n required\n formControlName=\"label\"\n (blur)=\"updateConfigurationLabel(configuration.value)\"\n />\n </label>\n </div>\n <div class=\"flex-nogrow d-flex gap-8\">\n <button\n class=\"btn-dot btn\"\n [attr.aria-label]=\"'Duplicate configuration' | translate\"\n tooltip=\"{{ 'Duplicate configuration' | translate }}\"\n placement=\"left\"\n (click)=\"addConfig(configuration.value.config)\"\n [delay]=\"500\"\n >\n <i c8yIcon=\"copy\"></i>\n </button>\n\n <button\n class=\"btn-dot btn btn-dot--danger\"\n [attr.aria-label]=\"'Remove configurations' | translate\"\n tooltip=\"{{\n (configuration.value.id === currentConfiguration.id\n ? activeConfigTooltip\n : removeConfigTooltip\n ) | translate\n }}\"\n placement=\"left\"\n [delay]=\"500\"\n (click)=\"$event.stopPropagation(); deleteConfiguration(configuration.value)\"\n [disabled]=\"configuration.value.id === currentConfiguration.id\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </div>\n </c8y-li>\n </div>\n </form>\n </c8y-list-group>\n <div class=\"sticky-bottom separator-top\">\n <div class=\"d-flex p-l-16 p-r-16 p-t-8 p-b-8\">\n <button\n class=\"btn btn-danger btn-sm flex-grow\"\n (click)=\"clearAll()\"\n [disabled]=\"configurations.length < 2\"\n >\n <i [c8yIcon]=\"'delete'\"></i>\n {{ 'Delete all configurations' | translate }}\n </button>\n <button\n class=\"btn btn-default btn-sm flex-grow\"\n type=\"button\"\n (click)=\"addConfig()\"\n >\n <i [c8yIcon]=\"'add-circle-outline'\"></i>\n {{ 'Add configuration' | translate }}\n </button>\n </div>\n </div>\n </div>\n</div>\n","import { Component, DestroyRef, ElementRef, inject, ViewChild } from '@angular/core';\nimport {\n AlertService,\n CommonModule,\n ContextRouteService,\n CoreModule,\n DynamicComponentAlertAggregator,\n FormsModule,\n gettext,\n Permissions,\n ViewContext\n} from '@c8y/ngx-components';\nimport { AlarmEventSelectorModule } from '@c8y/ngx-components/alarm-event-selector';\nimport { TimeContextComponent } from '@c8y/ngx-components/time-context';\nimport {\n DatapointAttributesFormConfig,\n DatapointSelectorModule,\n KPIDetails\n} from '@c8y/ngx-components/datapoint-selector';\nimport {\n AlarmDetailsExtended,\n AlarmOrEventExtended,\n DatapointsGraphKPIDetails,\n DatapointsGraphWidgetConfig,\n EventDetailsExtended,\n TimeContextProps\n} from '@c8y/ngx-components/echart/models';\nimport {\n ChartAlarmsService,\n ChartEventsService,\n ChartsComponent\n} from '@c8y/ngx-components/echart';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { BsDropdownModule } from 'ngx-bootstrap/dropdown';\nimport { aggregationType, IIdentified } from '@c8y/client';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\nimport { PopoverModule } from 'ngx-bootstrap/popover';\nimport { Interval } from '@c8y/ngx-components/interval-picker';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { SendAsWidgetToReportModal } from './send-as-widget-to-report-modal/send-as-widget-to-report-modal.component';\nimport { ContextDashboardService } from '@c8y/ngx-components/context-dashboard';\nimport {\n DatapointsExportSelectorComponent,\n ExportConfig\n} from '@c8y/ngx-components/datapoints-export-selector';\nimport { CreateNewReportModalComponent } from './create-new-report-modal/create-new-report-modal.component';\nimport { DataExplorerService } from './datapoint-explorer.service';\nimport { WorkspaceConfigComponent } from './configuration/workspace-configuration.component';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\n\n@Component({\n selector: 'c8y-datapoint-explorer',\n templateUrl: './datapoint-explorer.component.html',\n standalone: true,\n imports: [\n CoreModule,\n CommonModule,\n DatapointSelectorModule,\n AlarmEventSelectorModule,\n TooltipModule,\n PopoverModule,\n TimeContextComponent,\n ChartsComponent,\n BsDropdownModule,\n FormsModule,\n A11yModule,\n DatapointsExportSelectorComponent,\n WorkspaceConfigComponent\n ],\n providers: [ChartEventsService, ChartAlarmsService]\n})\nexport class DatapointExplorerComponent {\n @ViewChild('chart') chart: ElementRef;\n config: DatapointsGraphWidgetConfig = { datapoints: [], alarmsEventsConfigs: [] };\n exportConfig: ExportConfig;\n alerts: DynamicComponentAlertAggregator | undefined;\n alarms: AlarmDetailsExtended[] = [];\n events: EventDetailsExtended[] = [];\n datapointsOutOfSync = new Map<DatapointsGraphKPIDetails, boolean>();\n contextAsset: IIdentified;\n formGroup: FormGroup;\n hasAtLeastOneDatapointActive = true;\n hasAtLeastOneAlarmActive = true;\n isMarkedAreaEnabled = false;\n alarmsOrEventsHaveNoMatchingDps = false;\n canAddReport = false;\n timeProps: {\n dateFrom: Date;\n dateTo: Date;\n interval?: Interval['id'];\n realtime?: boolean;\n aggregation?: aggregationType | null;\n };\n datapointSelectDefaultFormOptions: Partial<DatapointAttributesFormConfig> = {\n showRange: true,\n showChart: true\n };\n\n #destroyRef = inject(DestroyRef);\n\n private readonly dataExplorerService = inject(DataExplorerService);\n private readonly formBuilder = inject(FormBuilder);\n private readonly alertService = inject(AlertService);\n private readonly bsModalService = inject(BsModalService);\n private readonly permissions = inject(Permissions);\n private readonly contextDashboardService = inject(ContextDashboardService);\n private readonly activatedRoute = inject(ActivatedRoute);\n private readonly contextRouteService = inject(ContextRouteService);\n\n constructor() {\n this.formGroup = this.initForm();\n }\n\n ngOnInit(): void {\n this.contextAsset = this.initializeContextSourceId();\n this.checkForMatchingDatapoints();\n this.canAddReport = this.permissions.hasAnyRole([\n Permissions.ROLE_INVENTORY_CREATE,\n Permissions.ROLE_INVENTORY_ADMIN,\n Permissions.ROLE_MANAGED_OBJECT_ADMIN,\n Permissions.ROLE_MANAGED_OBJECT_CREATE\n ]);\n this.formGroup.valueChanges\n .pipe(takeUntilDestroyed(this.#destroyRef))\n .subscribe(configChange => {\n const { alarms, events, ...configValues } = configChange;\n this.config = {\n ...configValues,\n alarmsEventsConfigs: [...(alarms || []), ...(events || [])]\n };\n this.updateExportConfig();\n this.checkForMatchingDatapoints();\n });\n }\n\n onTimeContextChange(timeProps: TimeContextProps): void {\n const realtime = this.formGroup.controls.realtime.value;\n if (timeProps.realtime !== realtime) {\n this.formGroup.patchValue({ realtime: timeProps.realtime });\n }\n if (timeProps.realtime) {\n if (timeProps.currentDateContextInterval !== this.formGroup.value.interval) {\n this.formGroup.patchValue({ interval: timeProps.currentDateContextInterval });\n }\n return;\n }\n this.formGroup.patchValue({\n dateFrom: timeProps.currentDateContextFromDate,\n dateTo: timeProps.currentDateContextToDate,\n interval: timeProps.currentDateContextInterval,\n aggregation: timeProps.aggregation || null\n });\n }\n\n onConfigurationChange(config: DatapointsGraphWidgetConfig): void {\n if (config.dateFrom && config.dateTo) {\n this.timeProps = {\n dateFrom: new Date(config?.dateFrom),\n dateTo: new Date(config?.dateTo),\n interval: config?.interval,\n realtime: config?.realtime,\n aggregation: config?.realtime ? null : config?.aggregation\n };\n }\n this.alarms = config.alarmsEventsConfigs.filter(ae => ae.timelineType === 'ALARM');\n this.events = config.alarmsEventsConfigs.filter(ae => ae.timelineType === 'EVENT');\n this.formGroup.patchValue({ alarms: this.alarms, events: this.events, ...config });\n }\n\n onSliderZoom(timeProps: { dateFrom: Date; dateTo: Date; interval?: Interval['id'] }) {\n this.formGroup.patchValue(timeProps);\n this.timeProps = { ...timeProps, realtime: false };\n }\n\n updateTimeRangeOnRealtime(\n timeRange: Pick<DatapointsGraphWidgetConfig, 'dateFrom' | 'dateTo'>\n ): void {\n this.formGroup.patchValue(timeRange, { emitEvent: false });\n }\n\n createNewReportWithWidget(): void {\n const modal = this.bsModalService.show(CreateNewReportModalComponent, {\n ignoreBackdropClick: true,\n keyboard: false,\n class: 'modal-sm'\n });\n const content = modal.content as CreateNewReportModalComponent;\n content.result\n .then(async report => {\n const reportDashboard = await this.dataExplorerService.fetchReportDashboard(report.id);\n const widget = this.createWidgetConfig();\n reportDashboard.c8y_Dashboard.children = { [widget.id]: widget };\n await this.contextDashboardService.update(reportDashboard);\n this.alertService.success(gettext('Report and widget created.'));\n })\n .catch(e => {\n if (e) {\n this.alertService.danger(gettext('Failed to create report and widget.'));\n this.alertService.addServerFailure(e);\n }\n // else: modal was closed\n });\n }\n\n sendAsWidgetToReport(): void {\n const modal = this.bsModalService.show(SendAsWidgetToReportModal, {\n ignoreBackdropClick: true,\n keyboard: false,\n class: 'modal-sm'\n });\n const content = modal.content as SendAsWidgetToReportModal;\n content.result\n .then(async reports => {\n const selectedReports = Object.entries(reports).filter(([, value]) => value);\n const reportPromises = selectedReports.map(async ([reportId]) => {\n const reportDashboard = await this.dataExplorerService.fetchReportDashboard(reportId);\n const widget = this.createWidgetConfig();\n const children = reportDashboard.c8y_Dashboard.children || {};\n reportDashboard.c8y_Dashboard.children = { ...children, [widget.id]: widget };\n await this.contextDashboardService.update(reportDashboard);\n });\n\n await Promise.all(reportPromises);\n this.alertService.success(gettext('Widget created.'));\n })\n .catch(e => {\n if (e) {\n this.alertService.danger(gettext('Failed to create widget.'));\n this.alertService.addServerFailure(e);\n }\n // else: modal was closed\n });\n }\n\n updateAlarmsAndEvents(alarmsEventsConfigs: AlarmOrEventExtended[]): void {\n this.alarms = alarmsEventsConfigs.filter(this.isAlarm);\n this.events = alarmsEventsConfigs.filter(this.isEvent);\n this.hasAtLeastOneAlarmActive = this.hasActiveAlarms(this.alarms);\n }\n\n handleDatapointOutOfSync(dpOutOfSync: DatapointsGraphKPIDetails): void {\n const key = (dp: KPIDetails) => dp.__target?.id + dp.fragment + dp.series;\n const dpMatch = this.config.datapoints?.find(dp => key(dp) === key(dpOutOfSync));\n if (!dpMatch) {\n return;\n }\n this.datapointsOutOfSync.set(dpMatch, true);\n }\n\n private isAlarm(item: AlarmOrEventExtended): item is AlarmDetailsExtended {\n return item.timelineType === 'ALARM';\n }\n\n private isEvent(item: AlarmOrEventExtended): item is EventDetailsExtended {\n return item.timelineType === 'EVENT';\n }\n\n private hasActiveAlarms(alarms: AlarmDetailsExtended[]): boolean {\n return alarms.length > 0 && alarms.some(alarm => alarm.__active);\n }\n\n private checkForMatchingDatapoints(): void {\n const allMatch = this.config?.alarmsEventsConfigs?.every(ae =>\n this.formGroup.value.datapoints?.some(dp => dp.__target?.id === ae.__target?.id)\n );\n\n queueMicrotask(() => {\n this.alarmsOrEventsHaveNoMatchingDps = !allMatch;\n });\n }\n\n private updateExportConfig(): void {\n const datapointDetails = this.config.datapoints\n .filter(({ __active }) => __active)\n .map(({ __target, fragment, series }) => ({\n deviceName: __target.name,\n source: __target.id,\n valueFragmentSeries: series,\n valueFragmentType: fragment\n }));\n\n if (datapointDetails.length === 0) {\n this.exportConfig = null;\n } else {\n this.exportConfig = {\n aggregation: this.config.aggregation,\n dateFrom: new Date(this.config.dateFrom).toISOString(),\n dateTo: new Date(this.config.dateTo).toISOString(),\n datapointDetails: datapointDetails\n };\n }\n }\n\n private initializeContextSourceId(): IIdentified {\n const routeContext = this.contextRouteService.getContextData(this.activatedRoute);\n if (!routeContext) {\n return;\n }\n const { context, contextData } = routeContext;\n if ([ViewContext.Device, ViewContext.Group].includes(context)) {\n return contextData;\n }\n }\n\n private createWidgetConfig(): any {\n return {\n name: 'Data points graph 2',\n title: gettext('Data points graph 2'),\n id: String(Math.random()).substr(2),\n _width: 24,\n _height: 5,\n config: this.config\n };\n }\n\n private initForm() {\n return this.formBuilder.group({\n datapoints: [this.config.datapoints || []],\n alarms: [this.alarms || []],\n events: [this.events || []],\n dateFrom: [this.config.dateFrom || (null as Date)],\n dateTo: [this.config.dateFrom || (null as Date)],\n interval: [this.config.interval || 'hours'],\n aggregation: [this.config.aggregation || (null as aggregationType | null)],\n realtime: [this.config.realtime || false],\n displayMarkedLine: [true, []],\n displayMarkedPoint: [true, []],\n mergeMatchingDatapoints: [true, []],\n showLabelAndUnit: [true, []],\n showSlider: [true, []],\n displayDateSelection: [false, []],\n yAxisSplitLines: [false],\n xAxisSplitLines: [false]\n });\n }\n}\n","<c8y-title>{{ 'Data explorer' | translate }}</c8y-title>\n\n<c8y-time-context\n (contextChange)=\"onTimeContextChange($event)\"\n [changedDateContext]=\"timeProps\"\n></c8y-time-context>\n\n<c8y-action-bar-item [placement]=\"'left'\">\n <c8y-workspace-config\n [updatedConfig]=\"config\"\n (onConfigurationChange)=\"onConfigurationChange($event)\"\n ></c8y-workspace-config>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item placement=\"right\">\n <c8y-datapoints-export-selector [exportConfig]=\"exportConfig\"></c8y-datapoints-export-selector>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-2000\"\n>\n <button\n title=\"{{ 'Send as widget to report' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--copy-dashboard\"\n (click)=\"sendAsWidgetToReport()\"\n [disabled]=\"config?.datapoints?.length === 0 || !canAddReport\"\n >\n <i c8yIcon=\"c8y-reports\"></i>\n <span>{{ 'Send as widget to report' | translate }}</span>\n </button>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-2000\"\n>\n <button\n title=\"{{ 'Create a new report with widget' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--copy-dashboard\"\n (click)=\"createNewReportWithWidget()\"\n [disabled]=\"config?.datapoints?.length === 0 || !canAddReport\"\n >\n <i c8yIcon=\"c8y-reports\"></i>\n <span>{{ 'Create a new report with widget' | translate }}</span>\n </button>\n</c8y-action-bar-item>\n\n<div class=\"content-fullpage d-grid grid_col--auto-360 gap-24\">\n <div class=\"d-col gap-16\">\n <c8y-charts\n class=\"flex-grow data-point-explorer\"\n #chart\n [config]=\"config\"\n [alerts]=\"alerts\"\n (updateAlarmsAndEvents)=\"updateAlarmsAndEvents($event)\"\n (configChangeOnZoomOut)=\"onSliderZoom($event)\"\n (datapointOutOfSync)=\"handleDatapointOutOfSync($event)\"\n (timeRangeChangeOnRealtime)=\"updateTimeRangeOnRealtime($event)\"\n (isMarkedAreaEnabled)=\"isMarkedAreaEnabled = $event\"\n ></c8y-charts>\n <form\n class=\"m-l-48 m-r-48\"\n [formGroup]=\"formGroup\"\n >\n <div class=\"d-grid-md grid__col--4-4-4 gap-16\">\n <fieldset class=\"c8y-fieldset form-group-sm m-t-md-0 p-b-8\">\n <legend>{{ 'Axis' | translate }}</legend>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Y axis helper lines' | translate\"\n >\n <input\n name=\"yAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"yAxisSplitLines\"\n />\n <span></span>\n <span translate>Y-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'X axis helper lines' | translate\"\n >\n <input\n name=\"xAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"xAxisSplitLines\"\n />\n <span></span>\n <span translate>X-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Merge matching data points into single axis' | translate\"\n >\n <input\n name=\"mergeMatchingDatapoints\"\n type=\"checkbox\"\n formControlName=\"mergeMatchingDatapoints\"\n />\n <span></span>\n <span translate>Merge matching data points into single axis</span>\n </label>\n </fieldset>\n <fieldset class=\"c8y-fieldset form-group-sm m-t-md-0 p-b-8\">\n <legend>{{ 'Alarms & events' | translate }}</legend>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show vertical line when alarm occurs' | translate\"\n >\n <input\n name=\"displayMarkedLine\"\n type=\"checkbox\"\n formControlName=\"displayMarkedLine\"\n />\n <span></span>\n <span translate>Show vertical line on every occurrence</span>\n </label>\n\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon when alarm or event occurs' | translate\"\n >\n <input\n name=\"displayMarkedPoint\"\n type=\"checkbox\"\n formControlName=\"displayMarkedPoint\"\n />\n <span></span>\n <span>\n {{\n 'Show icon when triggered`When alarms and events occur the icon will be shown`'\n | translate\n }}\n </span>\n <button\n class=\"btn-dot m-l-8\"\n [attr.aria-label]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n [tooltip]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n container=\"body\"\n type=\"button\"\n *ngIf=\"alarmsOrEventsHaveNoMatchingDps\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n >\n <i\n class=\"text-info\"\n c8yIcon=\"info\"\n ></i>\n </button>\n </label>\n </fieldset>\n <fieldset class=\"c8y-fieldset form-group-sm m-t-md-0 p-b-8\">\n <legend>{{ 'Chart' | translate }}</legend>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show labels and units' | translate\"\n >\n <input\n name=\"showLabelAndUnit\"\n type=\"checkbox\"\n formControlName=\"showLabelAndUnit\"\n />\n <span></span>\n <span translate>Display labels and units on Y-axis</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show slider' | translate\"\n >\n <input\n name=\"showSlider\"\n type=\"checkbox\"\n formControlName=\"showSlider\"\n />\n <span></span>\n <span translate>Show slider</span>\n </label>\n </fieldset>\n </div>\n </form>\n </div>\n <div class=\"card inner-scroll bg-level-1\">\n <form\n class=\"bg-inherit\"\n [formGroup]=\"formGroup\"\n >\n <c8y-datapoint-selection-list\n class=\"bg-inherit separator-top d-block\"\n name=\"datapoints\"\n [minActiveCount]=\"0\"\n [defaultFormOptions]=\"datapointSelectDefaultFormOptions\"\n [config]=\"{ contextAsset: contextAsset }\"\n formControlName=\"datapoints\"\n [allowDragAndDrop]=\"true\"\n ></c8y-datapoint-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-inherit\"\n name=\"alarms\"\n formControlName=\"alarms\"\n [timelineType]=\"'ALARM'\"\n [datapoints]=\"config.datapoints\"\n ></c8y-alarm-event-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-inherit\"\n name=\"events\"\n formControlName=\"events\"\n [timelineType]=\"'EVENT'\"\n [datapoints]=\"config.datapoints\"\n ></c8y-alarm-event-selection-list>\n </form>\n </div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1","i2","i4","i5","FormsModule"],"mappings":"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