thingsboard/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts

1032 lines
34 KiB
TypeScript
Raw Normal View History

///
2020-02-20 10:26:43 +02:00
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
NgZone,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { WidgetAction, WidgetContext } from '@home/models/widget-component.models';
2020-07-03 18:33:06 +03:00
import { DataKey, Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
2020-07-03 18:33:06 +03:00
import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber } from '@core/utils';
import cssjs from '@core/css/css';
2020-07-03 18:33:06 +03:00
import { sortItems } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections';
2020-07-03 18:33:06 +03:00
import { BehaviorSubject, forkJoin, fromEvent, merge, Observable } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
2020-07-03 18:33:06 +03:00
import { debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator';
2020-07-03 18:33:06 +03:00
import { MatSort, SortDirection } from '@angular/material/sort';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import {
CellContentInfo,
CellStyleInfo,
constructTableCssString,
DisplayColumn,
EntityColumn,
2020-07-03 18:33:06 +03:00
entityDataSortOrderFromString,
findColumnByEntityKey,
findEntityKeyByColumnDef,
fromEntityColumnDef,
getAlarmValue,
getCellContentInfo,
getCellStyleInfo,
getColumnWidth,
TableWidgetDataKeySettings,
TableWidgetSettings,
widthStyle
} from '@home/components/widget/lib/table-widget.models';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import {
DISPLAY_COLUMNS_PANEL_DATA,
DisplayColumnsPanelComponent,
DisplayColumnsPanelData
} from '@home/components/widget/lib/display-columns-panel.component';
import {
2020-07-03 18:33:06 +03:00
AlarmDataInfo,
alarmFields,
2020-07-06 14:42:36 +03:00
AlarmSearchStatus,
alarmSeverityColors,
alarmSeverityTranslations,
AlarmStatus,
alarmStatusTranslations
} from '@shared/models/alarm.models';
import { DatePipe } from '@angular/common';
2020-01-29 17:20:28 +02:00
import {
AlarmDetailsDialogComponent,
AlarmDetailsDialogData
} from '@home/components/alarm/alarm-details-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { DialogService } from '@core/services/dialog.service';
import { AlarmService } from '@core/http/alarm.service';
2020-07-03 18:33:06 +03:00
import {
AlarmData,
AlarmDataPageLink,
dataKeyToEntityKey,
dataKeyTypeToEntityKeyType,
entityDataPageLinkSortDirection,
KeyFilter
} from '@app/shared/models/query/query.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
2020-07-06 14:42:36 +03:00
import {
ALARM_FILTER_PANEL_DATA,
AlarmFilterPanelComponent,
AlarmFilterPanelData
} from '@home/components/widget/lib/alarm-filter-panel.component';
import { entityFields } from '@shared/models/entity.models';
interface AlarmsTableWidgetSettings extends TableWidgetSettings {
alarmsTitle: string;
enableSelection: boolean;
2020-07-06 14:42:36 +03:00
enableStatusFilter?: boolean;
enableFilter: boolean;
enableStickyAction: boolean;
displayDetails: boolean;
allowAcknowledgment: boolean;
allowClear: boolean;
}
interface AlarmWidgetActionDescriptor extends WidgetActionDescriptor {
details?: boolean;
acknowledge?: boolean;
clear?: boolean;
}
@Component({
selector: 'tb-alarms-table-widget',
templateUrl: './alarms-table-widget.component.html',
styleUrls: ['./alarms-table-widget.component.scss', './table-widget.scss']
})
export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit {
@Input()
ctx: WidgetContext;
2020-02-10 13:10:14 +02:00
@ViewChild('searchInput') searchInputField: ElementRef;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
public enableSelection = true;
public displayPagination = true;
public enableStickyAction = false;
public pageSizeOptions;
2020-07-03 18:33:06 +03:00
public pageLink: AlarmDataPageLink;
public sortOrderProperty: string;
public textSearchMode = false;
public columns: Array<EntityColumn> = [];
public displayedColumns: string[] = [];
public actionCellDescriptors: AlarmWidgetActionDescriptor[] = [];
public alarmsDatasource: AlarmsDatasource;
private settings: AlarmsTableWidgetSettings;
private widgetConfig: WidgetConfig;
private subscription: IWidgetSubscription;
private alarmsTitlePattern: string;
private displayDetails = true;
private allowAcknowledgment = true;
private allowClear = true;
private defaultPageSize = 10;
private defaultSortOrder = '-' + alarmFields.createdTime.value;
private contentsInfo: {[key: string]: CellContentInfo} = {};
private stylesInfo: {[key: string]: CellStyleInfo} = {};
private columnWidth: {[key: string]: string} = {};
private searchAction: WidgetAction = {
name: 'action.search',
show: true,
icon: 'search',
onAction: () => {
this.enterFilterMode();
}
};
private columnDisplayAction: WidgetAction = {
name: 'entity.columns-to-display',
show: true,
icon: 'view_column',
onAction: ($event) => {
this.editColumnsToDisplay($event);
}
};
2020-07-06 14:42:36 +03:00
private alarmFilterAction: WidgetAction = {
name: 'alarm.alarm-filter',
show: true,
onAction: ($event) => {
2020-07-06 14:42:36 +03:00
this.editAlarmFilter($event);
},
icon: 'filter_list'
};
constructor(protected store: Store<AppState>,
private elementRef: ElementRef,
private ngZone: NgZone,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private utils: UtilsService,
public translate: TranslateService,
private domSanitizer: DomSanitizer,
2020-01-29 17:20:28 +02:00
private datePipe: DatePipe,
private dialog: MatDialog,
private dialogService: DialogService,
private alarmService: AlarmService) {
super(store);
2020-07-03 18:33:06 +03:00
this.pageLink = {
page: 0,
pageSize: this.defaultPageSize,
textSearch: null
};
}
ngOnInit(): void {
this.ctx.$scope.alarmsTableWidget = this;
this.settings = this.ctx.settings;
this.widgetConfig = this.ctx.widgetConfig;
this.subscription = this.ctx.defaultSubscription;
this.initializeConfig();
this.updateAlarmSource();
this.ctx.updateWidgetParams();
}
ngAfterViewInit(): void {
fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
if (this.displayPagination) {
this.paginator.pageIndex = 0;
}
this.updateData();
})
)
.subscribe();
if (this.displayPagination) {
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
}
2020-08-13 19:52:17 +03:00
((this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange) as Observable<any>)
.pipe(
tap(() => this.updateData())
)
.subscribe();
this.updateData();
}
public onDataUpdated() {
this.ngZone.run(() => {
this.updateTitle(true);
2020-07-03 18:33:06 +03:00
this.alarmsDatasource.updateAlarms();
this.ctx.detectChanges();
});
}
2020-07-03 18:33:06 +03:00
public pageLinkSortDirection(): SortDirection {
return entityDataPageLinkSortDirection(this.pageLink);
}
private initializeConfig() {
2020-07-06 14:42:36 +03:00
this.ctx.widgetActions = [this.searchAction, this.alarmFilterAction, this.columnDisplayAction];
this.displayDetails = isDefined(this.settings.displayDetails) ? this.settings.displayDetails : true;
this.allowAcknowledgment = isDefined(this.settings.allowAcknowledgment) ? this.settings.allowAcknowledgment : true;
this.allowClear = isDefined(this.settings.allowClear) ? this.settings.allowClear : true;
if (this.displayDetails) {
this.actionCellDescriptors.push(
{
displayName: this.translate.instant('alarm.details'),
icon: 'more_horiz',
details: true
} as AlarmWidgetActionDescriptor
);
}
if (this.allowAcknowledgment) {
this.actionCellDescriptors.push(
{
displayName: this.translate.instant('alarm.acknowledge'),
icon: 'done',
acknowledge: true
} as AlarmWidgetActionDescriptor
);
}
if (this.allowClear) {
this.actionCellDescriptors.push(
{
displayName: this.translate.instant('alarm.clear'),
icon: 'clear',
clear: true
} as AlarmWidgetActionDescriptor
);
}
this.actionCellDescriptors = this.actionCellDescriptors.concat(this.ctx.actionsApi.getActionDescriptors('actionCellButton'));
if (this.settings.alarmsTitle && this.settings.alarmsTitle.length) {
this.alarmsTitlePattern = this.utils.customTranslation(this.settings.alarmsTitle, this.settings.alarmsTitle);
} else {
this.alarmsTitlePattern = this.translate.instant('alarm.alarms');
}
this.updateTitle(false);
this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true;
if (!this.allowAcknowledgment && !this.allowClear) {
this.enableSelection = false;
}
this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true;
this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true;
this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : false;
this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true;
2020-07-06 14:42:36 +03:00
let enableFilter;
if (isDefined(this.settings.enableFilter)) {
enableFilter = this.settings.enableFilter;
} else if (isDefined(this.settings.enableStatusFilter)) {
enableFilter = this.settings.enableStatusFilter;
} else {
enableFilter = true;
}
this.alarmFilterAction.show = enableFilter;
const pageSize = this.settings.defaultPageSize;
if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) {
this.defaultPageSize = pageSize;
}
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
2020-07-06 14:42:36 +03:00
this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms)
? this.widgetConfig.searchPropagatedAlarms : true;
let alarmStatusList: AlarmSearchStatus[] = [];
if (isDefined(this.widgetConfig.alarmStatusList) && this.widgetConfig.alarmStatusList.length) {
alarmStatusList = this.widgetConfig.alarmStatusList;
} else if (isDefined(this.widgetConfig.alarmSearchStatus) && this.widgetConfig.alarmSearchStatus !== AlarmSearchStatus.ANY) {
alarmStatusList = [this.widgetConfig.alarmSearchStatus];
}
this.pageLink.statusList = alarmStatusList;
this.pageLink.severityList = isDefined(this.widgetConfig.alarmSeverityList) ? this.widgetConfig.alarmSeverityList : [];
this.pageLink.typeList = isDefined(this.widgetConfig.alarmTypeList) ? this.widgetConfig.alarmTypeList : [];
2020-07-03 18:33:06 +03:00
const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs();
cssParser.testMode = false;
2020-05-05 11:57:35 +03:00
const namespace = 'alarms-table-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace);
}
private updateTitle(updateWidgetParams = false) {
const newTitle = createLabelFromDatasource(this.subscription.alarmSource, this.alarmsTitlePattern);
if (this.ctx.widgetTitle !== newTitle) {
this.ctx.widgetTitle = newTitle;
if (updateWidgetParams) {
this.ctx.updateWidgetParams();
}
}
}
private updateAlarmSource() {
if (this.enableSelection) {
this.displayedColumns.push('select');
}
2020-07-03 18:33:06 +03:00
const latestDataKeys: Array<DataKey> = [];
if (this.subscription.alarmSource) {
this.subscription.alarmSource.dataKeys.forEach((alarmDataKey) => {
const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn;
2020-07-03 18:33:06 +03:00
dataKey.entityKey = dataKeyToEntityKey(alarmDataKey);
dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
dataKey.def = 'def' + this.columns.length;
const keySettings: TableWidgetDataKeySettings = dataKey.settings;
if (dataKey.type === DataKeyType.alarm && !isDefined(keySettings.columnWidth)) {
const alarmField = alarmFields[dataKey.name];
if (alarmField && alarmField.time) {
keySettings.columnWidth = '120px';
}
}
this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
this.contentsInfo[dataKey.def].units = dataKey.units;
this.contentsInfo[dataKey.def].decimals = dataKey.decimals;
this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
this.columns.push(dataKey);
2020-07-03 18:33:06 +03:00
if (dataKey.type !== DataKeyType.alarm) {
latestDataKeys.push(dataKey);
}
});
this.displayedColumns.push(...this.columns.map(column => column.def));
}
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
this.defaultSortOrder = this.settings.defaultSortOrder;
}
2020-07-03 18:33:06 +03:00
this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns);
let sortColumn: EntityColumn;
if (this.pageLink.sortOrder) {
sortColumn = findColumnByEntityKey(this.pageLink.sortOrder.key, this.columns);
}
this.sortOrderProperty = sortColumn ? sortColumn.def : null;
if (this.actionCellDescriptors.length) {
this.displayedColumns.push('actions');
}
2020-07-03 18:33:06 +03:00
this.alarmsDatasource = new AlarmsDatasource(this.subscription, latestDataKeys);
if (this.enableSelection) {
this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => {
const hideTitlePanel = selectionMode || this.textSearchMode;
if (this.ctx.hideTitlePanel !== hideTitlePanel) {
this.ctx.hideTitlePanel = hideTitlePanel;
this.ctx.detectChanges(true);
} else {
this.ctx.detectChanges();
}
});
}
}
private editColumnsToDisplay($event: Event) {
if ($event) {
$event.stopPropagation();
}
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const columns: DisplayColumn[] = this.columns.map(column => {
return {
title: column.title,
def: column.def,
display: this.displayedColumns.indexOf(column.def) > -1
};
});
const injectionTokens = new WeakMap<any, any>([
[DISPLAY_COLUMNS_PANEL_DATA, {
columns,
columnsUpdated: (newColumns) => {
this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def);
if (this.enableSelection) {
this.displayedColumns.unshift('select');
}
this.displayedColumns.push('actions');
}
} as DisplayColumnsPanelData],
[OverlayRef, overlayRef]
]);
const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens);
overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent,
this.viewContainerRef, injector));
this.ctx.detectChanges();
}
2020-07-06 14:42:36 +03:00
private editAlarmFilter($event: Event) {
2020-01-29 17:20:28 +02:00
if ($event) {
$event.stopPropagation();
}
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const injectionTokens = new WeakMap<any, any>([
2020-07-06 14:42:36 +03:00
[ALARM_FILTER_PANEL_DATA, {
statusList: this.pageLink.statusList,
severityList: this.pageLink.severityList,
typeList: this.pageLink.typeList
} as AlarmFilterPanelData],
2020-01-29 17:20:28 +02:00
[OverlayRef, overlayRef]
]);
const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens);
2020-07-06 14:42:36 +03:00
const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterPanelComponent,
2020-01-29 17:20:28 +02:00
this.viewContainerRef, injector));
2020-07-06 14:42:36 +03:00
componentRef.onDestroy(() => {
if (componentRef.instance.result) {
const result = componentRef.instance.result;
this.pageLink.statusList = result.statusList;
this.pageLink.severityList = result.severityList;
this.pageLink.typeList = result.typeList;
this.updateData();
}
});
2020-01-29 17:20:28 +02:00
this.ctx.detectChanges();
}
private enterFilterMode() {
this.textSearchMode = true;
this.pageLink.textSearch = '';
this.ctx.hideTitlePanel = true;
this.ctx.detectChanges(true);
setTimeout(() => {
this.searchInputField.nativeElement.focus();
this.searchInputField.nativeElement.setSelectionRange(0, 0);
}, 10);
}
exitFilterMode() {
this.textSearchMode = false;
this.pageLink.textSearch = null;
if (this.displayPagination) {
this.paginator.pageIndex = 0;
}
this.updateData();
this.ctx.hideTitlePanel = false;
this.ctx.detectChanges(true);
}
private updateData() {
if (this.displayPagination) {
this.pageLink.page = this.paginator.pageIndex;
this.pageLink.pageSize = this.paginator.pageSize;
} else {
this.pageLink.page = 0;
}
const key = findEntityKeyByColumnDef(this.sort.active, this.columns);
if (key) {
this.pageLink.sortOrder = {
key,
direction: Direction[this.sort.direction.toUpperCase()]
};
} else {
this.pageLink.sortOrder = null;
}
2020-07-03 18:33:06 +03:00
const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
const keyFilters: KeyFilter[] = null; // TODO:
this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters);
this.ctx.detectChanges();
}
public trackByColumnDef(index, column: EntityColumn) {
return column.def;
}
public headerStyle(key: EntityColumn): any {
const columnWidth = this.columnWidth[key.def];
2020-02-04 19:50:18 +02:00
return widthStyle(columnWidth);
}
2020-07-03 18:33:06 +03:00
public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any {
let style: any = {};
if (alarm && key) {
const styleInfo = this.stylesInfo[key.def];
const value = getAlarmValue(alarm, key);
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
try {
style = styleInfo.cellStyleFunction(value);
} catch (e) {
style = {};
}
} else {
style = this.defaultStyle(key, value);
}
}
if (!style.width) {
const columnWidth = this.columnWidth[key.def];
2020-02-04 19:50:18 +02:00
style = {...style, ...widthStyle(columnWidth)};
}
return style;
}
2020-07-03 18:33:06 +03:00
public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml {
if (alarm && key) {
const contentInfo = this.contentsInfo[key.def];
const value = getAlarmValue(alarm, key);
2020-01-30 13:03:53 +02:00
let content = '';
if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
try {
content = contentInfo.cellContentFunction(value, alarm, this.ctx);
} catch (e) {
2020-01-30 13:03:53 +02:00
content = '' + value;
}
} else {
2020-07-06 14:42:36 +03:00
content = this.defaultContent(key, contentInfo, value);
}
2020-01-30 13:03:53 +02:00
return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : '';
} else {
2020-01-30 13:03:53 +02:00
return '';
}
}
2020-07-03 18:33:06 +03:00
public onRowClick($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
this.alarmsDatasource.toggleCurrentAlarm(alarm);
const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick');
if (descriptors.length) {
let entityId;
let entityName;
if (alarm && alarm.originator) {
entityId = alarm.originator;
entityName = alarm.originatorName;
}
this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {alarm});
}
}
2020-07-03 18:33:06 +03:00
public onActionButtonClick($event: Event, alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor) {
if (actionDescriptor.details) {
this.openAlarmDetails($event, alarm);
} else if (actionDescriptor.acknowledge) {
this.ackAlarm($event, alarm);
} else if (actionDescriptor.clear) {
this.clearAlarm($event, alarm);
} else {
if ($event) {
$event.stopPropagation();
}
let entityId;
let entityName;
if (alarm && alarm.originator) {
entityId = alarm.originator;
entityName = alarm.originatorName;
}
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm});
}
}
2020-07-03 18:33:06 +03:00
public actionEnabled(alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean {
if (actionDescriptor.acknowledge) {
return (alarm.status === AlarmStatus.ACTIVE_UNACK ||
alarm.status === AlarmStatus.CLEARED_UNACK);
} else if (actionDescriptor.clear) {
return (alarm.status === AlarmStatus.ACTIVE_ACK ||
alarm.status === AlarmStatus.ACTIVE_UNACK);
}
return true;
}
2020-07-03 18:33:06 +03:00
private openAlarmDetails($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
2020-01-29 17:20:28 +02:00
if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
(AlarmDetailsDialogComponent,
{
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
alarmId: alarm.id.id,
allowAcknowledgment: this.allowAcknowledgment,
allowClear: this.allowClear,
displayDetails: true
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.subscription.update();
}
}
);
}
}
2020-07-03 18:33:06 +03:00
private ackAlarm($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
2020-01-29 17:20:28 +02:00
if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
this.dialogService.confirm(
this.translate.instant('alarm.aknowledge-alarm-title'),
this.translate.instant('alarm.aknowledge-alarm-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes')
).subscribe((res) => {
if (res) {
if (res) {
this.alarmService.ackAlarm(alarm.id.id).subscribe(() => {
this.subscription.update();
});
}
}
});
}
}
public ackAlarms($event: Event) {
if ($event) {
$event.stopPropagation();
}
2020-01-29 17:20:28 +02:00
if (this.alarmsDatasource.selection.hasValue()) {
2020-07-03 18:33:06 +03:00
const alarmIds = this.alarmsDatasource.selection.selected.filter(
(alarmId) => alarmId !== NULL_UUID
2020-01-29 17:20:28 +02:00
);
2020-07-03 18:33:06 +03:00
if (alarmIds.length) {
const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarmIds.length});
const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarmIds.length});
2020-01-29 17:20:28 +02:00
this.dialogService.confirm(
title,
content,
this.translate.instant('action.no'),
this.translate.instant('action.yes')
).subscribe((res) => {
if (res) {
if (res) {
const tasks: Observable<void>[] = [];
2020-07-03 18:33:06 +03:00
for (const alarmId of alarmIds) {
tasks.push(this.alarmService.ackAlarm(alarmId));
2020-01-29 17:20:28 +02:00
}
forkJoin(tasks).subscribe(() => {
this.alarmsDatasource.clearSelection();
this.subscription.update();
});
}
}
});
}
}
}
2020-07-03 18:33:06 +03:00
private clearAlarm($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
2020-01-29 17:20:28 +02:00
if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
this.dialogService.confirm(
this.translate.instant('alarm.clear-alarm-title'),
this.translate.instant('alarm.clear-alarm-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes')
).subscribe((res) => {
if (res) {
if (res) {
this.alarmService.clearAlarm(alarm.id.id).subscribe(() => {
this.subscription.update();
});
}
}
});
}
}
public clearAlarms($event: Event) {
if ($event) {
$event.stopPropagation();
}
2020-01-29 17:20:28 +02:00
if (this.alarmsDatasource.selection.hasValue()) {
2020-07-03 18:33:06 +03:00
const alarmIds = this.alarmsDatasource.selection.selected.filter(
(alarmId) => alarmId !== NULL_UUID
2020-01-29 17:20:28 +02:00
);
2020-07-03 18:33:06 +03:00
if (alarmIds.length) {
const title = this.translate.instant('alarm.clear-alarms-title', {count: alarmIds.length});
const content = this.translate.instant('alarm.clear-alarms-text', {count: alarmIds.length});
2020-01-29 17:20:28 +02:00
this.dialogService.confirm(
title,
content,
this.translate.instant('action.no'),
this.translate.instant('action.yes')
).subscribe((res) => {
if (res) {
if (res) {
const tasks: Observable<void>[] = [];
2020-07-03 18:33:06 +03:00
for (const alarmId of alarmIds) {
tasks.push(this.alarmService.clearAlarm(alarmId));
2020-01-29 17:20:28 +02:00
}
forkJoin(tasks).subscribe(() => {
this.alarmsDatasource.clearSelection();
this.subscription.update();
});
}
}
});
}
}
}
2020-07-06 14:42:36 +03:00
private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any {
if (isDefined(value)) {
const alarmField = alarmFields[key.name];
if (alarmField) {
if (alarmField.time) {
return value ? this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss') : '';
} else if (alarmField.value === alarmFields.severity.value) {
return this.translate.instant(alarmSeverityTranslations.get(value));
} else if (alarmField.value === alarmFields.status.value) {
return this.translate.instant(alarmStatusTranslations.get(value));
} else if (alarmField.value === alarmFields.originatorType.value) {
return this.translate.instant(entityTypeTranslations.get(value).type);
} else {
return value;
}
}
2020-07-06 14:42:36 +03:00
const entityField = entityFields[key.name];
if (entityField) {
if (entityField.time) {
return this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss');
}
}
const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals;
const units = contentInfo.units || this.ctx.widgetConfig.units;
return this.ctx.utils.formatValue(value, decimals, units, true);
} else {
return '';
}
}
private defaultStyle(key: EntityColumn, value: any): any {
if (isDefined(value)) {
const alarmField = alarmFields[key.name];
if (alarmField) {
if (alarmField.value === alarmFields.severity.value) {
return {
fontWeight: 'bold',
color: alarmSeverityColors.get(value)
};
} else {
return {};
}
} else {
return {};
}
} else {
return {};
}
}
isSorting(column: EntityColumn): boolean {
return column.type === DataKeyType.alarm && column.name.startsWith('details.');
}
}
2020-07-03 18:33:06 +03:00
class AlarmsDatasource implements DataSource<AlarmDataInfo> {
2020-07-03 18:33:06 +03:00
private alarmsSubject = new BehaviorSubject<AlarmDataInfo[]>([]);
private pageDataSubject = new BehaviorSubject<PageData<AlarmDataInfo>>(emptyPageData<AlarmDataInfo>());
2020-07-03 18:33:06 +03:00
public selection = new SelectionModel<string>(true, [], false);
private selectionModeChanged = new EventEmitter<boolean>();
2020-07-03 18:33:06 +03:00
public selectionModeChanged$ = this.selectionModeChanged.asObservable();
private currentAlarm: AlarmDataInfo = null;
2020-07-03 18:33:06 +03:00
public dataLoading = true;
2020-07-03 18:33:06 +03:00
private appliedPageLink: AlarmDataPageLink;
private appliedSortOrderLabel: string;
2020-07-03 18:33:06 +03:00
constructor(private subscription: IWidgetSubscription,
private dataKeys: Array<DataKey>) {
}
2020-07-03 18:33:06 +03:00
connect(collectionViewer: CollectionViewer): Observable<AlarmDataInfo[] | ReadonlyArray<AlarmDataInfo>> {
return this.alarmsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.alarmsSubject.complete();
this.pageDataSubject.complete();
}
2020-07-03 18:33:06 +03:00
loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
2020-07-06 14:42:36 +03:00
this.dataLoading = true;
// this.clear();
2020-07-06 14:42:36 +03:00
this.appliedPageLink = pageLink;
this.appliedSortOrderLabel = sortOrderLabel;
this.subscription.subscribeForAlarms(pageLink, keyFilters);
}
private clear() {
if (this.selection.hasValue()) {
this.selection.clear();
this.onSelectionModeChanged(false);
}
2020-07-06 14:42:36 +03:00
this.alarmsSubject.next([]);
this.pageDataSubject.next(emptyPageData<AlarmDataInfo>());
}
2020-07-03 18:33:06 +03:00
updateAlarms() {
const subscriptionAlarms = this.subscription.alarms;
let alarms = new Array<AlarmDataInfo>();
subscriptionAlarms.data.forEach((alarmData) => {
alarms.push(this.alarmDataToInfo(alarmData));
});
2020-07-03 18:33:06 +03:00
if (this.appliedSortOrderLabel && this.appliedSortOrderLabel.length) {
const asc = this.appliedPageLink.sortOrder.direction === Direction.ASC;
alarms = alarms.sort((a, b) => sortItems(a, b, this.appliedSortOrderLabel, asc));
}
if (this.selection.hasValue()) {
2020-07-03 18:33:06 +03:00
const alarmIds = alarms.map((alarm) => alarm.id.id);
const toRemove = this.selection.selected.filter(alarmId => alarmIds.indexOf(alarmId) === -1);
this.selection.deselect(...toRemove);
if (this.selection.isEmpty()) {
this.onSelectionModeChanged(false);
}
}
2020-07-03 18:33:06 +03:00
const alarmsPageData: PageData<AlarmDataInfo> = {
data: alarms,
totalPages: subscriptionAlarms.totalPages,
totalElements: subscriptionAlarms.totalElements,
hasNext: subscriptionAlarms.hasNext
};
this.alarmsSubject.next(alarms);
this.pageDataSubject.next(alarmsPageData);
this.dataLoading = false;
}
private alarmDataToInfo(alarmData: AlarmData): AlarmDataInfo {
const alarm: AlarmDataInfo = deepClone(alarmData);
delete alarm.latest;
const latest = alarmData.latest;
this.dataKeys.forEach((dataKey, index) => {
const type = dataKeyTypeToEntityKeyType(dataKey.type);
let value = '';
if (type) {
if (latest && latest[type]) {
const tsVal = latest[type][dataKey.name];
if (tsVal) {
value = tsVal.value;
}
}
}
alarm[dataKey.name] = value;
2020-07-03 18:33:06 +03:00
});
return alarm;
}
isAllSelected(): Observable<boolean> {
const numSelected = this.selection.selected.length;
return this.alarmsSubject.pipe(
map((alarms) => numSelected === alarms.length)
);
}
isEmpty(): Observable<boolean> {
return this.alarmsSubject.pipe(
map((alarms) => !alarms.length)
);
}
total(): Observable<number> {
return this.pageDataSubject.pipe(
map((pageData) => pageData.totalElements)
);
}
2020-07-03 18:33:06 +03:00
toggleSelection(alarm: AlarmDataInfo) {
const hasValue = this.selection.hasValue();
2020-07-03 18:33:06 +03:00
this.selection.toggle(alarm.id.id);
if (hasValue !== this.selection.hasValue()) {
this.onSelectionModeChanged(this.selection.hasValue());
}
}
2020-07-03 18:33:06 +03:00
isSelected(alarm: AlarmDataInfo): boolean {
return this.selection.isSelected(alarm.id.id);
}
2020-01-29 17:20:28 +02:00
clearSelection() {
if (this.selection.hasValue()) {
this.selection.clear();
this.onSelectionModeChanged(false);
}
}
masterToggle() {
this.alarmsSubject.pipe(
tap((alarms) => {
const numSelected = this.selection.selected.length;
if (numSelected === alarms.length) {
this.selection.clear();
if (numSelected > 0) {
this.onSelectionModeChanged(false);
}
} else {
alarms.forEach(row => {
2020-07-03 18:33:06 +03:00
this.selection.select(row.id.id);
});
if (numSelected === 0) {
this.onSelectionModeChanged(true);
}
}
}),
take(1)
).subscribe();
}
2020-07-03 18:33:06 +03:00
public toggleCurrentAlarm(alarm: AlarmDataInfo): boolean {
if (this.currentAlarm !== alarm) {
this.currentAlarm = alarm;
return true;
} else {
return false;
}
}
2020-07-03 18:33:06 +03:00
public isCurrentAlarm(alarm: AlarmDataInfo): boolean {
return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) &&
(this.currentAlarm.id.id === alarm.id.id);
}
private onSelectionModeChanged(selectionMode: boolean) {
this.selectionModeChanged.emit(selectionMode);
}
}