@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 78.2 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-widgets-implementations-alarms.mjs","sources":["../../widgets/implementations/alarms/alarm-list-widget.model.ts","../../widgets/implementations/alarms/alarm-widget.service.ts","../../widgets/implementations/alarms/severity-icon.pipe.ts","../../widgets/implementations/alarms/sorting-description-popover-message.pipe.ts","../../widgets/implementations/alarms/alarm-list-widget-config/alarm-list-widget-config.component.ts","../../widgets/implementations/alarms/alarm-list-widget-config/alarm-list-widget-config.component.html","../../widgets/implementations/alarms/alarm-list-widget-view/alarm-list-widget.component.ts","../../widgets/implementations/alarms/alarm-list-widget-view/alarm-list-widget.component.html","../../widgets/implementations/alarms/alarms-widget.module.ts","../../widgets/implementations/alarms/c8y-ngx-components-widgets-implementations-alarms.ts"],"sourcesContent":["import type { AlarmStatusSettings, SeverityFilter } from '@c8y/client';\nimport { GlobalAutoRefreshWidgetConfig } from '@c8y/ngx-components';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport type { GlobalContextState } from '@c8y/ngx-components/global-context';\n\nexport const ALARM_ORDER_VALUES = {\n BY_ACTIVE: 'BY_ACTIVE',\n BY_DATE_ASCENDING: 'BY_DATE_ASCENDING',\n BY_DATE_DESCENDING: 'BY_DATE_DESCENDING',\n BY_SEVERITY: 'BY_SEVERITY'\n} as const;\n\nexport const ALARM_ORDER_LABELS = {\n BY_ACTIVE: gettext('By active status') as 'By active status',\n BY_DATE_ASCENDING: gettext('By date (ascending)') as 'By date (ascending)',\n BY_DATE_DESCENDING: gettext('By date (descending)') as 'By date (descending)',\n BY_SEVERITY: gettext('By severity') as 'By severity'\n} as const;\n\nexport type AlarmOrderType = keyof typeof ALARM_ORDER_VALUES;\n\nexport type SelectedDevice = { id: string; name: string };\n\nexport const ASSET_ALARMS_WIDGET_ID = 'Asset Alarms';\n\nexport const RECENT_ALARMS_WIDGET_ID = 'Recent Alarms';\n\n// TODO: remove\nexport type AlarmListRefreshOption = 'none' | 'interval' | 'global-interval';\n\n// TODO: remove\nexport const GLOBAL_INTERVAL_OPTION: AlarmListRefreshOption = 'global-interval';\n\nexport type LegacyAlarmListConfig = {\n realtime: boolean;\n options: {\n severity: SeverityFilter;\n types: string[];\n orderMode: string;\n device: string;\n status: AlarmStatusSettings;\n };\n device?: {\n name: string;\n id: string;\n };\n};\n\nexport interface AlarmListWidgetConfig\n extends Partial<GlobalContextState>,\n GlobalAutoRefreshWidgetConfig {\n order: AlarmOrderType;\n showAlarmsForChildren?: boolean;\n device?: SelectedDevice | null;\n severities: SeverityFilter;\n status: AlarmStatusSettings;\n types?: string[];\n isRealtime?: boolean;\n realtime?: boolean;\n widgetId?: string;\n}\n","import { Injectable } from '@angular/core';\nimport {\n AlarmQueryFilter,\n AlarmStatusSettings,\n AlarmStatus,\n IAlarm,\n Severity,\n SeverityFilter,\n SEVERITY_LABELS\n} from '@c8y/client';\n\nimport { SeveritySettings, SeverityType } from '@c8y/client';\nimport { AlarmsViewService } from '@c8y/ngx-components/alarms';\nimport {\n ALARM_ORDER_VALUES,\n AlarmListWidgetConfig,\n AlarmOrderType,\n LegacyAlarmListConfig,\n RECENT_ALARMS_WIDGET_ID\n} from './alarm-list-widget.model';\n\nexport const DEFAULT_PAGE_SIZE = 20;\n\n@Injectable({ providedIn: 'root' })\nexport class AlarmWidgetService {\n constructor(private alarmsViewService: AlarmsViewService) {}\n\n /**\n * Checks if the provided data follows the LegacyAlarmConfig structure.\n *\n * This function determines if a given data object is an instance of LegacyAlarmConfig\n * by checking for the presence of the 'options' property.\n *\n * @param data - The data object to be checked.\n * @returns - Returns `true` if the data object is a LegacyAlarmConfig, otherwise `false`.\n */\n isOldAlarmConfigStructure(\n data: LegacyAlarmListConfig | AlarmListWidgetConfig\n ): data is LegacyAlarmListConfig {\n return data !== null && typeof data === 'object' && 'options' in data;\n }\n\n /**\n * Creates predefined widget configuration object.\n *\n * This method creates a new configuration object based on\n * a widgets ID (that determines if is a legacy Recent or Critical alarms widget).\n *\n * @param isIntervalRefresh - determines a type of a refresh.\n * @param widgetId - determines if a config should be done for Recent or Critical alarms widget.\n * @returns The new, predefined configuration object.\n */\n getPredefinedConfiguration(widgetId?: string, existingConfig?: any): AlarmListWidgetConfig {\n // Check if any time/refresh configuration exists (cast to any for legacy properties)\n const hasAnyTimeConfig =\n existingConfig?.dateTimeContext ||\n existingConfig?.dateFilter ||\n existingConfig?.date ||\n existingConfig?.dateFrom ||\n existingConfig?.dateTo ||\n existingConfig?.refreshOption ||\n existingConfig?.isAutoRefreshEnabled !== undefined ||\n existingConfig?.refreshInterval ||\n existingConfig?.aggregation !== undefined;\n\n const config: AlarmListWidgetConfig = {\n order: ALARM_ORDER_VALUES.BY_ACTIVE,\n severities:\n widgetId === RECENT_ALARMS_WIDGET_ID\n ? ({\n [Severity.CRITICAL]: true,\n [Severity.MAJOR]: true,\n [Severity.MINOR]: true,\n [Severity.WARNING]: true\n } as SeveritySettings)\n : ({\n [Severity.CRITICAL]: true,\n [Severity.MAJOR]: false,\n [Severity.MINOR]: false,\n [Severity.WARNING]: false\n } as SeveritySettings),\n status:\n widgetId === RECENT_ALARMS_WIDGET_ID\n ? ({\n [AlarmStatus.ACKNOWLEDGED]: true,\n [AlarmStatus.ACTIVE]: true,\n [AlarmStatus.CLEARED]: true\n } as AlarmStatusSettings)\n : ({\n [AlarmStatus.ACKNOWLEDGED]: false,\n [AlarmStatus.ACTIVE]: true,\n [AlarmStatus.CLEARED]: false\n } as AlarmStatusSettings),\n types: [''],\n widgetId: widgetId || ''\n };\n\n // Only add global context defaults if no time/refresh config exists at all\n if (!hasAnyTimeConfig) {\n config.isAutoRefreshEnabled = true;\n config.refreshInterval = this.alarmsViewService.DEFAULT_INTERVAL_VALUE;\n config.aggregation = null;\n config.refreshOption = 'live';\n config.dateTimeContext = {\n dateFrom: new Date(0).toISOString(),\n dateTo: new Date().toISOString(),\n interval: 'custom'\n };\n }\n\n return config;\n }\n\n /**\n * Transforms a LegacyAlarmConfig object into an AlarmListWidgetConfig object.\n *\n * This function maps the properties from an old configuration structure (LegacyAlarmConfig)\n * to a new configuration structure (AlarmListWidgetConfig).\n *\n * @param oldConfig - The old configuration object to be transformed.\n * @returns - The new configuration object mapped from the old one.\n */\n mapToNewConfigStructure(oldConfig: LegacyAlarmListConfig): AlarmListWidgetConfig {\n const order: AlarmOrderType =\n oldConfig.options.orderMode === 'ACTIVE_FIRST'\n ? ALARM_ORDER_VALUES.BY_ACTIVE\n : ALARM_ORDER_VALUES.BY_SEVERITY;\n\n if (!this.isContainingAllSeverityTypes(oldConfig.options.severity)) {\n oldConfig.options.severity = this.addAllMissingSeverityTypes(oldConfig.options.severity);\n }\n\n return {\n order: order,\n isRealtime: oldConfig.realtime,\n device: oldConfig.device,\n showAlarmsForChildren: true,\n severities: oldConfig.options.severity,\n status: this.allValuesFalse(oldConfig.options.status)\n ? ({\n [AlarmStatus.ACKNOWLEDGED]: true,\n [AlarmStatus.ACTIVE]: true,\n [AlarmStatus.CLEARED]: true\n } as AlarmStatusSettings)\n : oldConfig.options.status,\n types: oldConfig.options.types?.length ? oldConfig.options.types : [''],\n isAutoRefreshEnabled: true,\n refreshInterval: this.alarmsViewService.DEFAULT_INTERVAL_VALUE\n };\n }\n\n /**\n * Checks if the provided severity object contains all the predefined severity types.\n *\n * @param severity - A record object where keys are severity type strings and values are boolean.\n * - This object is checked against the predefined severity types.\n * @returns `true` if all predefined severity types are present in the severity object; otherwise, `false`.\n */\n isContainingAllSeverityTypes(severity: SeverityFilter): boolean {\n return Object.keys(SEVERITY_LABELS).every(severityType => severityType in severity);\n }\n\n /**\n * Adds any missing severity types to the provided severity object with a default value of `false`.\n *\n * @param severity - A record object where keys are severity type strings and values are boolean.\n * - Missing severity types will be added to this object.\n * @returns The modified severity object, which includes all predefined severity types, adding any\n * that were missing with a value of `false`.\n */\n addAllMissingSeverityTypes(severity: SeverityFilter): SeverityFilter {\n Object.keys(SEVERITY_LABELS).forEach(severityType => {\n if (!(severityType in severity)) {\n severity[severityType] = false;\n }\n });\n return severity;\n }\n\n /**\n * Maps an AlarmListWidgetConfig object to an AlarmQueryFilter.\n *\n * This function converts the provided AlarmListWidgetConfig into a format suitable for querying alarms.\n *\n * @param config - The configuration object for the alarm list widget.\n * @param pageSize - Optional number specifying the size of the pages to be returned in the query.\n * @returns - The query filter object constructed from the provided configuration.\n */\n mapConfigToQueryFilter(config: AlarmListWidgetConfig, pageSize?: number): AlarmQueryFilter {\n const filter: AlarmQueryFilter = {\n pageSize: pageSize || DEFAULT_PAGE_SIZE,\n query: this.getOrderParameters(config.order),\n severity: this.extractFilterParams(config.severities || {}),\n status: this.extractFilterParams(config.status || {}),\n type: (config.types || []).join(','),\n withTotalPages: true\n };\n if (config.dateTimeContext) {\n const { dateFrom, dateTo } = config.dateTimeContext;\n filter.lastUpdatedFrom = typeof dateFrom === 'string' ? dateFrom : dateFrom.toISOString();\n filter.createdTo = typeof dateTo === 'string' ? dateTo : dateTo.toISOString();\n }\n if (config.device) {\n filter.source = config.device.id;\n filter.withSourceAssets = true;\n filter.withSourceDevices = true;\n filter.withSourceAssets = config.showAlarmsForChildren ?? true;\n filter.withSourceDevices = config.showAlarmsForChildren ?? true;\n }\n return filter;\n }\n\n /**\n * Extracts and concatenates filter parameters from a given object.\n *\n * This function takes an object containing filter settings (either SeverityFilter\n * or AlarmStatusSettings) and returns a string of all keys where the corresponding value is true.\n * If the object is empty or null, an empty string is returned.\n *\n * @param obj - The object containing filter settings.\n * @returns - A concatenated string of keys with true values, separated by commas.\n */\n extractFilterParams(obj: SeverityFilter | AlarmStatusSettings): string {\n if (!obj) {\n return '';\n }\n return Object.entries(obj)\n .filter(([, value]) => value)\n .map(([key]) => key)\n .join(',');\n }\n\n /**\n * Determines if an incoming real-time alarm has a different status than an existing alarm.\n *\n * This function checks if the provided incoming real-time alarm's status differs\n * from that of an existing alarm with the same ID in the given array of alarms.\n *\n * @param existingAlarms - The array of existing alarms.\n * @param incomingRealtimeAlarm - The incoming real-time alarm to check.\n * @returns - True if the existing alarm's status has changed, otherwise false.\n */\n hasExistingAlarmChangedStatus(existingAlarms: IAlarm[], incomingRealtimeAlarm: IAlarm): boolean {\n const existingAlarm = existingAlarms.find(alarm => alarm.id === incomingRealtimeAlarm.id);\n return !!existingAlarm && existingAlarm.status !== incomingRealtimeAlarm.status;\n }\n\n /**\n * Filters alarms based on their status, severity, and type.\n *\n * This method determines if a given alarm, identified either by a numeric ID or an `IAlarm` object,\n * matches specific criteria defined in `alarms` and optionally `config`.\n *\n * @param alarm - The alarm to check, represented either by a numeric ID or an `IAlarm` object.\n * @param alarms - An array of `IAlarm` objects against which the given alarm is evaluated.\n * @param config - Optional. Configuration for the alarm list widget, used to define additional\n * filtering criteria.\n *\n * @returns `true` if the alarm matches the specified criteria; otherwise, `false`.\n * If `alarm` is a number, it always returns `false`.\n * If `config` is not provided, it uses a legacy filter for critical alarms.\n *\n * @remarks\n * - When `alarm` is a numeric ID, the function returns `false` as it cannot match against type and severity.\n * - If `config` is not provided, the function assumes a legacy scenario for filtering all critical alarms.\n */\n filterAlarmsByStatusSeverityAndType(\n alarm: number | IAlarm,\n alarms: IAlarm[],\n config?: AlarmListWidgetConfig\n ): boolean {\n if (typeof alarm === 'number') {\n return false;\n }\n\n return this.isAlarmMatchedByConfig(alarm, alarms, config);\n }\n\n /**\n * Determines if all values in the given object are false.\n *\n * This function checks every value in the provided object to see if they are all false.\n *\n * @param obj - An object with boolean values.\n * @returns - Returns `true` if all values in the object are false, otherwise `false`.\n */\n allValuesFalse(obj: { [key: string]: boolean }): boolean {\n return Object.values(obj).every(value => !value);\n }\n\n /**\n * Constructs a string of order parameters for a query based on the specified alarm order.\n *\n * This function takes an alarm order and maps it to a corresponding set of order parameters.\n * It supports different ordering types, such as BY_ACTIVE, BY_SEVERITY, and BY_DATE_ASCENDING.\n * The order parameters are used to construct a query string that determines the order\n * in which alarms are retrieved or displayed.\n *\n * @param order - The specified order for sorting alarms (e.g., BY_ACTIVE).\n * @returns - A string of order parameters to be used in a query, or an empty string if the order type is unrecognized.\n */\n getOrderParameters(order: AlarmOrderType): string {\n let orderParams: string[];\n\n switch (order) {\n case ALARM_ORDER_VALUES.BY_ACTIVE:\n orderParams = ['status asc', 'severity asc', 'time.date desc', 'text asc'];\n return this.buildOrderParameters(orderParams);\n case ALARM_ORDER_VALUES.BY_SEVERITY:\n orderParams = ['severity asc', 'time.date desc', 'text asc'];\n return this.buildOrderParameters(orderParams);\n case ALARM_ORDER_VALUES.BY_DATE_ASCENDING:\n orderParams = ['time.date asc', 'text asc'];\n return this.buildOrderParameters(orderParams);\n default:\n orderParams = ['time.date desc', 'text asc'];\n return this.buildOrderParameters(orderParams);\n }\n }\n\n /**\n * Determines if an alarm is matched by the specified widget configuration.\n *\n * This function evaluates whether a given alarm should be included based on the severity,\n * status, and type filters defined in the AlarmListWidgetConfig. It checks if the alarm's\n * severity and status match the configuration settings and if the alarm's type is included\n * in the configuration's types (if specified).\n *\n * @ignore\n * @param alarm - The alarm to evaluate.\n * @param alarms - An array of existing alarms, used for status matching.\n * @param config - The configuration settings to match against.\n * @returns - Returns `true` if the alarm matches the configuration criteria; otherwise, `false`.\n */\n isAlarmMatchedByConfig(alarm: IAlarm, alarms: IAlarm[], config: AlarmListWidgetConfig): boolean {\n const isSeverityMatched = this.isSeverityMatching(alarm.severity, config);\n const isStatusMatched = this.isStatusMatching(alarm, alarms, config);\n const isTypeMatched = this.isTypesMatching(config, alarm);\n\n return isSeverityMatched && isStatusMatched && isTypeMatched;\n }\n /**\n * Checks if the severity of an alarm matches the configuration setting.\n *\n * This function determines whether the severity of an alarm is included in the\n * severity settings defined in the AlarmListWidgetConfig.\n *\n * @ignore\n * @param severity - The severity of the alarm to check.\n * @param config - The configuration with severity settings.\n * @returns - Returns `true` if the alarm's severity matches the configuration; otherwise, `false`.\n */\n isSeverityMatching(severity: SeverityType, config: AlarmListWidgetConfig): boolean {\n return !!config.severities[severity];\n }\n\n /**\n * Evaluates if the status of an alarm matches the configuration setting or has changed.\n *\n * This function checks if the status of an alarm is included in the status settings defined in\n * the AlarmListWidgetConfig, or if the alarm's status has changed based on the existing alarms.\n *\n * @ignore\n * @param alarm - The alarm whose status is to be evaluated.\n * @param alarms - An array of existing alarms to compare against for status changes.\n * @param config - The configuration with status settings.\n * @returns - Returns `true` if the alarm's status matches or has changed as per the configuration; otherwise, `false`.\n */\n isStatusMatching(alarm: IAlarm, alarms: IAlarm[], config: AlarmListWidgetConfig): boolean {\n return !!config.status[alarm.status] || this.hasExistingAlarmChangedStatus(alarms, alarm);\n }\n\n /**\n * Checks if the configuration's types array contains only empty string or includes a specific alarm type.\n *\n * @param config - The configuration object with a `types` property.\n * @param alarm - The alarm object with a `type` property to check against the config's types.\n * @returns `true` if the config's types array contains only empty string or includes the alarm's type, otherwise `false`.\n */\n isTypesMatching(config: AlarmListWidgetConfig, alarm: IAlarm): boolean {\n return Array.isArray(config.types) && config.types.length === 1 && config.types[0] === ''\n ? true\n : (config.types?.includes(alarm.type) ?? false);\n }\n\n /**\n * Constructs a query string from an array of order parameters.\n *\n * This function takes an array of ordering parameters and constructs a query string\n * for use in alarm ordering queries. The parameters are concatenated into a single string,\n * prefixed with '$orderby='.\n *\n * @ignore\n * @private\n * @param orderParams - The order parameters to be included in the query.\n * @returns - A query string representing the order parameters.\n */\n private buildOrderParameters(orderParams: string[]): string {\n return `$orderby=${orderParams.join(',')}`;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { Severity } from '@c8y/client';\nimport { SupportedIconsSuggestions } from '@c8y/ngx-components/icon-selector/icons';\n\ninterface SeverityIcon {\n c8yIcon?: string;\n iconClass: string;\n severityClass?: string;\n}\n\n@Pipe({ name: 'severityIcon' })\nexport class SeverityIconPipe implements PipeTransform {\n transform(severity: string): SeverityIcon {\n let severityClassName = '';\n let iconClassName: SupportedIconsSuggestions = '';\n\n switch (severity) {\n case Severity.CRITICAL:\n severityClassName = 'critical';\n iconClassName = 'exclamation-circle';\n break;\n case Severity.MAJOR:\n severityClassName = 'major';\n iconClassName = 'warning';\n break;\n case Severity.MINOR:\n severityClassName = 'minor';\n iconClassName = 'high-priority';\n break;\n case Severity.WARNING:\n severityClassName = 'warning';\n iconClassName = 'circle';\n break;\n default:\n return { iconClass: '', severityClass: '' };\n }\n\n return {\n iconClass: `status icon-lg stroked-icon dlt-c8y-icon-${iconClassName} ${severityClassName}`,\n c8yIcon: severityClassName\n };\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { ALARM_ORDER_VALUES, AlarmOrderType } from './alarm-list-widget.model';\n\n@Pipe({ name: 'sortingDescriptionPopoverMessage' })\nexport class SortingDescriptionPopoverMessagePipe implements PipeTransform {\n transform(order: AlarmOrderType): string {\n switch (order) {\n case ALARM_ORDER_VALUES.BY_ACTIVE:\n return gettext('Order alarms by active status, severity, and time.');\n case ALARM_ORDER_VALUES.BY_DATE_ASCENDING:\n return gettext('Order alarms by time, starting with the oldest ones.');\n case ALARM_ORDER_VALUES.BY_DATE_DESCENDING:\n return gettext('Order alarms by time, starting with the latest ones.');\n case ALARM_ORDER_VALUES.BY_SEVERITY:\n return gettext('Order alarms by severity and time.');\n default:\n return '';\n }\n }\n}\n","import { AsyncPipe, NgClass } from '@angular/common';\nimport { Component, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';\nimport {\n ControlContainer,\n FormArray,\n FormBuilder,\n FormControl,\n FormGroup,\n FormsModule,\n NgForm,\n ReactiveFormsModule,\n ValidatorFn\n} from '@angular/forms';\nimport {\n ALARM_STATUS_LABELS,\n AlarmService,\n AlarmStatus,\n IAlarm,\n IResultList,\n SEVERITY_LABELS\n} from '@c8y/client';\nimport {\n AlertService,\n C8yTranslatePipe,\n FormGroupComponent,\n IconDirective,\n MessageDirective,\n MessagesComponent,\n OnBeforeSave,\n RequiredInputPlaceholderDirective\n} from '@c8y/ngx-components';\nimport {\n ALARM_STATUS_ICON,\n AlarmsDateFilterComponent,\n AlarmsListComponent,\n AlarmsViewService,\n DEFAULT_SEVERITY_VALUES,\n DEFAULT_STATUS_VALUES\n} from '@c8y/ngx-components/alarms';\nimport { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';\nimport {\n DateTimeContextUtil,\n LocalControlsComponent,\n PRESET_NAME,\n PresetName,\n REFRESH_OPTION\n} from '@c8y/ngx-components/global-context';\nimport { merge } from 'lodash-es';\nimport { PopoverDirective } from 'ngx-bootstrap/popover';\nimport { BehaviorSubject, EMPTY, from, Observable, Subject } from 'rxjs';\nimport { filter, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators';\nimport {\n ALARM_ORDER_LABELS,\n ALARM_ORDER_VALUES,\n AlarmListWidgetConfig,\n AlarmOrderType\n} from '../alarm-list-widget.model';\nimport { AlarmWidgetService, DEFAULT_PAGE_SIZE } from '../alarm-widget.service';\nimport { SeverityIconPipe } from '../severity-icon.pipe';\nimport { SortingDescriptionPopoverMessagePipe } from '../sorting-description-popover-message.pipe';\n\n@Component({\n selector: 'c8y-alarm-list-widget-config',\n templateUrl: './alarm-list-widget-config.component.html',\n viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],\n imports: [\n FormsModule,\n ReactiveFormsModule,\n PopoverDirective,\n FormGroupComponent,\n AlarmsDateFilterComponent,\n RequiredInputPlaceholderDirective,\n NgClass,\n IconDirective,\n MessagesComponent,\n MessageDirective,\n AlarmsListComponent,\n C8yTranslatePipe,\n AsyncPipe,\n SeverityIconPipe,\n SortingDescriptionPopoverMessagePipe,\n LocalControlsComponent\n ]\n})\nexport class AlarmListWidgetConfigComponent implements OnInit, OnBeforeSave, OnDestroy {\n @Input() config: AlarmListWidgetConfig;\n\n readonly REFRESH_INTERVAL_IN_MILLISECONDS = this.alarmsViewService.DEFAULT_INTERVAL_VALUES;\n readonly SEVERITY_LABELS = SEVERITY_LABELS;\n readonly STATUS_LABELS = ALARM_STATUS_LABELS;\n\n readonly ACKNOWLEDGE_STATUS_VALUE = AlarmStatus.ACKNOWLEDGED;\n readonly CLEARED_STATUS_VALUE = AlarmStatus.CLEARED;\n\n readonly BELL_SLASH_ICON = ALARM_STATUS_ICON.BELL_SLASH;\n readonly BELL_ICON = ALARM_STATUS_ICON.BELL;\n readonly C8Y_ALERT_IDLE_ICON = ALARM_STATUS_ICON.ALERT_IDLE;\n\n readonly ALARM_ORDER_LABELS = ALARM_ORDER_LABELS;\n\n alarms$: BehaviorSubject<IResultList<IAlarm> | null> = new BehaviorSubject(null);\n formGroup: ReturnType<AlarmListWidgetConfigComponent['createForm']>;\n\n isLoading: boolean;\n orderList = Object.values(ALARM_ORDER_VALUES);\n severityList = Object.keys(SEVERITY_LABELS);\n /**\n * Order does matter.\n */\n statusList = [AlarmStatus.ACTIVE, AlarmStatus.ACKNOWLEDGED, AlarmStatus.CLEARED];\n\n @ViewChild('alarmListPreview')\n set previewMapSet(template: TemplateRef<any>) {\n if (template) {\n this.widgetConfigService.setPreview(template);\n return;\n }\n this.widgetConfigService.setPreview(null);\n }\n\n private destroy$ = new Subject<void>();\n\n config$ = this.widgetConfigService.currentConfig$.pipe(\n filter(config => !!config),\n filter(c => !!c.dateTimeContext),\n tap(config => {\n if (!this.formGroup) {\n this.initializeForm();\n } else {\n this.formGroup.patchValue(config, { emitEvent: false });\n }\n }),\n takeUntil(this.destroy$),\n shareReplay(1)\n );\n\n // Preview controls\n controls: PresetName = PRESET_NAME.ALARM_LIST;\n constructor(\n private alarmListWidgetService: AlarmWidgetService,\n private alarmService: AlarmService,\n private alarmsViewService: AlarmsViewService,\n private alertService: AlertService,\n private form: NgForm,\n private formBuilder: FormBuilder,\n private widgetConfigService: WidgetConfigService\n ) {}\n\n ngOnInit(): void {\n this.config$\n .pipe(\n takeUntil(this.destroy$),\n filter(config => config?.isGlobalContextReady === true),\n map(config => {\n if (config.refreshOption !== REFRESH_OPTION.HISTORY && config.dateTimeContext) {\n return {\n ...config,\n dateTimeContext: DateTimeContextUtil.normalizeForLive(config.dateTimeContext)\n };\n }\n return config;\n }),\n switchMap(config => {\n if (\n (config?.severities && this.alarmListWidgetService.allValuesFalse(config.severities)) ||\n (config?.status && this.alarmListWidgetService.allValuesFalse(config.status))\n ) {\n this.alarms$.next({ data: [], res: null });\n return EMPTY;\n }\n const isWidgetWithExistingConfig = config.order && config.severities && config.status;\n const filterConfig = isWidgetWithExistingConfig\n ? config\n : { ...this.formGroup.value, ...config };\n return from(this.getAlarms(filterConfig as AlarmListWidgetConfig));\n })\n )\n .subscribe();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n onBeforeSave(config?: AlarmListWidgetConfig): boolean | Promise<boolean> | Observable<boolean> {\n const { types } = this.formGroup.value;\n if (types.length > 1) {\n const stringTypes = types as string[];\n\n if (this.isContainingOnlyEmptyTypes(stringTypes)) {\n this.formGroup.value.types = [''];\n } else {\n this.formGroup.value.types = this.filterEmptyTypes(stringTypes);\n }\n }\n\n /**\n * Applies only to converted legacy Alarm list widget\n */\n if (config['options']) {\n delete config['options'];\n }\n\n Object.assign(config, this.formGroup.value);\n\n return true;\n }\n\n removeType(index: number): void {\n if (this.types.controls.length === 1) {\n this.formGroup.get('types').reset();\n } else {\n this.types.removeAt(index);\n }\n }\n\n addType(): void {\n this.types.push(this.formBuilder.control(''));\n }\n\n get types(): FormArray {\n return this.formGroup.get('types') as FormArray;\n }\n\n private filterEmptyTypes(types: (string | null)[]): string[] {\n return types.filter((element: string | null) => element !== '' && element !== null);\n }\n\n private isContainingOnlyEmptyTypes(types: (string | null)[]): boolean {\n return types.every((element: string | null) => element === '' || element === null);\n }\n\n private async getAlarms(config: AlarmListWidgetConfig): Promise<void> {\n try {\n this.isLoading = true;\n const result: IResultList<IAlarm> = await this.alarmService.list(\n this.alarmListWidgetService.mapConfigToQueryFilter(config, DEFAULT_PAGE_SIZE)\n );\n\n this.alarms$.next(result);\n } catch (error) {\n this.alertService.addServerFailure(error);\n } finally {\n this.isLoading = false;\n }\n }\n\n private initializeForm(): void {\n this.formGroup = this.createForm();\n this.form.form.addControl('config', this.formGroup);\n this.formGroup.patchValue(this.config, { emitEvent: false });\n this.initializeTypes(this.config.types);\n\n this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => {\n this.config = merge(this.config, value);\n this.getAlarms(this.config);\n });\n }\n\n private createForm() {\n return this.formBuilder.group({\n status: this.formBuilder.group(DEFAULT_STATUS_VALUES, {\n validators: this.minSelectedCheckboxes(1)\n }),\n showAlarmsForChildren: true,\n types: this.formBuilder.array([]),\n severities: this.formBuilder.group(DEFAULT_SEVERITY_VALUES, {\n validators: this.minSelectedCheckboxes(1)\n }),\n order: ALARM_ORDER_VALUES.BY_ACTIVE as AlarmOrderType,\n device: this.config.device ? new FormControl(this.config.device) : new FormControl(undefined)\n });\n }\n\n private minSelectedCheckboxes(min = 1): ValidatorFn {\n const validator: ValidatorFn = (formGroup: FormGroup) => {\n const totalSelected = Object.values(formGroup.controls).reduce(\n (prev, next) => (next.value ? prev + next.value : prev),\n 0\n );\n\n return totalSelected >= min ? null : { required: true };\n };\n\n return validator;\n }\n\n private initializeTypes(types: string[]): void {\n const typesControl = this.formGroup.get('types') as FormArray;\n if (types) {\n types.forEach(type => {\n typesControl.push(this.formBuilder.control(type));\n });\n } else {\n typesControl.push(this.formBuilder.control(''));\n }\n }\n}\n","<div\n class=\"p-l-24 p-r-24\"\n [style.pointer-events]=\"'auto'\"\n [style.opacity]=\"1\"\n></div>\n\n@if (formGroup) {\n <form\n class=\"row d-flex flex-wrap\"\n [formGroup]=\"formGroup\"\n >\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-8\">\n @for (order of orderList; track order; let i = $index) {\n <div\n class=\"d-flex p-b-8 p-t-4 a-i-center\"\n data-cy=\"c8y-alarm-list-widget-config--order-elements\"\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 }\n </c8y-form-group>\n </fieldset>\n </div>\n <div class=\"col-md-6 col-xs-12\">\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"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 data-cy=\"c8y-alarm-list-widget-config--severities-elements\"\n >\n @for (severityOption of severityList; track severityOption) {\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 @let severityIcon = severityOption | severityIcon;\n <i\n class=\"a-s-center m-r-4 icon-20 {{ severityIcon.iconClass }}\"\n [c8yIcon]=\"severityIcon.c8yIcon\"\n ></i>\n <span>{{ SEVERITY_LABELS[severityOption] | translate }}</span>\n </label>\n }\n <c8y-messages>\n @if (formGroup.controls.severities.hasError('required')) {\n <c8y-message>\n {{ 'Select at least one severity.' | translate }}\n </c8y-message>\n }\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n @if (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 }\n </div>\n\n <div class=\"col-md-6 col-xs-12\">\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"status\"\n >\n <legend data-cy=\"c8y-alarm-list-widget-config-status-label\">\n {{ 'Status' | translate }}\n </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 data-cy=\"c8y-alarm-list-widget-config--status-elements\"\n >\n @for (statusOption of statusList; track statusOption) {\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 }\n <c8y-messages>\n @if (formGroup.controls.status.hasError('required')) {\n <c8y-message>\n {{ 'Select at least one status.' | translate }}\n </c8y-message>\n }\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n </div>\n <div class=\"col-md-6 col-xs-12\">\n <fieldset\n class=\"c8y-fieldset\"\n data-cy=\"c8y-alarm-list-widget-config--child-devices-section\"\n >\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 data-cy=\"c8y-alarm-list-widget-config--child-devices-label\"\n >\n <input\n type=\"checkbox\"\n formControlName=\"showAlarmsForChildren\"\n data-cy=\"c8y-alarm-list-widget-config--showAlarmsForChildren-checkbox\"\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 </div>\n\n <div class=\"col-xs-12\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Types' | translate }}</legend>\n <c8y-form-group\n class=\"m-b-8\"\n formArrayName=\"types\"\n >\n @for (type of types.controls; track type; let i = $index) {\n <div\n class=\"input-group m-b-4\"\n data-cy=\"c8y-alarm-list-widget-config--types-elements\"\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-dot btn-dot--danger m-l-auto\"\n [title]=\"'Remove' | translate\"\n type=\"button\"\n (click)=\"removeType(i)\"\n data-cy=\"c8y-alarm-list-widget-config--types-remove-type\"\n [disabled]=\"types.controls?.length === 1 && !type.value\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n @if (i === types.controls.length - 1) {\n <div class=\"input-group-btn\">\n <button\n class=\"btn-dot btn-dot--primary m-l-auto\"\n [title]=\"'Add alarm type' | translate\"\n type=\"button\"\n (click)=\"addType()\"\n data-cy=\"c8y-alarm-list-widget-config--types-add-type\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n </button>\n </div>\n }\n </div>\n }\n </c8y-form-group>\n </fieldset>\n </div>\n </form>\n}\n\n<ng-template #alarmListPreview>\n @if ((config$ | async)?.displayMode !== 'dashboard') {\n <c8y-local-controls\n [controls]=\"controls\"\n [displayMode]=\"(config$ | async)?.displayMode\"\n [config]=\"config$ | async\"\n [disabled]=\"true\"\n ></c8y-local-controls>\n }\n <c8y-alarms-list\n [alarms]=\"alarms$ | async\"\n [isInPreviewMode]=\"true\"\n [isInitialLoading]=\"isLoading\"\n [navigationOptions]=\"{\n allowNavigationToAlarmsView: false,\n alwaysNavigateToAllAlarms: false,\n includeClearedQueryParams: false,\n queryParamsHandling: 'merge'\n }\"\n data-cy=\"c8y-alarm-list-widget-config--preview-alarm-list\"\n ></c8y-alarms-list>\n</ng-template>\n\n<ng-template #dateSelectionHelpTemplate>\n <div [innerHTML]=\"dateSelectionHelp\"></div>\n</ng-template>\n","import { AsyncPipe } from '@angular/common';\nimport { Component, Input, OnDestroy, OnInit, inject, signal } from '@angular/core';\nimport { AlarmService, IAlarm, IResultList } from '@c8y/client';\nimport {\n AlarmRealtimeService,\n AlarmWithChildrenRealtimeService,\n AlertService,\n DashboardChildComponent,\n DismissAlertStrategy,\n DynamicComponent,\n DynamicComponentAlert,\n DynamicComponentAlertAggregator\n} from '@c8y/ngx-components';\nimport { AlarmsListComponent } from '@c8y/ngx-components/alarms';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n LocalControlsComponent,\n GlobalContextState,\n GlobalContextConnectorComponent,\n REFRESH_OPTION,\n WidgetConfigMigrationService,\n PresetName,\n DisplayMode,\n GLOBAL_CONTEXT_DISPLAY_MODE,\n CONTEXT_FEATURE,\n PRESET_NAME\n} from '@c8y/ngx-components/global-context';\nimport { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';\nimport { isEmpty, isMatch, merge } from 'lodash-es';\nimport { BehaviorSubject, Subject } from 'rxjs';\nimport {\n AlarmListWidgetConfig,\n ASSET_ALARMS_WIDGET_ID,\n RECENT_ALARMS_WIDGET_ID\n} from '../alarm-list-widget.model';\nimport { AlarmWidgetService } from '../alarm-widget.service';\n\n@Component({\n selector: 'c8y-alarm-list-widget',\n templateUrl: './alarm-list-widget.component.html',\n providers: [AlarmRealtimeService, AlarmWithChildrenRealtimeService],\n imports: [AlarmsListComponent, AsyncPipe, LocalControlsComponent, GlobalContextConnectorComponent]\n})\nexport class AlarmListWidgetComponent implements OnInit, OnDestroy, DynamicComponent {\n private alarmWidgetService = inject(AlarmWidgetService);\n private alarmService = inject(AlarmService);\n private dashboardChild = inject(DashboardChildComponent);\n private alertService = inject(AlertService);\n private widgetConfigMigrationService = inject(WidgetConfigMigrationService);\n\n @Input() config: AlarmListWidgetConfig;\n displayMode = signal<DisplayMode>(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD);\n contextConfig = signal<GlobalContextState>({});\n isLinkedToGlobal = signal<boolean | undefined>(undefined);\n alarms$: BehaviorSubject<IResultList<IAlarm> | null> = new BehaviorSubject(null);\n isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(true);\n alerts: DynamicComponentAlertAggregator;\n widgetControls = signal<PresetName>(PRESET_NAME.ALARM_LIST);\n\n readonly GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE;\n readonly CONTEXT_FEATURE = CONTEXT_FEATURE;\n readonly REFRESH_OPTION = REFRESH_OPTION;\n readonly PRESET_NAME = PRESET_NAME;\n\n private TIMEOUT_ERROR_TEXT = gettext(\n 'The request is taking longer than usual. We apologize for the inconvenience.'\n );\n private SERVER_ERROR_TEXT = gettext('Server error occurred.');\n private unsubscribe$ = new Subject<void>();\n hasPermissions = true;\n\n ngOnInit(): void {\n this.alerts.setAlertGroupDismissStrategy(\n 'warning',\n DismissAlertStrategy.TEMPORARY_OR_PERMANENT\n );\n\n this.migrateConfig();\n this.resolveDisplayMode();\n this.initContextConfig();\n // First fetch is triggered by controls/connector emitting initial configChange.\n }\n\n ngOnDestroy(): void {\n if (this.config?.isRealtime) {\n this.unsubscribe$.next();\n this.unsubscribe$.complete();\n }\n }\n\n onContextChange(event: { context: GlobalContextState; diff: GlobalContextState }): void {\n const { diff, context } = event;\n this.contextConfig.set(context);\n\n if (\n diff.isAutoRefreshEnabled === false &&\n Object.keys(diff).length === 1 &&\n context.refreshOption === REFRESH_OPTION.LIVE\n ) {\n return;\n }\n\n void this.fetchAlarms();\n }\n\n getDashboardChild(): DashboardChildComponent {\n return this.dashboardChild;\n }\n\n onRefresh(): void {\n void this.fetchAlarms();\n }\n\n onScrollingStateChange(isScrolling: boolean): void {\n const current = this.contextConfig();\n\n if (current.refreshOption === REFRESH_OPTION.HISTORY) {\n return;\n }\n this.contextConfig.set({ ...current, isAutoRefreshEnabled: !isScrolling });\n this.isLinkedToGlobal.set(!isScrolling);\n }\n\n private migrateConfig(): void {\n const alarmsWidgetId =\n this.dashboardChild['data']?.componentId ?? this.dashboardChild['data']?.name;\n\n if (this.isLegacyWidgetWithoutConfig(alarmsWidgetId)) {\n const updatedId = this.resolveLegacyWidgetId(alarmsWidgetId);\n this.setLegacyRecentOrCriticalAlarmWidgetConfig(updatedId);\n this.config.widgetId = updatedId;\n } else if (this.alarmWidgetService.isOldAlarmConfigStructure(this.config)) {\n const mappedConfig = this.alarmWidgetService.mapToNewConfigStructure(this.config);\n Object.assign(this.config, mappedConfig);\n }\n\n if (\n this.config?.widgetId === RECENT_ALARMS_WIDGET_ID ||\n this.config?.widgetId === ASSET_ALARMS_WIDGET_ID\n ) {\n this.widgetControls.set(PRESET_NAME.ALARM_LIST_LEGACY);\n }\n\n const migratedConfig = this.widgetConfigMigrationService.migrateWidgetConfig(this.config);\n this.config = merge(this.config, migratedConfig);\n this.config.widgetId = this.config?.widgetId ?? alarmsWidgetId ?? defaultWidgetIds.ALARM_LIST;\n }\n\n private isLegacyWidgetWithoutConfig(alarmsWidgetId: string): boolean {\n const effectiveId = this.config?.widgetId ?? alarmsWidgetId;\n return (\n effectiveId === defaultWidgetIds.ALL_CRITICAL_ALARMS ||\n effectiveId === defaultWidgetIds.RECENT_ALARMS ||\n isEmpty(this.config) ||\n (isMatch(this.config, { displaySettings: { globalAutoRefreshContext: true } }) &&\n !this.config['options']?.device &&\n !this.config.types &&\n !this.config.status &&\n !this.config.severities)\n );\n }\n\n private resolveLegacyWidgetId(fallbackId: string): string {\n const title = this.dashboardChild['data']?.title;\n if (title === 'Active, critical alarms') return ASSET_ALARMS_WIDGET_ID;\n if (title === 'Recent alarms') return RECENT_ALARMS_WIDGET_ID;\n return fallbackId;\n }\n\n private resolveDisplayMode(): void {\n const displayMode = this.config.displayMode || GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD;\n this.displayMode.set(displayMode as DisplayMode);\n }\n\n private initContextConfig(): void {\n this.contextConfig.set({\n dateTimeContext: this.config.dateTimeContext,\n aggregation: this.config.aggregation,\n isAutoRefreshEnabled: this.config.isAutoRefreshEnabled,\n refreshInterval: this.config.refreshInterval,\n refreshOption: this.config.refreshOption\n });\n }\n\n private async fetchAlarms(): Promise<void> {\n if (!this.hasPermissions) {\n return;\n }\n const alarms = await this.getAlarms();\n this.alarms$.next(alarms);\n }\n\n private async getAlarms(): Promise<IResultList<IAlarm>> {\n try {\n this.isLoading$.next(true);\n const effectiveConfig = {\n ...this.config,\n ...this.contextConfig()\n };\n return await this.alarmService.list(\n this.alarmWidgetService.mapConfigToQueryFilter(effectiveConfig)\n );\n } catch (error) {\n this.alerts.addAlerts(\n new DynamicComponentAlert({\n type: 'warning',\n text:\n error?.name === 'TimeoutError'\n ? this.TIMEOUT_ERROR_TEXT\n : (error?.message ?? this.SERVER_ERROR_TEXT)\n })\n );\n if (error?.res?.status === 403) {\n this.hasPermissions = false;\n return;\n }\n this.alertService.addServerFailure(error);\n } finally {\n this.isLoading$.next(false);\n }\n }\n\n private setLegacyRecentOrCriticalAlarmWidgetConfig(widgetId: string): void {\n if (widgetId === RECENT_ALARMS_WIDGET_ID) {\n const predefinedConfig = this.alarmWidgetService.getPredefinedConfiguration(\n RECENT_ALARMS_WIDGET_ID,\n this.config\n );\n Object.assign(this.config, predefinedConfig);\n return;\n }\n\n if (widgetId === ASSET_ALARMS_WIDGET_ID) {\n const predefinedConfig = this.alarmWidgetService.getPredefinedConfiguration(\n ASSET_ALARMS_WIDGET_ID,\n this.config\n );\n Object.assign(this.config, predefinedConfig);\n }\n }\n}\n","@if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {\n <c8y-global-context-connector\n [controls]=\"widgetControls()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading$ | async\"\n [dashboardChild]=\"getDashboardChild()\"\n [linked]=\"isLinkedToGlobal()\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-global-context-connector>\n} @else {\n <c8y-local-controls\n [controls]=\"widgetControls()\"\n [displayMode]=\"displayMode()\"\n [config]=\"contextConfig()\"\n [isLoading]=\"isLoading$ | async\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-local-controls>\n}\n\n<c8y-alarms-list\n #list\n [alarms]=\"alarms$ | async\"\n [navigationOptions]=\"{\n alwaysNavigateToAllAlarms: !config.device,\n allowNavigationToAlarmsView: true,\n includeClearedQueryParams: true,\n queryParamsHandling: ''\n }\"\n [isInitialLoading]=\"isLoading$ | async\"\n [hasPermissions]=\"hasPe