@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
966 lines (956 loc) • 223 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Pipe, InjectionToken, Component, Optional, Input, EventEmitter, Output, ViewChild, forwardRef, NgModule } from '@angular/core';
import { combineLatest, Subject, BehaviorSubject, firstValueFrom, of, from, forkJoin, EMPTY, Observable, pipe, fromEvent, take, takeUntil as takeUntil$1, map as map$1 } from 'rxjs';
import { filter, map, switchMap, startWith, takeUntil, catchError, finalize, tap, debounceTime, distinctUntilChanged, shareReplay, throttleTime } from 'rxjs/operators';
import * as i3 from '@c8y/ngx-components';
import { Permissions, gettext, ViewContext, SupportedApps, CountdownIntervalComponent, DynamicComponentAlertAggregator, DynamicComponentAlert, AlarmWithChildrenRealtimeService, ContextRouteComponent, ContextRouteGuard, RouterTabsResolver, hookNavigator, hookRoute, CommonModule, CoreModule, HeaderModule, DynamicComponentModule, RelativeTimePipe } from '@c8y/ngx-components';
import { sortBy, cloneDeep } from 'lodash-es';
import * as i2 from '@c8y/client';
import { AlarmStatus, Severity, ALARM_STATUS_LABELS, SEVERITY_LABELS } from '@c8y/client';
import * as i1 from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';
import * as i9 from '@c8y/ngx-components/interval-picker';
import { INTERVAL_TITLES, INTERVALS, IntervalPickerComponent } from '@c8y/ngx-components/interval-picker';
import * as i7 from '@angular/common';
import { DatePipe, TitleCasePipe } from '@angular/common';
import * as i8 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import * as i1$1 from '@angular/router';
import { NavigationEnd, RouterModule } from '@angular/router';
import * as i1$2 from '@angular/forms';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import * as i6 from 'ngx-bootstrap/dropdown';
import { BsDropdownDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown';
import * as i5 from '@angular/cdk/a11y';
import { A11yModule } from '@angular/cdk/a11y';
import * as i5$1 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import * as i1$3 from '@c8y/ngx-components/alarm-event-selector';
import { AlarmEventSelectorModule } from '@c8y/ngx-components/alarm-event-selector';
/**
* A service to retrieve custom buttons for the alarm details view.
*/
class AlarmDetailsButtonService {
constructor(serviceRegistry, pluginsResolver) {
this.serviceRegistry = serviceRegistry;
this.pluginsResolver = pluginsResolver;
}
get$(alarm, source) {
const providers$ = this.pluginsResolver.allPluginsLoaded$.pipe(filter(Boolean), map(() => {
return this.serviceRegistry.get('alarmDetailsButton');
}));
return providers$.pipe(switchMap(providers => {
const observables$ = providers.map(provider => provider.getAlarmDetailsButton$(alarm, source).pipe(startWith(false)));
return combineLatest(observables$);
}), map(indicators => {
return indicators.filter(Boolean);
}), map(indicators => sortBy(indicators, this.byPriority)));
}
byPriority(item) {
if (item.priority === undefined) {
return 0;
}
return -item.priority;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonService, deps: [{ token: i3.ServiceRegistry }, { token: i3.PluginsResolveService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i3.ServiceRegistry }, { type: i3.PluginsResolveService }] });
/**
* A pipe to provide custom buttons for the alarm details view.
*
* Will call `get$()` method of `AlarmDetailsButtonService` to get the custom buttons for the provided alarm.
*/
class AlarmDetailsButtonPipe {
constructor(alarmDetailsButtonService) {
this.alarmDetailsButtonService = alarmDetailsButtonService;
}
transform(alarm, source) {
return this.alarmDetailsButtonService.get$(alarm, source);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonPipe, deps: [{ token: AlarmDetailsButtonService }], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonPipe, isStandalone: true, name: "alarmDetailsButton" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsButtonPipe, decorators: [{
type: Pipe,
args: [{
standalone: true,
name: 'alarmDetailsButton',
pure: true
}]
}], ctorParameters: () => [{ type: AlarmDetailsButtonService }] });
class AlarmDetailsService {
constructor(permissions) {
this.permissions = permissions;
this.STATUS_ATTRIBUTE = 'status';
}
/**
* Retrieves the username of the user who acknowledged an alarm status.
*
* This method checks if the provided status is equal to the acknowledged
* status. If it is not, or if the
* audit log is empty or the first log item does not contain a user, the
* method returns a default value ('--').
*
* If the status is the acknowledged status and the audit log contains valid
* records, the method iterates over the audit records in reverse order
* (starting from the most recent). It finds the first record where the
* status attribute (defined by this.STATUS_ATTRIBUTE) has been changed to
* the acknowledged status. The method then returns the username of the user
* who made this change.
*
* If no such change is found in the audit records, it returns the username
* from the first record of the audit log.
*
* There can be multiple audit logs with ACKNOWLEDGED status.
*
* @param status - The current status of the alarm.
* @param auditLog - An array of audit records to process.
* @returns The username of the user who acknowledged the status
* or '--' if the status is not acknowledged or audit log is invalid.
*/
getAcknowledgedBy(status, auditLog) {
let acknowledgedBy = '--';
if (status !== AlarmStatus.ACKNOWLEDGED || !auditLog || !auditLog[0]?.user) {
return acknowledgedBy;
}
acknowledgedBy = auditLog[0].user;
return auditLog.reduceRight((acc, auditLogItem) => {
const changes = Array.from(auditLogItem.changes || []);
const acknowledgedStatusChange = changes.find((change) => change.attribute === this.STATUS_ATTRIBUTE && change.newValue === AlarmStatus.ACKNOWLEDGED);
return (acknowledgedStatusChange && auditLogItem.user) || acc;
}, acknowledgedBy);
}
/**
* Calculates the acknowledge time from a list of audit records.
*
* This method iterates over the provided audit records in reverse order
* (starting from the most recent) and finds the first record where a
* specific status attribute (defined by this.STATUS_ATTRIBUTE) has been
* acknowledged. It then returns the creation time of that record.
*
* If no such record is found, the method returns the creation time of the
* first audit record. If the audit record list is empty, it returns null.
*
* There can be multiple audit logs with ACKNOWLEDGED status.
*
* @param auditLog - An array of audit records to process.
* @returns The creation time of the acknowledged record,
* the creation time of the first record if no acknowledged record is found,
* or null if the audit log is empty.
*/
getAcknowledgeTime(auditLog) {
const initialValue = auditLog.length ? auditLog[0].creationTime : null;
return auditLog.reduceRight((acc, auditLogItem) => {
const changes = Array.from(auditLogItem.changes || []);
const acknowledgedStatusChange = changes.find((change) => change.attribute === this.STATUS_ATTRIBUTE && change.newValue === AlarmStatus.ACKNOWLEDGED);
return acknowledgedStatusChange ? auditLogItem.creationTime : acc;
}, initialValue);
}
/**
* Retrieves the end time of an event from an audit log.
*
* The method processes the provided audit log to find the first instance
* (starting from the most recent record) where the status was changed to 'CLEARED'.
* It iterates over the audit records and
* checks the changes in each record to find this status change.
*
* If a record with the CLEARED status is found, the method returns the creation time
* of that record. If the entire audit log is processed without finding a CLEARED status,
* the creation time of the first audit log record is returned.
*
* If the audit log is empty or null, the method returns null.
*
* There can be only one audit log with CLEARED status.
*
* @param auditLog - An array of audit records to process.
* @returns The creation time of the record with the CLEARED status,
* the creation time of the first record if no CLEARED status is found,
* or null if the audit log is empty or null.
*/
getEndTime(auditLog) {
if (!auditLog || auditLog.length === 0) {
return null;
}
let latestClearedAuditTime = null;
for (const auditLogItem of auditLog) {
const changes = Array.from(auditLogItem.changes || []);
const clearedStatusChange = changes.find(change => change.attribute === this.STATUS_ATTRIBUTE && change.newValue === AlarmStatus.CLEARED);
if (clearedStatusChange) {
if (!latestClearedAuditTime || auditLogItem.creationTime > latestClearedAuditTime) {
latestClearedAuditTime = auditLogItem.creationTime;
}
}
}
return latestClearedAuditTime || auditLog[0].creationTime;
}
checkIfHasAnyRoleAllowingToCreateSmartRule() {
const ROLES_ALLOWING_SMART_RULE_CREATION = [
[
Permissions.ROLE_INVENTORY_ADMIN,
Permissions.ROLE_INVENTORY_CREATE,
Permissions.ROLE_MANAGED_OBJECT_ADMIN,
Permissions.ROLE_MANAGED_OBJECT_CREATE
],
[Permissions.ROLE_CEP_MANAGEMENT_ADMIN, Permissions.ROLE_SMARTRULE_ADMIN]
];
return (this.permissions.hasAnyRole(ROLES_ALLOWING_SMART_RULE_CREATION[0]) &&
this.permissions.hasAnyRole(ROLES_ALLOWING_SMART_RULE_CREATION[1]));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsService, deps: [{ token: i3.Permissions }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i3.Permissions }] });
const ALARMS_MODULE_CONFIG = new InjectionToken('AlarmsModuleConfig');
const ALARM_STATUS_ICON = {
ALERT_IDLE: 'c8y-alert-idle',
BELL_SLASH: 'bell-slash',
BELL: 'bell'
};
/**
* A lookup table to map alarm statuses to corresponding icons.
*/
const AlarmIconMap = {
[AlarmStatus.CLEARED]: ALARM_STATUS_ICON.ALERT_IDLE,
[AlarmStatus.ACKNOWLEDGED]: ALARM_STATUS_ICON.BELL_SLASH,
[AlarmStatus.ACTIVE]: ALARM_STATUS_ICON.BELL
};
const ALARM_SEVERITY_ICON = {
CIRCLE: 'circle',
HIGH_PRIORITY: 'high-priority',
WARNING: 'warning',
EXCLAMATION_CIRCLE: 'exclamation-circle'
};
const HELP_ICON = 'help';
/**
* A lookup table to map alarm severity types to corresponding icons.
*/
const ALARM_SEVERITY_ICON_MAP = {
[Severity.CRITICAL]: ALARM_SEVERITY_ICON.EXCLAMATION_CIRCLE,
[Severity.MAJOR]: ALARM_SEVERITY_ICON.WARNING,
[Severity.MINOR]: ALARM_SEVERITY_ICON.HIGH_PRIORITY,
[Severity.WARNING]: ALARM_SEVERITY_ICON.CIRCLE
};
/**
* Extended interval titles with an additional title for the case when no date is selected.
*/
const INTERVAL_TITLES_EXTENDED = {
...INTERVAL_TITLES,
none: gettext('No date filter')
};
const INTERVALS_EXTENDED = [
{
id: 'none',
title: gettext('No date filter')
},
...INTERVALS
];
const DEFAULT_ALARM_COUNTS = { CRITICAL: 0, MAJOR: 0, MINOR: 0, WARNING: 0 };
const DEFAULT_SEVERITY_VALUES = {
[Severity.CRITICAL]: true,
[Severity.MAJOR]: true,
[Severity.MINOR]: true,
[Severity.WARNING]: true
};
const DEFAULT_STATUS_VALUES = {
[AlarmStatus.ACTIVE]: true,
[AlarmStatus.ACKNOWLEDGED]: true,
[AlarmStatus.CLEARED]: true
};
const ALARMS_PATH = 'alarms';
/**
* Default properties of a alarm. Used to extract the custom properties from a Alarm object.
*/
const ALARM_DEFAULT_PROPERTIES = [
'severity',
'source',
'type',
'time',
'text',
'id',
'status',
'count',
'name',
'history',
'self',
'creationTime',
'firstOccurrenceTime',
'lastUpdated'
];
const THROTTLE_REALTIME_REFRESH = 1_000;
/**
* This service is a duplicate of smart-rules-service with slight name change.
* Duplicating allows to pass 'Verify App tutorial' job.
* Name renames allows to pass 'Reusable build codex' job.
* Overall this service is considered as a workaround.
* In ticket MTM-58985 we will investigate if it's possible to remove this service
* along with making failing jobs pass.
*/
class Ng1SmartRulesUpgradeService {
}
function SmartRulesUpgradeServiceFactory(injector) {
return injector.get('smartRulesSvc');
}
const smartRulesUpgradeServiceProvider = {
provide: Ng1SmartRulesUpgradeService,
useFactory: SmartRulesUpgradeServiceFactory,
deps: ['$injector']
};
/**
* Service for managing and retrieving alarms data within the alarms view.
*
* The `AlarmsViewService` provides functionality to interact with alarms,
* including filtering, counting, and translation-related operations in an alarms view.
*
* This service relies on the `AlarmService` for fetching alarm data and the `OptionsService`
* for configuring alarms view options.
*/
class AlarmsViewService {
constructor(alarmService, optionsService) {
this.alarmService = alarmService;
this.optionsService = optionsService;
this.ALARM_REFRESH_TYPE_KEY = 'alarmsRefreshType';
this.DEFAULT_INTERVAL_VALUE = 30_000;
this.DEFAULT_REFRESH_OPTION_VALUE = 'interval';
this.DEFAULT_INTERVAL_VALUES = [5_000, 10_000, 15_000, 30_000, 60_000];
this.REALTIME_UPDATE_ALARMS_MESSAGE = gettext('The list was updated, click to refresh.');
this.reloadAlarmsList$ = new Subject();
if (this.isIntervalRefresh()) {
this._isIntervalEnabled = new BehaviorSubject(true);
this.isIntervalEnabled$ = this._isIntervalEnabled.asObservable();
}
}
/**
* Emits a subject to initialize the alarms reloading.
*/
updateAlarmList() {
this.reloadAlarmsList$.next();
}
/**
* Retrieves a list of alarms filtered by specified severities and other optional query filters.
*
* @param severities an array of severities to filter the alarms.
* @param showCleared flag indicating whether to show cleared alarms. Defaults to false.
* @param selectedDates an array of two dates to filter alarms by creation and last update dates.
* @param filter additional query filters for retrieving alarms.
*
* @returns A promise that resolves to a list of alarms satisfying the specified filters.
*/
retrieveFilteredAlarms(severities, showCleared = false, selectedDates, filter) {
const severitiesQuery = this.getSeverityQueryParameter(severities);
const statusesQuery = this.getStatusQueryParameter(showCleared);
const _filter = {
pageSize: 50,
withTotalPages: true,
...(severitiesQuery && { severity: severitiesQuery }),
...(statusesQuery && { status: statusesQuery }),
...(selectedDates && {
lastUpdatedFrom: selectedDates[0].toISOString(),
createdTo: selectedDates[1].toISOString()
}),
...filter
};
return this.alarmService.list(_filter);
}
retrieveAlarmsByDate(dates) {
return this.alarmService.list({
lastUpdatedFrom: dates[0].toISOString(),
createdTo: dates[1].toISOString(),
pageSize: 50,
withTotalPages: true
});
}
/**
* Updates the state to enable or disable intervals.
* @param value - A boolean value to indicate whether to enable intervals.
*/
updateIntervalState(value) {
this._isIntervalEnabled?.next(value);
}
/**
* Fetches the count of alarms filtered by severity and clearance status.
*
* @param severity - The severity level to filter by (e.g., CRITICAL, MAJOR, etc.).
* @param showCleared - Whether or not to include cleared alarms in the count.
* @param filter - Additional filter criteria for alarms.
*
* @returns A promise that resolves to the number of alarms that match the filter criteria.
*
*/
async getAlarmsCountBySeverity(severity, showCleared, filter) {
const statusesQuery = this.getStatusQueryParameter(showCleared);
const _filter = {
...(severity && { severity: severity }),
...(statusesQuery && { status: statusesQuery }),
...filter
};
const { data } = await this.alarmService.count(_filter);
return data;
}
/**
* Retrieves the current alarms refresh type from the OptionsService
* and determines whether it is set to "interval".
*
* @returns `true` if the alarms refresh type is "interval," otherwise `false`.
*/
isIntervalRefresh() {
const value = this.optionsService.get(this.ALARM_REFRESH_TYPE_KEY, 'interval');
return value === 'interval';
}
/**
* Updates the list of selected severities based on the new severity filter.
*
* @param severityUpdates - The object representing the updates to each severity.
*
* @returns An array representing the updated selected severities.
*/
updateSelectedSeverities(severityUpdates) {
return Object.keys(severityUpdates)
.filter(key => severityUpdates[key])
.map(key => key.toUpperCase());
}
/**
* Clears all active alarms of the selected severities.
*
* This method clears all active alarms for the given list of severities by making bulk update calls. If no severities are selected, it defaults to using all available severities.
* It works by sending a series of update requests for each severity and returns a Promise that resolves with an object indicating if all alarms were resolved immediately.
*
* @param selectedSeverities An array of severities to be cleared. If not provided, all severities will be cleared.
* @param sourceId - Identifier for the source associated with the alarms to be cleared.
*
* @returns A Promise that resolves with an object with a flag `resolvedImmediately`. The flag is true if all alarms for all selected severities were cleared successfully; otherwise false.
*
* **Example**
* ```typescript
* const severitiesToClear: SeverityType[] = [Severity.MAJOR, Severity.MINOR];
*
* clearAllActiveAlarms(severitiesToClear).then(({ resolvedImmediately }) => {
* if (resolvedImmediately) {
* console.log('All selected alarms were cleared successfully.');
* } else {
* console.log('Some alarms could not be cleared.');
* }
* });
* ```
*
* **Note**
* - The method uses the `alarmService.updateBulk` for each severity to clear the active alarms.
* - It may fetch the `sourceId` based on the view (if applicable) and include it as a query parameter in the update calls.
* - The method returns immediately but the returned Promise needs to have a `then` or `catch` method call to handle the result or error respectively.
* - Uses `Promise.all` to wait for all update requests to complete before resolving the final result.
*/
async clearAllActiveAlarms(selectedSeverities, sourceId) {
const severitiesToUpdate = selectedSeverities || Severity;
const promises = Object.values(severitiesToUpdate).map((severity) => {
const commonParams = { resolved: false, severity };
const parameters = sourceId
? {
...commonParams,
source: sourceId,
withSourceAssets: true,
withSourceDevices: true
}
: commonParams;
return this.alarmService.updateBulk({ status: AlarmStatus.CLEARED }, parameters);
});
const responses = await Promise.all(promises);
return {
resolvedImmediately: responses.every(res => res)
};
}
/**
* Returns the correct link based on the provided context data.
* @param contextData The context the navigation was triggered from.
* @param alarm The alarm to navigate to.
* @returns A link to be used as an url navigation.
*/
getRouterLink(contextData, alarm) {
let detailUrl = `/${ALARMS_PATH}`;
if (alarm) {
detailUrl = `/${ALARMS_PATH}/${alarm.id}`;
}
if (!contextData) {
return detailUrl;
}
switch (contextData.context) {
case ViewContext.Device:
return `/device/${contextData.contextData.id}${detailUrl}`;
case ViewContext.Group:
return `/group/${contextData.contextData.id}${detailUrl}`;
case ViewContext.Simulators:
return `/simulators/${contextData.contextData.id}${detailUrl}`;
default:
return detailUrl;
}
}
/**
* Returns the correct array navigation.
* @param contextData The context the navigation was triggered from.
* @param alarm The alarm to navigate to.
* @returns A link to be used as a router.navigation.
*/
getRouterNavigationArray(contextData, alarm) {
return this.getRouterLink(contextData, alarm).split('/').filter(Boolean);
}
/**
* Returns the correct from and to dates based on the selected interval
* @param intervalId the selected interval. E.g. 'none', 'hours', 'custom' ...
* @returns The calculated date context based on the selected interval.
*/
getDateTimeContextByInterval(intervalId) {
const interval = INTERVALS_EXTENDED.find(({ id }) => id === intervalId);
if (interval.id === 'none') {
return [new Date(0), new Date()];
}
const dateTo = new Date();
const dateFrom = new Date(dateTo.valueOf() - interval.timespanInMs);
return [dateFrom, dateTo];
}
/**
* Creates a value for query parameter for filtering alarms by severity based on array of selected severities.
*
* @param severities - An array of alarm severity types to include in the filter.
* If the array is empty or undefined, no severity filter will be applied.
*
* @returns A comma-separated string of selected alarm severities,
* or null if no severities are provided.
*/
getSeverityQueryParameter(severities) {
if (!severities || severities.length === 0) {
return;
}
if (severities.length === Object.keys(Severity).length) {
return;
}
return severities.join(',');
}
/**
* Creates a value for query parameter for filtering alarms by statuses based on showCleared option.
*
* @param showCleared - A flag indicating whether to include cleared statuses.
* If true, all statuses, including 'CLEARED', will be included; if false, 'CLEARED' will be excluded.
*
* @returns A comma-separated string of alarm statuses.
*/
getStatusQueryParameter(showCleared) {
const statuses = Object.keys(ALARM_STATUS_LABELS);
const filteredStatuses = showCleared
? statuses
: statuses.filter(status => status !== 'CLEARED');
return filteredStatuses.join(',');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmsViewService, deps: [{ token: i2.AlarmService }, { token: i3.OptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmsViewService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmsViewService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i2.AlarmService }, { type: i3.OptionsService }] });
/**
* Pipe for transforming alarm severity types into corresponding icons.
*
* @example
* Usage in an Angular template:
* {{ 'CRITICAL' | AlarmSeverityToIcon }}
* Result: 'exclamation-circle'
*/
class AlarmSeverityToIconPipe {
/**
* Transforms an alarm severity type into a corresponding icon.
*
* @param alarmSeverity - The severity type of the alarm.
* @returns The corresponding icon for the given alarm severity type.
*/
transform(alarmSeverity) {
const alarmSeverityMapped = Severity[alarmSeverity?.toUpperCase()];
return ALARM_SEVERITY_ICON_MAP[alarmSeverityMapped] || HELP_ICON;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmSeverityToIconPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AlarmSeverityToIconPipe, isStandalone: true, name: "AlarmSeverityToIcon" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmSeverityToIconPipe, decorators: [{
type: Pipe,
args: [{
name: 'AlarmSeverityToIcon',
standalone: true
}]
}] });
/**
* Angular pipe for transforming alarm statuses into corresponding icons.
*
* @example
* Usage in an Angular template:
* {{ 'ACTIVE' | AlarmStatusToIcon }}
* Result: 'bell'
*/
class AlarmStatusToIconPipe {
/**
* Transforms an alarm status into a corresponding icon.
*
* @param alarmStatus - The status of the alarm.
* @returns - The corresponding icon for the given alarm status.
*/
transform(alarmStatus) {
return AlarmIconMap[alarmStatus?.toUpperCase()] || HELP_ICON;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmStatusToIconPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AlarmStatusToIconPipe, name: "AlarmStatusToIcon" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmStatusToIconPipe, decorators: [{
type: Pipe,
args: [{
name: 'AlarmStatusToIcon'
}]
}] });
/**
* A pipe for transforming audit record data into localized messages.
* It specifically addresses changes in the audit records, with an emphasis on status changes.
*/
class AuditChangesMessagePipe {
constructor(translateService) {
this.translateService = translateService;
}
/**
* Transforms an IAuditRecord into a localized string message.
* If the record contains changes, and if the first change is related to the 'status' attribute,
* it formats a message indicating the status change. Otherwise, it returns a general activity message.
* Example when there is a status change: "Alarm status changed from ACKNOWLEDGED to ACTIVE".
* Example when record does not have a status attribute: "Alarm updated".
*
* @param record - The audit record to be transformed.
* @returns The localized message describing the audit record,
* particularly focusing on status changes if applicable.
*/
transform(record) {
const firstItem = !!record.changes && Array.from(record.changes)[0];
if (!firstItem || firstItem.attribute !== 'status') {
const activityString = gettext(record.activity);
return this.translateService.instant(activityString);
}
const { newValue, previousValue } = firstItem;
const message = gettext(`Alarm status changed from {{ previousValue }} to {{ newValue }}`);
return this.translateService.instant(message, {
previousValue: this.translateService.instant(previousValue),
newValue: this.translateService.instant(newValue)
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuditChangesMessagePipe, deps: [{ token: i1.TranslateService }], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AuditChangesMessagePipe, name: "auditChangesMessage" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuditChangesMessagePipe, decorators: [{
type: Pipe,
args: [{
name: 'auditChangesMessage'
}]
}], ctorParameters: () => [{ type: i1.TranslateService }] });
class AlarmDetailsComponent {
constructor(alarmDetailsService, alarmService, alertService, appState, auditService, relativeTime, ng1SmartRulesUpgradeService, translateService, inventoryService, alarmsViewService, colorService, interAppService) {
this.alarmDetailsService = alarmDetailsService;
this.alarmService = alarmService;
this.alertService = alertService;
this.appState = appState;
this.auditService = auditService;
this.relativeTime = relativeTime;
this.ng1SmartRulesUpgradeService = ng1SmartRulesUpgradeService;
this.translateService = translateService;
this.inventoryService = inventoryService;
this.alarmsViewService = alarmsViewService;
this.colorService = colorService;
this.interAppService = interAppService;
this.ACKNOWLEDGED_STATUS_VALUE = AlarmStatus.ACKNOWLEDGED;
this.ACTIVE_STATUS_VALUE = AlarmStatus.ACTIVE;
this.CLEARED_STATUS_VALUE = AlarmStatus.CLEARED;
this.ACKNOWLEDGE_LABEL = gettext('Acknowledge');
this.REACTIVATE_LABEL = gettext('Reactivate');
this.SEVERITY_LABELS = SEVERITY_LABELS;
this.BELL_SLASH_ICON = ALARM_STATUS_ICON.BELL_SLASH;
this.BELL_ICON = ALARM_STATUS_ICON.BELL;
this.deviceManagementAppKey = SupportedApps.devicemanagement;
this.linkTitle = gettext('Open in {{ appName }}');
this.PAGE_SIZE = 100;
/**
* Indicates when alarms status change was started (Acknowledge/Reactivate)
*/
this.isAlarmStatusChanging = false;
/**
* Custom fragments of the selected alarm. If none exist, null is returned.
*/
this.customFragments = null;
}
async ngOnInit() {
const isSmartRulesServiceSubscribed = !!(await firstValueFrom(this.interAppService.getApp$(SupportedApps.smartrules)));
const hasAnyRoleAllowingToCreateSmartRule = this.alarmDetailsService.checkIfHasAnyRoleAllowingToCreateSmartRule();
this.isCreateSmartRulesButtonAvailable =
!!this.ng1SmartRulesUpgradeService &&
isSmartRulesServiceSubscribed &&
hasAnyRoleAllowingToCreateSmartRule;
this.userDeviceManagementApp$ = this.interAppService.getApp$(this.deviceManagementAppKey);
this.showSourceNavigationLink$ = this.interAppService.shouldShowAppLink$(this.deviceManagementAppKey);
this.typeColor = await this.colorService.generateColor(this.selectedAlarm.type);
}
async ngOnChanges(changes) {
if (changes.selectedAlarm && changes.selectedAlarm.currentValue) {
await this.reloadAuditLog(true, true);
await this.updateStatusMessage();
const { data } = await this.inventoryService.detail(this.selectedAlarm.source.id);
this.selectedAlarmMO = data;
this.customFragments = this.getCustomFragments(this.selectedAlarm);
}
}
createSmartRule() {
if (!this.isCreateSmartRulesButtonAvailable) {
return;
}
this.ng1SmartRulesUpgradeService.addNewForInputAlarmAndOutputUserWithUI(this.selectedAlarm, this.appState.currentUser.value);
}
/**
* Navigates to a specific alarm source device based on the provided source.
*
* @param sourceId - The source id.
*/
async goToAlarmSource(sourceId) {
const { data } = await this.alarmService.detail(sourceId);
await this.interAppService.navigateToApp(this.deviceManagementAppKey, `#/device/${data.source.id}/alarms`);
}
/**
* Reloads audit log data asynchronously.
*
* This method fetches audit records using `getAlarmAuditRecords` and optionally updates the audit logs
* state in the component based on the `isSetAuditLogs` flag. It handles the loading state and potential
* errors during the fetch operation.
*
* @param isRevert - A boolean flag indicating whether to retrieve a 100 (see PAGE_SIZE) records (true)
* or only record, that chronologically will be the oldest one (false). Defaults to true.
* If set to false, it will set PAGE_SIZE to 1 and trigger a logic
* concatenating a most recent record with the very first one to
* calculate the alarm duration (change to CLEARED status).
* It's passed to the `getAlarmAuditRecords` method.
* @param isSetAuditLogs - A boolean flag to determine if the fetched audit logs should be set in the component state. Defaults to `false`.
* @returns A promise that resolves to a list of `IAuditRecord` objects.
*/
async reloadAuditLog(isRevert = true, isSetAuditLogs = false) {
try {
this.isLoading = true;
const auditLogs = await this.getAlarmAuditRecords(isRevert);
if (isSetAuditLogs) {
this.setAuditLogs(auditLogs);
}
return auditLogs;
}
catch (error) {
this.alertService.addServerFailure(error);
}
finally {
this.isLoading = false;
}
}
async onUpdateDetails(status) {
try {
this.isAlarmStatusChanging = true;
await this.updateAlarmStatus(status);
await this.reloadAuditLog(true, true);
await this.updateStatusMessage();
this.updateLastUpdatedDate(this.auditLog.data[0]);
}
catch (error) {
this.alertService.addServerFailure(error);
}
finally {
this.isAlarmStatusChanging = false;
}
}
async detailsButtonAction(button, alarm) {
const result = button.action(alarm);
let shouldReload = false;
if (result instanceof Promise) {
shouldReload = await result;
}
else {
shouldReload = result;
}
if (shouldReload) {
let alarm;
if (shouldReload === true) {
const { data: updatedAlarm } = await this.alarmService.detail(this.selectedAlarm.id);
alarm = updatedAlarm;
}
else {
alarm = shouldReload;
}
this.alarmsViewService.updateAlarmList();
const previousValue = this.selectedAlarm;
this.selectedAlarm = alarm;
this.ngOnChanges({
selectedAlarm: {
currentValue: alarm,
previousValue,
firstChange: false,
isFirstChange: () => false
}
});
}
}
async updateAlarmStatus(status) {
const partiallyUpdatedAlarm = { id: this.selectedAlarm.id, status };
await this.alarmService.update(partiallyUpdatedAlarm);
const translatedStatusLabel = this.translateService.instant(ALARM_STATUS_LABELS[status]);
this.alertService.success(this.translateService.instant(gettext('Alarm status changed to {{ status }}'), {
status: translatedStatusLabel.toUpperCase()
}));
this.selectedAlarm.status = status;
this.alarmsViewService.updateAlarmList();
}
/**
* Retrieves the audit log and appends the last audit record to it.
*
* This method fetches the existing audit log data and makes a deep copy of it. It then
* retrieves the last audit record and appends it to the copied audit log data. This is
* useful for scenarios where the most recent audit record needs to be included in the
* existing audit log data (calculating the CLEARED period).
*
* @returns A promise of `IResultList<IAuditRecord>`, which includes the
* existing audit log data along with the last audit record appended.
* @private
*/
async auditLogWithFirstRecord() {
const existingData = this.auditLog;
const copiedExistingData = cloneDeep(existingData);
const lastAuditRecord = await this.reloadAuditLog(false);
const lastRecord = lastAuditRecord.data[lastAuditRecord.data.length - 1];
copiedExistingData.data.push(lastRecord);
return copiedExistingData;
}
setAuditLogs(auditLogs) {
this.auditLog = auditLogs;
}
updateLastUpdatedDate(updatedAuditRecords) {
if (!updatedAuditRecords) {
return;
}
const { creationTime } = updatedAuditRecords;
this.selectedAlarm.lastUpdated = creationTime;
}
getActiveStatusMessage(time) {
return this.translateService.instant(gettext('ACTIVE`alarm`: triggered {{alarmTimeFromNow}}'), {
alarmTimeFromNow: this.relativeTime.transform(new Date(time))
});
}
getAcknowledgedStatusMessage(status, changeLog) {
if (changeLog.length === 0) {
return this.translateService.instant(gettext('ACKNOWLEDGED`alarm`'));
}
const acknowledgedBy = this.alarmDetailsService.getAcknowledgedBy(status, changeLog);
const acknowledgeTime = this.alarmDetailsService.getAcknowledgeTime(changeLog);
if (acknowledgedBy) {
return this.translateService.instant(gettext('ACKNOWLEDGED`alarm` by: {{ackBy}} {{ackTimeFromNow}}'), {
ackBy: acknowledgedBy,
ackTimeFromNow: this.relativeTime.transform(new Date(acknowledgeTime))
});
}
return this.translateService.instant(gettext('ACKNOWLEDGED`alarm` {{ackTimeFromNow}}'), {
ackTimeFromNow: this.relativeTime.transform(new Date(acknowledgeTime))
});
}
getClearedStatusMessage(auditLog) {
if (auditLog.length === 0) {
return this.translateService.instant(gettext('CLEARED`alarm`'));
}
const differenceInMs = this.calculateAlarmDuration(auditLog);
return this.translateService.instant(gettext('CLEARED`alarm`: was active for {{alarmDuration}}'), {
alarmDuration: this.relativeTime.transform(differenceInMs, true)
});
}
/**
* Calculates the duration of an alarm based on audit log records.
*
* This method computes the duration of an alarm by finding the difference
* between the start and end times of the alarm. The start time is determined
* from the last record in the audit log, using the first available time field
* (`firstOccurrenceTime`, `time`, or `creationTime`). The end time is obtained
* from the `alarmDetailsService`.
*
* @param auditLog - An array of `IAuditRecord` objects representing the audit log records.
* @returns The duration of the alarm in milliseconds, or `null` if the end time is not available.
* @private
*/
calculateAlarmDuration(auditLog) {
const firstAlarm = auditLog[auditLog.length - 1];
const startTime = firstAlarm.firstOccurrenceTime || firstAlarm.time || firstAlarm.creationTime;
const endTime = this.alarmDetailsService.getEndTime(auditLog);
if (!endTime) {
return null;
}
const startTimeToDate = new Date(startTime);
const endTimeToDate = new Date(endTime);
return endTimeToDate.getTime() - startTimeToDate.getTime();
}
/**
* Retrieves a list of audit records for a selected alarm.
*
* This method fetches audit records based on the specified properties, including
* the date, page size, whether to revert, the source alarm ID, and whether to include total pages.
*
* @param isRevert - A boolean flag indicating whether to retrieve a 100 (see PAGE_SIZE) records (true)
* or only record, that chronologically will be the oldest one (false). Defaults to true.
* If set to false, it will set PAGE_SIZE to 1 and trigger a logic
* concatenating a most recent record with the very first one to
* calculate the alarm duration (change to CLEARED status).
* @returns A Promise that resolves to an IResultList of IAuditRecord objects, representing the audit records.
* @async
* @private
*/
async getAlarmAuditRecords(isRevert = true) {
const properties = {
dateTo: new Date(Date.now()).toISOString(),
pageSize: isRevert ? this.PAGE_SIZE : 1,
revert: isRevert,
source: this.selectedAlarm.id,
withTotalPages: true
};
return await this.auditService.list(properties);
}
async updateStatusMessage() {
switch (this.selectedAlarm.status) {
case this.ACTIVE_STATUS_VALUE:
this.statusMessage = this.getActiveStatusMessage(this.selectedAlarm.time);
break;
case this.ACKNOWLEDGED_STATUS_VALUE:
this.statusMessage = this.getAcknowledgedStatusMessage(this.selectedAlarm.status, this.auditLog.data);
break;
case this.CLEARED_STATUS_VALUE:
if (this.hasReachedOrExceededPageSizeLimit()) {
this.extendedAuditLogs = await this.auditLogWithFirstRecord();
this.statusMessage = this.getClearedStatusMessage(this.extendedAuditLogs.data);
return;
}
this.statusMessage = this.getClearedStatusMessage(this.auditLog.data);
break;
}
}
hasReachedOrExceededPageSizeLimit() {
return this.auditLog.data.length >= this.PAGE_SIZE;
}
getCustomFragments(selectedAlarm) {
let customProperties = null;
for (const key in selectedAlarm) {
if (!ALARM_DEFAULT_PROPERTIES.find(k => k === key)) {
if (!customProperties) {
customProperties = {};
}
customProperties[key] = selectedAlarm[key];
}
}
return customProperties;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmDetailsComponent, deps: [{ token: AlarmDetailsService }, { token: i2.AlarmService }, { token: i3.AlertService }, { token: i3.AppStateService }, { token: i2.AuditService }, { token: i3.RelativeTimePipe }, { token: Ng1SmartRulesUpgradeService, optional: true }, { token: i1.TranslateService }, { token: i2.InventoryService }, { token: AlarmsViewService }, { token: i3.ColorService }, { token: i3.InterAppService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: AlarmDetailsComponent, selector: "c8y-alarm-details", inputs: { selectedAlarm: "selectedAlarm" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"d-flex row tight-grid flex-wrap a-i-stretch\">\n <div class=\"col-xs-12 col-md-6 d-flex p-b-8\">\n <div\n class=\"border-all fit-w d-flex\"\n data-cy=\"c8y-alarm-details--status-section-wrapper\"\n >\n <div\n class=\"p-8\"\n data-cy=\"c8y-alarm-details--status-icon\"\n >\n <i\n class=\"icon-24 text-gray-dark m-t-4 c8y-icon\"\n [c8yIcon]=\"selectedAlarm.status | AlarmStatusToIcon\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Status' | translate }}</p>\n <p class=\"small\">{{ statusMessage }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-md-6 d-flex p-b-8\">\n <div\n class=\"border-all fit-w d-flex\"\n data-cy=\"c8y-alarm-details--severity-section-wrapper\"\n >\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4 stroked-icon status\"\n [c8yIcon]=\"selectedAlarm.severity | AlarmSeverityToIcon\"\n [ngClass]=\"selectedAlarm.severity?.toString() | lowercase\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Severity' | translate }}</p>\n <p class=\"small\">{{ SEVERITY_LABELS[selectedAlarm.severity] | translate }}</p>\n </div>\n </div>\n </div>\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-alarm-details--source-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4 stroked-icon status\"\n c8yIcon=\"contactless-payment\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Source' | translate }}</p>\n <p class=\"small\">\n <button\n class=\"btn-link text-muted p-0 m-r-8 text-left\"\n title=\"{{ selectedAlarm.source.name }}\"\n type=\"button\"\n routerLink=\"{{ selectedAlarmMO | assetLink }}\"\n >\n <small class=\"icon-flex\">\n <i c8yIcon=\"exchange\"></i>\n {{ selectedAlarm.source.name || selectedAlarm.source.id }}\n </small>\n </button>\n <ng-container *ngIf=\"showSourceNavigationLink$ | async\">\n <button\n class=\"btn-link p-0 text-left\"\n title=\"{{\n linkTitle\n | translate\n : { appName: userDeviceManagementApp$ | async | humanizeAppName | async }\n }}\"\n type=\"button\"\n (click)=\"goToAlarmSource(selectedAlarm.id)\"\n data-cy=\"alarm-details-device-management-link\"\n >\n {{ userDeviceManagementApp$ | async | humanizeAppName | async }}\n <i c8yIcon=\"external-link\"></i>\n </button>\n </ng-container>\n </p>\n </div>\n </div>\n </div>\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-alarm-details--severity-type-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <span\n class=\"circle-icon-wrapper\"\n [ngStyle]=\"{ 'background-color': typeColor }\"\n >\n <i\n class=\"stroked-icon\"\n c8yIcon=\"bell\"\n ></i>\n </span>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 min-width-0\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Type' | translate }}</p>\n <p\n class=\"small text-truncate\"\n title=\"{{ selectedAlarm.type }}\"\n >\n <code>{{ selectedAlarm.type }}</code>\n </p>\n </div>\n </div>\n </div>\n\n <div class=\"col-xs-12 col-md-12 p-b-16\">\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n data-cy=\"c8y-alarm-details--last-updated-icon\"\n c8yIcon=\"calendar\"\n data-cy=\"c8y-alarm-details--last-updated-icon\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-0 p-r-8 flex-grow\">\n <div class=\"content-flex-50\">\n <div\n class=\"col-4 p-b-8\"\n *ngIf=\"selectedAlarm.count > 1\"\n data-cy=\"c8y-alarm-details--number-of-occurrences-wrapper\"\n >\n