UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

738 lines (733 loc) 97 kB
import * as i0 from '@angular/core'; import { Injectable, Pipe, Component, Input, EventEmitter, Output, ViewChild, NgModule } from '@angular/core'; import * as i5 from '@angular/forms'; import { FormControl, ControlContainer, NgForm, FormsModule as FormsModule$1, ReactiveFormsModule } from '@angular/forms'; import * as i2 from '@c8y/client'; import { Severity, AlarmStatus, SEVERITY_LABELS, ALARM_STATUS_LABELS } from '@c8y/client'; import * as i2$1 from '@c8y/ngx-components'; import { gettext, IntervalBasedReload, CountdownIntervalComponent, DismissAlertStrategy, DynamicComponentAlert, globalAutoRefreshLoading, AlarmRealtimeService, AlarmWithChildrenRealtimeService, CommonModule, CountdownIntervalModule, DocsModule, FormsModule } from '@c8y/ngx-components'; import * as i3 from '@c8y/ngx-components/alarms'; import { ALARM_STATUS_ICON, DEFAULT_STATUS_VALUES, DEFAULT_SEVERITY_VALUES, AlarmsModule } from '@c8y/ngx-components/alarms'; import * as i6 from '@ngx-translate/core'; import { omit, isEqual, isEmpty } from 'lodash-es'; import { BehaviorSubject, Subject } from 'rxjs'; import { startWith, pairwise, debounceTime, tap, takeUntil, skip } from 'rxjs/operators'; import * as i7 from '@angular/common'; import { CommonModule as CommonModule$1 } from '@angular/common'; import * as i8 from 'ngx-bootstrap/popover'; import { PopoverModule } from 'ngx-bootstrap/popover'; import * as i5$1 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { RouterModule } from '@angular/router'; const ALARM_ORDER_VALUES = { BY_ACTIVE: 'BY_ACTIVE', BY_DATE_ASCENDING: 'BY_DATE_ASCENDING', BY_DATE_DESCENDING: 'BY_DATE_DESCENDING', BY_SEVERITY: 'BY_SEVERITY' }; const ALARM_ORDER_LABELS = { BY_ACTIVE: gettext('By active status'), BY_DATE_ASCENDING: gettext('By date (ascending)'), BY_DATE_DESCENDING: gettext('By date (descending)'), BY_SEVERITY: gettext('By severity') }; const ASSET_ALARMS_WIDGET_ID = 'Asset Alarms'; const RECENT_ALARMS_WIDGET_ID = 'Recent Alarms'; const GLOBAL_INTERVAL_OPTION = 'global-interval'; var DATE_SELECTION; (function (DATE_SELECTION) { DATE_SELECTION["CONFIG"] = "config"; DATE_SELECTION["VIEW_AND_CONFIG"] = "view_and_config"; DATE_SELECTION["DASHBOARD_CONTEXT"] = "dashboard_context"; })(DATE_SELECTION || (DATE_SELECTION = {})); const DEFAULT_PAGE_SIZE = 20; class AlarmWidgetService { constructor(alarmsViewService) { this.alarmsViewService = alarmsViewService; } /** * Checks if the provided data follows the LegacyAlarmConfig structure. * * This function determines if a given data object is an instance of LegacyAlarmConfig * by checking for the presence of the 'options' property. * * @param data - The data object to be checked. * @returns - Returns `true` if the data object is a LegacyAlarmConfig, otherwise `false`. */ isOldAlarmConfigStructure(data) { return data !== null && typeof data === 'object' && 'options' in data; } /** * Creates predefined widget configuration object. * * This method creates a new configuration object based on * a widgets ID (that determines if is a legacy Recent or Critical alarms widget). * * @param isIntervalRefresh - determines a type of a refresh. * @param widgetId - determines if a config should be done for Recent or Critical alarms widget. * @returns The new, predefined configuration object. */ getPredefinedConfiguration(isIntervalRefresh, widgetId) { return { order: ALARM_ORDER_VALUES.BY_ACTIVE, severities: widgetId === RECENT_ALARMS_WIDGET_ID ? { [Severity.CRITICAL]: true, [Severity.MAJOR]: true, [Severity.MINOR]: true, [Severity.WARNING]: true } : { [Severity.CRITICAL]: true, [Severity.MAJOR]: false, [Severity.MINOR]: false, [Severity.WARNING]: false }, status: widgetId === RECENT_ALARMS_WIDGET_ID ? { [AlarmStatus.ACKNOWLEDGED]: true, [AlarmStatus.ACTIVE]: true, [AlarmStatus.CLEARED]: true } : { [AlarmStatus.ACKNOWLEDGED]: false, [AlarmStatus.ACTIVE]: true, [AlarmStatus.CLEARED]: false }, types: [''], isRealtime: !isIntervalRefresh, isAutoRefreshEnabled: true, refreshInterval: isIntervalRefresh ? this.alarmsViewService.DEFAULT_INTERVAL_VALUE : undefined }; } /** * Transforms a LegacyAlarmConfig object into an AlarmListWidgetConfig object. * * This function maps the properties from an old configuration structure (LegacyAlarmConfig) * to a new configuration structure (AlarmListWidgetConfig). * * @param oldConfig - The old configuration object to be transformed. * @returns - The new configuration object mapped from the old one. */ mapToNewConfigStructure(oldConfig, isIntervalRefresh) { const order = oldConfig.options.orderMode === 'ACTIVE_FIRST' ? ALARM_ORDER_VALUES.BY_ACTIVE : ALARM_ORDER_VALUES.BY_SEVERITY; if (!this.isContainingAllSeverityTypes(oldConfig.options.severity)) { oldConfig.options.severity = this.addAllMissingSeverityTypes(oldConfig.options.severity); } return { order: order, isRealtime: oldConfig.realtime, device: oldConfig.device, showAlarmsForChildren: false, severities: oldConfig.options.severity, status: this.allValuesFalse(oldConfig.options.status) ? { [AlarmStatus.ACKNOWLEDGED]: true, [AlarmStatus.ACTIVE]: true, [AlarmStatus.CLEARED]: true } : oldConfig.options.status, types: oldConfig.options.types?.length ? oldConfig.options.types : [''], isAutoRefreshEnabled: true, refreshInterval: isIntervalRefresh ? this.alarmsViewService.DEFAULT_INTERVAL_VALUE : undefined }; } /** * Checks if the provided severity object contains all the predefined severity types. * * @param severity - A record object where keys are severity type strings and values are boolean. * - This object is checked against the predefined severity types. * @returns `true` if all predefined severity types are present in the severity object; otherwise, `false`. */ isContainingAllSeverityTypes(severity) { return Object.keys(SEVERITY_LABELS).every(severityType => severityType in severity); } /** * Adds any missing severity types to the provided severity object with a default value of `false`. * * @param severity - A record object where keys are severity type strings and values are boolean. * - Missing severity types will be added to this object. * @returns The modified severity object, which includes all predefined severity types, adding any * that were missing with a value of `false`. */ addAllMissingSeverityTypes(severity) { Object.keys(SEVERITY_LABELS).forEach(severityType => { if (!(severityType in severity)) { severity[severityType] = false; } }); return severity; } /** * Maps an AlarmListWidgetConfig object to an AlarmQueryFilter. * * This function converts the provided AlarmListWidgetConfig into a format suitable for querying alarms. * * @param config - The configuration object for the alarm list widget. * @param pageSize - Optional number specifying the size of the pages to be returned in the query. * @returns - The query filter object constructed from the provided configuration. */ mapConfigToQueryFilter(config, pageSize) { const filter = { pageSize: pageSize || DEFAULT_PAGE_SIZE, query: this.getOrderParameters(config.order), severity: this.extractFilterParams(config.severities || {}), status: this.extractFilterParams(config.status || {}), type: (config.types || []).join(','), withTotalPages: true }; if (config.dateFilter) { filter.lastUpdatedFrom = typeof config.dateFilter[0] === 'string' ? config.dateFilter[0] : config.dateFilter[0].toISOString(); filter.createdTo = typeof config.dateFilter[1] === 'string' ? config.dateFilter[1] : config.dateFilter[1].toISOString(); } if (config.device) { filter.source = config.device.id; filter.withSourceAssets = true; filter.withSourceDevices = true; filter.withSourceAssets = config.showAlarmsForChildren ?? true; filter.withSourceDevices = config.showAlarmsForChildren ?? true; } return filter; } /** * Extracts and concatenates filter parameters from a given object. * * This function takes an object containing filter settings (either SeverityFilter * or AlarmStatusSettings) and returns a string of all keys where the corresponding value is true. * If the object is empty or null, an empty string is returned. * * @param obj - The object containing filter settings. * @returns - A concatenated string of keys with true values, separated by commas. */ extractFilterParams(obj) { if (!obj) { return ''; } return Object.entries(obj) .filter(([, value]) => value) .map(([key]) => key) .join(','); } /** * Determines if an incoming real-time alarm has a different status than an existing alarm. * * This function checks if the provided incoming real-time alarm's status differs * from that of an existing alarm with the same ID in the given array of alarms. * * @param existingAlarms - The array of existing alarms. * @param incomingRealtimeAlarm - The incoming real-time alarm to check. * @returns - True if the existing alarm's status has changed, otherwise false. */ hasExistingAlarmChangedStatus(existingAlarms, incomingRealtimeAlarm) { const existingAlarm = existingAlarms.find(alarm => alarm.id === incomingRealtimeAlarm.id); return !!existingAlarm && existingAlarm.status !== incomingRealtimeAlarm.status; } /** * Filters alarms based on their status, severity, and type. * * This method determines if a given alarm, identified either by a numeric ID or an `IAlarm` object, * matches specific criteria defined in `alarms` and optionally `config`. * * @param alarm - The alarm to check, represented either by a numeric ID or an `IAlarm` object. * @param alarms - An array of `IAlarm` objects against which the given alarm is evaluated. * @param config - Optional. Configuration for the alarm list widget, used to define additional * filtering criteria. * * @returns `true` if the alarm matches the specified criteria; otherwise, `false`. * If `alarm` is a number, it always returns `false`. * If `config` is not provided, it uses a legacy filter for critical alarms. * * @remarks * - When `alarm` is a numeric ID, the function returns `false` as it cannot match against type and severity. * - If `config` is not provided, the function assumes a legacy scenario for filtering all critical alarms. */ filterAlarmsByStatusSeverityAndType(alarm, alarms, config) { if (typeof alarm === 'number') { return false; } return this.isAlarmMatchedByConfig(alarm, alarms, config); } /** * Determines if all values in the given object are false. * * This function checks every value in the provided object to see if they are all false. * * @param obj - An object with boolean values. * @returns - Returns `true` if all values in the object are false, otherwise `false`. */ allValuesFalse(obj) { return Object.values(obj).every(value => !value); } /** * Constructs a string of order parameters for a query based on the specified alarm order. * * This function takes an alarm order and maps it to a corresponding set of order parameters. * It supports different ordering types, such as BY_ACTIVE, BY_SEVERITY, and BY_DATE_ASCENDING. * The order parameters are used to construct a query string that determines the order * in which alarms are retrieved or displayed. * * @param order - The specified order for sorting alarms (e.g., BY_ACTIVE). * @returns - A string of order parameters to be used in a query, or an empty string if the order type is unrecognized. */ getOrderParameters(order) { let orderParams; switch (order) { case ALARM_ORDER_VALUES.BY_ACTIVE: orderParams = ['status asc', 'severity asc', 'time.date desc', 'text asc']; return this.buildOrderParameters(orderParams); case ALARM_ORDER_VALUES.BY_SEVERITY: orderParams = ['severity asc', 'time.date desc', 'text asc']; return this.buildOrderParameters(orderParams); case ALARM_ORDER_VALUES.BY_DATE_ASCENDING: orderParams = ['time.date asc', 'text asc']; return this.buildOrderParameters(orderParams); default: orderParams = ['time.date desc', 'text asc']; return this.buildOrderParameters(orderParams); } } /** * Determines if an alarm is matched by the specified widget configuration. * * This function evaluates whether a given alarm should be included based on the severity, * status, and type filters defined in the AlarmListWidgetConfig. It checks if the alarm's * severity and status match the configuration settings and if the alarm's type is included * in the configuration's types (if specified). * * @ignore * @param alarm - The alarm to evaluate. * @param alarms - An array of existing alarms, used for status matching. * @param config - The configuration settings to match against. * @returns - Returns `true` if the alarm matches the configuration criteria; otherwise, `false`. */ isAlarmMatchedByConfig(alarm, alarms, config) { const isSeverityMatched = this.isSeverityMatching(alarm.severity, config); const isStatusMatched = this.isStatusMatching(alarm, alarms, config); const isTypeMatched = this.isTypesMatching(config, alarm); return isSeverityMatched && isStatusMatched && isTypeMatched; } /** * Checks if the severity of an alarm matches the configuration setting. * * This function determines whether the severity of an alarm is included in the * severity settings defined in the AlarmListWidgetConfig. * * @ignore * @param severity - The severity of the alarm to check. * @param config - The configuration with severity settings. * @returns - Returns `true` if the alarm's severity matches the configuration; otherwise, `false`. */ isSeverityMatching(severity, config) { return !!config.severities[severity]; } /** * Evaluates if the status of an alarm matches the configuration setting or has changed. * * This function checks if the status of an alarm is included in the status settings defined in * the AlarmListWidgetConfig, or if the alarm's status has changed based on the existing alarms. * * @ignore * @param alarm - The alarm whose status is to be evaluated. * @param alarms - An array of existing alarms to compare against for status changes. * @param config - The configuration with status settings. * @returns - Returns `true` if the alarm's status matches or has changed as per the configuration; otherwise, `false`. */ isStatusMatching(alarm, alarms, config) { return !!config.status[alarm.status] || this.hasExistingAlarmChangedStatus(alarms, alarm); } /** * Checks if the configuration's types array contains only empty string or includes a specific alarm type. * * @param config - The configuration object with a `types` property. * @param alarm - The alarm object with a `type` property to check against the config's types. * @returns `true` if the config's types array contains only empty string or includes the alarm's type, otherwise `false`. */ isTypesMatching(config, alarm) { return Array.isArray(config.types) && config.types.length === 1 && config.types[0] === '' ? true : config.types?.includes(alarm.type) ?? false; } /** * Constructs a query string from an array of order parameters. * * This function takes an array of ordering parameters and constructs a query string * for use in alarm ordering queries. The parameters are concatenated into a single string, * prefixed with '$orderby='. * * @ignore * @private * @param orderParams - The order parameters to be included in the query. * @returns - A query string representing the order parameters. */ buildOrderParameters(orderParams) { return `$orderby=${orderParams.join(',')}`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, deps: [{ token: i3.AlarmsViewService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i3.AlarmsViewService }] }); class SeverityIconPipe { transform(severity) { let severityClassName = ''; let iconClassName = ''; switch (severity) { case Severity.CRITICAL: severityClassName = 'critical'; iconClassName = 'exclamation-circle'; break; case Severity.MAJOR: severityClassName = 'major'; iconClassName = 'warning'; break; case Severity.MINOR: severityClassName = 'minor'; iconClassName = 'high-priority'; break; case Severity.WARNING: severityClassName = 'warning'; iconClassName = 'circle'; break; default: return { iconClass: '', severityClass: '' }; } return { iconClass: `status icon-lg stroked-icon dlt-c8y-icon-${iconClassName} ${severityClassName}`, c8yIcon: severityClassName }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, name: "severityIcon" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, decorators: [{ type: Pipe, args: [{ name: 'severityIcon' }] }] }); class SortingDescriptionPopoverMessagePipe { transform(order) { switch (order) { case ALARM_ORDER_VALUES.BY_ACTIVE: return gettext('Order alarms by active status, severity, and time.'); case ALARM_ORDER_VALUES.BY_DATE_ASCENDING: return gettext('Order alarms by time, starting with the oldest ones.'); case ALARM_ORDER_VALUES.BY_DATE_DESCENDING: return gettext('Order alarms by time, starting with the latest ones.'); case ALARM_ORDER_VALUES.BY_SEVERITY: return gettext('Order alarms by severity and time.'); default: return ''; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, name: "sortingDescriptionPopoverMessage" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, decorators: [{ type: Pipe, args: [{ name: 'sortingDescriptionPopoverMessage' }] }] }); class AlarmListWidgetConfigComponent { constructor(alarmListWidgetService, alarmService, alarmsViewService, alertService, form, formBuilder, translateService) { this.alarmListWidgetService = alarmListWidgetService; this.alarmService = alarmService; this.alarmsViewService = alarmsViewService; this.alertService = alertService; this.form = form; this.formBuilder = formBuilder; this.translateService = translateService; this.REFRESH_INTERVAL_IN_MILLISECONDS = this.alarmsViewService.DEFAULT_INTERVAL_VALUES; this.SEVERITY_LABELS = SEVERITY_LABELS; this.STATUS_LABELS = ALARM_STATUS_LABELS; this.ACKNOWLEDGE_STATUS_VALUE = AlarmStatus.ACKNOWLEDGED; this.CLEARED_STATUS_VALUE = AlarmStatus.CLEARED; this.BELL_SLASH_ICON = ALARM_STATUS_ICON.BELL_SLASH; this.BELL_ICON = ALARM_STATUS_ICON.BELL; this.C8Y_ALERT_IDLE_ICON = ALARM_STATUS_ICON.ALERT_IDLE; this.ALARM_ORDER_LABELS = ALARM_ORDER_LABELS; this.GLOBAL_INTERVAL_OPTION = GLOBAL_INTERVAL_OPTION; this.alarms$ = new BehaviorSubject(null); this.orderList = Object.values(ALARM_ORDER_VALUES); this.severityList = Object.keys(SEVERITY_LABELS); this.showDateFilter = true; this.DATE_SELECTION = DATE_SELECTION; this.dateSelectionHelp = this.translateService.instant(gettext(`Choose how to select a date range, the available options are: <ul class="m-l-0 p-l-8 m-t-8 m-b-0"> <li> <b>Widget configuration:</b> restricts the date selection only to the widget configuration </li> <li> <b>Widget and widget configuration:</b> restricts the date selection to the widget view and widget configuration only </li> <li> <b>Dashboard time range:</b> restricts date selection to the global dashboard configuration only </li> </ul>`)); /** * Order does matter. */ this.statusList = [AlarmStatus.ACTIVE, AlarmStatus.ACKNOWLEDGED, AlarmStatus.CLEARED]; } async ngOnInit() { this.refreshTypeTitle = this.config.isRealtime ? this.translateService.instant(gettext('Realtime')) : this.translateService.instant(gettext('Auto refresh')); this.refreshTypePopoverMessage = this.config.isRealtime ? this.translateService.instant(gettext('Change the default state of realtime refresh.')) : this.translateService.instant(gettext('Change the state of interval automatic refresh and set the refresh frequency.')); this.initializeForm(); if (!this.config.isRealtime) { this.updateConfigBasedOnRefreshOption(); } this.config.isRealtime = !this.alarmsViewService.isIntervalRefresh(); const isWidgetWithExistingConfig = this.config.order && this.config.severities && this.config.status; if (isWidgetWithExistingConfig) { await this.getAlarms(this.config); } else { /** Used when creating a new widget */ await this.getAlarms(this.formGroup.value); } this.handlePreviewSubscription(); } ngOnChanges(changes) { if (this.formGroup && changes.config) { this.formGroup.get('device').patchValue(this.config.device); } } ngOnDestroy() { this.formChangesSubscription?.unsubscribe(); } onBeforeSave(config) { const { types } = this.formGroup.value; if (types.length > 1) { const stringTypes = types; if (this.isContainingOnlyEmptyTypes(stringTypes)) { this.formGroup.value.types = ['']; } else { this.formGroup.value.types = this.filterEmptyTypes(stringTypes); } } /** * Applies only to converted legacy Alarm list widget */ if (config['options']) { delete config['options']; } Object.assign(config, this.formGroup.value); return true; } onDateFilterChange(event) { this.formGroup.patchValue({ dateFilter: event.selectedDates, interval: event.interval }); } removeType(index) { if (this.types.controls.length === 1) { this.formGroup.get('types').reset(); } else { this.types.removeAt(index); } } addType() { this.types.push(this.formBuilder.control('')); } updateRefreshOption() { this.config.refreshOption = this.refreshOption; this.formGroup.controls.refreshOption.setValue(this.refreshOption); const isGlobalInterval = this.refreshOption === GLOBAL_INTERVAL_OPTION; this.config.widgetInstanceGlobalAutoRefreshContext = isGlobalInterval; this.formGroup.controls.widgetInstanceGlobalAutoRefreshContext.setValue(isGlobalInterval); this.updateConfigBasedOnRefreshOption(); } get types() { return this.formGroup.get('types'); } dateSelectionChange(dateSelection) { if (dateSelection === DATE_SELECTION.CONFIG) { this.showDateFilter = true; this.formGroup.patchValue({ displayDateSelection: false, widgetInstanceGlobalTimeContext: false }); } else if (dateSelection === DATE_SELECTION.VIEW_AND_CONFIG) { this.showDateFilter = true; this.formGroup.patchValue({ displayDateSelection: true, widgetInstanceGlobalTimeContext: false }); } else if (dateSelection === DATE_SELECTION.DASHBOARD_CONTEXT) { this.showDateFilter = false; this.formGroup.patchValue({ displayDateSelection: false, widgetInstanceGlobalTimeContext: true }); } } filterEmptyTypes(types) { return types.filter((element) => element !== '' && element !== null); } isContainingOnlyEmptyTypes(types) { return types.every((element) => element === '' || element === null); } async getAlarms(config) { try { this.isLoading = true; const result = await this.alarmService.list(this.alarmListWidgetService.mapConfigToQueryFilter(config, DEFAULT_PAGE_SIZE)); this.alarms$.next(result); } catch (error) { this.alertService.addServerFailure(error); } finally { this.isLoading = false; } } initializeForm() { this.formGroup = this.createForm(); this.form.form.addControl('config', this.formGroup); this.formGroup.patchValue(this.config); this.initializeTypes(this.config.types); this.initDateSelection(); this.refreshOption = this.config.refreshOption ?? 'interval'; } createForm() { return this.formBuilder.group({ status: this.formBuilder.group(DEFAULT_STATUS_VALUES, { validators: this.minSelectedCheckboxes(1) }), showAlarmsForChildren: true, types: this.formBuilder.array([]), severities: this.formBuilder.group(DEFAULT_SEVERITY_VALUES, { validators: this.minSelectedCheckboxes(1) }), order: ALARM_ORDER_VALUES.BY_ACTIVE, isAutoRefreshEnabled: [true], refreshInterval: !this.config.isRealtime ? new FormControl({ value: this.alarmsViewService.DEFAULT_INTERVAL_VALUE, disabled: this.isAutorefershDisabled() }) : new FormControl(undefined), device: this.config.device ? new FormControl(this.config.device) : new FormControl(undefined), dateFilter: this.config.dateFilter ? new FormControl(this.config.dateFilter) : new FormControl([new Date(0), new Date()]), displayDateSelection: this.config.displayDateSelection || false, widgetInstanceGlobalTimeContext: this.config.widgetInstanceGlobalTimeContext || false, interval: this.config.interval || 'none', refreshOption: this.config.isRealtime ? null : 'interval', widgetInstanceGlobalAutoRefreshContext: this.config.refreshOption === GLOBAL_INTERVAL_OPTION }); } isAutorefershDisabled() { /**This check is required on widget creation */ if (this.config.isAutoRefreshEnabled === undefined) { return false; } return !this.config.isAutoRefreshEnabled; } minSelectedCheckboxes(min = 1) { const validator = (formGroup) => { const totalSelected = Object.values(formGroup.controls).reduce((prev, next) => (next.value ? prev + next.value : prev), 0); return totalSelected >= min ? null : { required: true }; }; return validator; } initializeTypes(types) { const typesControl = this.formGroup.get('types'); if (types) { types.forEach(type => { typesControl.push(this.formBuilder.control(type)); }); } else { typesControl.push(this.formBuilder.control('')); } } initDateSelection() { this.dateSelection = this.config?.widgetInstanceGlobalTimeContext ? DATE_SELECTION.DASHBOARD_CONTEXT : this.config?.displayDateSelection ? DATE_SELECTION.VIEW_AND_CONFIG : DATE_SELECTION.CONFIG; this.dateSelectionChange(this.dateSelection); const interval = this.config?.interval ?? 'none'; if (interval === 'none' || interval === 'custom') { return; } this.config.dateFilter = this.alarmsViewService.getDateTimeContextByInterval(this.config.interval); this.formGroup.patchValue({ dateFilter: this.config.dateFilter }); } /** * Initializes and handles the form change subscription for the Alarm list preview at Widget's configuration. * * This method subscribes to form value changes with a debounce time of 500ms. * It compares the previous and current form values to determine if the changes are relevant. * If the only change is in the `isAutoRefreshEnabled` property, the change is ignored. */ handlePreviewSubscription() { this.formChangesSubscription = this.formGroup.valueChanges .pipe(startWith(this.formGroup.value), pairwise(), debounceTime(500)) .subscribe(([previousConfig, currentConfig]) => { const typedPreviousConfig = previousConfig; const typedCurrentConfig = currentConfig; if (this.isOnlyAutoRefreshOrIntervalChanged(typedPreviousConfig, typedCurrentConfig)) { return; } if (this.alarmListWidgetService.allValuesFalse(typedCurrentConfig.severities) || this.alarmListWidgetService.allValuesFalse(typedCurrentConfig.status)) { this.alarms$.next({ data: [], res: null }); return; } this.getAlarms(typedCurrentConfig); }); } /** * Determines if the only changes between two `AlarmListWidgetConfig` objects are in the `isAutoRefreshEnabled` and `refreshInterval` properties. * * This method compares two configurations by omitting `isAutoRefreshEnabled` and `refreshInterval`. * It returns true if these are the only properties that differ between the previous and current configurations. * Note that `refreshInterval` is relevant only when the refresh type is set to an interval. * * @param previousConfig - The previous configuration of `AlarmListWidgetConfig`. * @param currentConfig - The current configuration of `AlarmListWidgetConfig`. * @returns `true` if the differences are confined to `isAutoRefreshEnabled` and `refreshInterval`, `false` otherwise. */ isOnlyAutoRefreshOrIntervalChanged(previousConfig, currentConfig) { const prevConfigCopy = omit(previousConfig, 'isAutoRefreshEnabled', 'refreshInterval'); const currentConfigCopy = omit(currentConfig, 'isAutoRefreshEnabled', 'refreshInterval'); return isEqual(prevConfigCopy, currentConfigCopy); } updateConfigBasedOnRefreshOption() { const isInterval = this.refreshOption === 'interval'; this.config.isAutoRefreshEnabled = isInterval; this.formGroup.controls.isAutoRefreshEnabled.setValue(isInterval); isInterval ? this.formGroup.get('refreshInterval').enable() : this.formGroup.get('refreshInterval').disable(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmListWidgetConfigComponent, deps: [{ token: AlarmWidgetService }, { token: i2.AlarmService }, { token: i3.AlarmsViewService }, { token: i2$1.AlertService }, { token: i5.NgForm }, { token: i5.FormBuilder }, { token: i6.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: AlarmListWidgetConfigComponent, selector: "c8y-alarm-list-widget-config", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"p-l-24 p-r-24\"\n [style.pointer-events]=\"'auto'\"\n [style.opacity]=\"1\"\n></div>\n\n<form\n class=\"row\"\n [formGroup]=\"formGroup\"\n>\n <div class=\"form-group col-md-12\">\n <div class=\"d-flex a-i-center\">\n <label\n class=\"m-b-0\"\n translate\n >\n Date selection\n </label>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"dateSelectionHelpTemplate\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n [adaptivePosition]=\"false\"\n ></button>\n </div>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control input-sm\"\n [ngModel]=\"dateSelection\"\n (ngModelChange)=\"dateSelectionChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n >\n <option\n title=\"{{ 'Widget configuration' | translate }}\"\n [value]=\"DATE_SELECTION.CONFIG\"\n >\n {{ 'Widget configuration' | translate }}\n </option>\n <option\n title=\"{{ 'Widget and widget configuration' | translate }}\"\n [value]=\"DATE_SELECTION.VIEW_AND_CONFIG\"\n >\n {{ 'Widget and widget configuration' | translate }}\n </option>\n <option\n title=\"{{ 'Dashboard time range' | translate }}\"\n [value]=\"DATE_SELECTION.DASHBOARD_CONTEXT\"\n >\n {{ 'Dashboard time range' | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n <div class=\"col-md-6\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Order`of items on a list, noun`' | translate }}</legend>\n <c8y-form-group class=\"m-b-0\">\n <div\n class=\"d-flex m-b-8 a-i-center\"\n *ngFor=\"let order of orderList; let i = index\"\n >\n <label\n class=\"c8y-radio gap-4\"\n title=\"{{ ALARM_ORDER_LABELS[order] | translate }}\"\n >\n <input\n type=\"radio\"\n [value]=\"order\"\n formControlName=\"order\"\n />\n <span class=\"a-s-center\"></span>\n <span class=\"text-truncate\">{{ ALARM_ORDER_LABELS[order] | translate }}</span>\n </label>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"order | sortingDescriptionPopoverMessage | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </div>\n </c8y-form-group>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n formArrayName=\"severities\"\n >\n <legend>{{ 'Severities' | translate }}</legend>\n <c8y-form-group\n [hasError]=\"formGroup.controls.severities.hasError('required')\"\n [ngClass]=\"{\n 'm-b-8': !formGroup.controls.severities.hasError('required')\n }\"\n >\n <ng-container *ngFor=\"let severityOption of severityList\">\n <label\n class=\"c8y-checkbox m-t-0 gap-4\"\n [title]=\"SEVERITY_LABELS[severityOption] | translate\"\n >\n <input\n type=\"checkbox\"\n [formControlName]=\"severityOption\"\n [name]=\"severityOption\"\n />\n <span class=\"a-s-center\"></span>\n <ng-container *ngIf=\"{ result: severityOption | severityIcon } as severityIcon\">\n <i\n class=\"a-s-center m-r-4 icon-20 {{ severityIcon.result.iconClass }}\"\n [c8yIcon]=\"severityIcon.result.c8yIcon\"\n ></i>\n </ng-container>\n <span>{{ SEVERITY_LABELS[severityOption] | translate }}</span>\n </label>\n </ng-container>\n <c8y-messages>\n <c8y-message *ngIf=\"formGroup.controls.severities.hasError('required')\">\n {{ 'Select at least one severity.' | translate }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n <ng-container *ngIf=\"showDateFilter\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Date filter' | translate }}</legend>\n <c8y-form-group class=\"m-b-8\">\n <c8y-alarms-date-filter\n [updateQueryParams]=\"false\"\n [DEFAULT_INTERVAL]=\"config.interval || 'none'\"\n formControlName=\"dateFilter\"\n (dateFilterChange)=\"onDateFilterChange($event)\"\n ></c8y-alarms-date-filter>\n </c8y-form-group>\n </fieldset>\n </ng-container>\n </div>\n\n <div class=\"col-md-6\">\n <fieldset\n class=\"c8y-fieldset\"\n formArrayName=\"status\"\n >\n <legend>{{ 'Status' | translate }}</legend>\n <c8y-form-group\n [hasError]=\"formGroup.controls.status.hasError('required')\"\n [ngClass]=\"{\n 'm-b-8': !formGroup.controls.status.hasError('required')\n }\"\n >\n <ng-container *ngFor=\"let statusOption of statusList\">\n <label\n class=\"c8y-checkbox m-t-0 gap-4\"\n [title]=\"STATUS_LABELS[statusOption] | translate\"\n >\n <input\n type=\"checkbox\"\n [formControlName]=\"statusOption\"\n [name]=\"statusOption\"\n />\n <span class=\"a-s-center\"></span>\n <i\n class=\"a-s-center m-l-4 m-r-4 text-gray-dark c8y-icon icon-20\"\n [c8yIcon]=\"\n statusOption === CLEARED_STATUS_VALUE\n ? C8Y_ALERT_IDLE_ICON\n : statusOption === ACKNOWLEDGE_STATUS_VALUE\n ? BELL_SLASH_ICON\n : BELL_ICON\n \"\n ></i>\n <span>{{ STATUS_LABELS[statusOption] | translate }}</span>\n </label>\n </ng-container>\n <c8y-messages>\n <c8y-message *ngIf=\"formGroup.controls.status.hasError('required')\">\n {{ 'Select at least one status.' | translate }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Child devices' | translate }}</legend>\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-switch\"\n [title]=\"'Show alarms from child devices' | translate\"\n >\n <input\n type=\"checkbox\"\n formControlName=\"showAlarmsForChildren\"\n />\n <span></span>\n <span>{{ 'Show alarms' | translate }}</span>\n <span class=\"sr-only\">{{ 'Show alarms' | translate }}</span>\n </label>\n </c8y-form-group>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Types' | translate }}</legend>\n <c8y-form-group\n class=\"m-b-8\"\n formArrayName=\"types\"\n >\n <div\n class=\"input-group\"\n *ngFor=\"let type of types.controls; let i = index\"\n >\n <input\n class=\"form-control\"\n type=\"text\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'c8y_Alarm' }\"\n [formControlName]=\"i\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-dot btn-dot--danger m-l-auto\"\n [title]=\"'Remove' | translate\"\n type=\"button\"\n (click)=\"removeType(i)\"\n [disabled]=\"types.controls?.length === 1 && !type.value\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n <div\n class=\"input-group-btn\"\n *ngIf=\"i === types.controls.length - 1\"\n >\n <button\n class=\"btn btn-dot btn-dot--primary m-l-auto\"\n [title]=\"'Add alarm type' | translate\"\n type=\"button\"\n (click)=\"addType()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n </button>\n </div>\n </div>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset\">\n <legend class=\"d-flex\">\n {{ refreshTypeTitle }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"refreshTypePopoverMessage\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </legend>\n <c8y-form-group class=\"m-b-8 form-group-sm\">\n <div class=\"d-flex gap-8 a-i-center\">\n <ng-template #realtime>\n <label class=\"c8y-switch\">\n <input\n id=\"refreshToggle\"\n name=\"isAutoRefreshEnabled\"\n type=\"checkbox\"\n formControlName=\"isAutoRefreshEnabled\"\n />\n <span></span>\n <span class=\"sr-only\">{{ 'Realtime refresh' | translate }}</span>\n </label>\n </ng-template>\n <div\n class=\"c8y-select-wrapper\"\n *ngIf=\"!config.isRealtime; else realtime\"\n >\n <select\n class=\"form-control\"\n title=\"{{ 'Refresh options`options for refreshing a view`' | translate }}\"\n [(ngModel)]=\"refreshOption\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"updateRefreshOption()\"\n >\n <option value=\"none\">\n {{ 'No automatic refresh' | translate }}\n </option>\n <option\n [title]=\"'Refreshing after the given interval' | translate\"\n value=\"interval\"\n >\n {{ 'Use refresh interval' | translate }}\n </option>\n <option\n [title]=\"'Refreshing after the given interval, synchronized globally' | translate\"\n value=\"global-interval\"\n >\n {{ 'Use global refresh interval' | translate }}\n </option>\n </select>\n </div>\n <ng-container\n *ngIf=\"!config.isRealtime && config.refreshOption !== GLOBAL_INTERVAL_OPTION\"\n >\n <label\n class=\"m-b-0\"\n for=\"refreshInterval\"\n >\n {{ 'Interval' | translate }}\n </label>\n <div class=\"c8y-select-wrapper flex-grow\">\n <select\n class=\"form-control text-12\"\n [title]=\"'Refresh interval in seconds' | translate\"\n id=\"refreshInterval\"\n formControlName=\"refreshInterval\"\n >\n <option\n *ngFor=\"let refreshInterval of REFRESH_INTERVAL_IN_MILLISECONDS\"\n [ngValue]=\"refreshInterval\"\n >\n {{ '{{ seconds }} s' | translate: { seconds: refreshInterval / 1000 } }}\n </option>\n </select>\n </div>\n </ng-container>\n </div>\n </c8y-form-group>\n </fieldset>\n </div>\n</form>\n\n<div class=\"legend form-block m-b-0\">{{ 'Preview`of an alarm list`' | translate }}</div>\n<c8y-alarms-list\n [alarms]=\"alarms$ | async\"\n [isInitialLoading]=\"isLoading\"\n [navigationOptions]=\"{\n allowNavigationToAlarmsView: false,\n alwaysNavigateToAllAlarms: false,\n includeClearedQueryParams: false,\n queryParamsHandling: 'merge'\n }\"\n></c8y-alarms-list>\n\n<ng-template #dateSelectionHelpTemplate>\n <div [innerHTML]=\"dateSelectionHelp\"></div>\n</ng-template>\n", dependencies: [{ kind: "component", type: i3.AlarmsListComponent, selector: "c8y-alarms-list", inputs: ["alarms", "hasPermissions", "typeFilters", "loadMoreMode", "navigationOptions", "isInitialLoading", "splitView"], outputs: ["onSelectedAlarm", "onScrollingStateChange"] }, { kind: "component", type: i3.AlarmsDateFilterComponent, selector: "c8y-alarms-date-filter", inputs: ["DEFAULT_INTERVAL", "updateQueryParams", "date"], outputs: ["dateFilterChange"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i2$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.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: i5.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i5.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i5.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2$1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i2$1.MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: i2$1.MessagesCo