Commit bd8af1111eecf4ddd0c708278a02ba685985eda0

Authored by Igor Kulikov
1 parent ae3ea313

Implement Alarm widget

@@ -259,5 +259,7 @@ export interface IWidgetSubscription { @@ -259,5 +259,7 @@ export interface IWidgetSubscription {
259 259
260 destroy(): void; 260 destroy(): void;
261 261
  262 + update(): void;
  263 +
262 [key: string]: any; 264 [key: string]: any;
263 } 265 }
@@ -30,7 +30,6 @@ import { RelationTableComponent } from '@home/components/relation/relation-table @@ -30,7 +30,6 @@ import { RelationTableComponent } from '@home/components/relation/relation-table
30 import { RelationDialogComponent } from './relation/relation-dialog.component'; 30 import { RelationDialogComponent } from './relation/relation-dialog.component';
31 import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; 31 import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
32 import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; 32 import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
33 -import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component';  
34 import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component'; 33 import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component';
35 import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; 34 import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component';
36 import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; 35 import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component';
@@ -64,6 +63,7 @@ import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-d @@ -64,6 +63,7 @@ import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-d
64 import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component'; 63 import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component';
65 import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component'; 64 import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component';
66 import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; 65 import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component';
  66 +import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
67 67
68 @NgModule({ 68 @NgModule({
69 entryComponents: [ 69 entryComponents: [
@@ -73,7 +73,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten @@ -73,7 +73,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten
73 EventTableHeaderComponent, 73 EventTableHeaderComponent,
74 RelationDialogComponent, 74 RelationDialogComponent,
75 AlarmTableHeaderComponent, 75 AlarmTableHeaderComponent,
76 - AlarmDetailsDialogComponent,  
77 AddAttributeDialogComponent, 76 AddAttributeDialogComponent,
78 EditAttributeValuePanelComponent, 77 EditAttributeValuePanelComponent,
79 AliasesEntitySelectPanelComponent, 78 AliasesEntitySelectPanelComponent,
@@ -104,7 +103,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten @@ -104,7 +103,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten
104 RelationFiltersComponent, 103 RelationFiltersComponent,
105 AlarmTableHeaderComponent, 104 AlarmTableHeaderComponent,
106 AlarmTableComponent, 105 AlarmTableComponent,
107 - AlarmDetailsDialogComponent,  
108 AttributeTableComponent, 106 AttributeTableComponent,
109 AddAttributeDialogComponent, 107 AddAttributeDialogComponent,
110 EditAttributeValuePanelComponent, 108 EditAttributeValuePanelComponent,
@@ -136,7 +134,8 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten @@ -136,7 +134,8 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten
136 ], 134 ],
137 imports: [ 135 imports: [
138 CommonModule, 136 CommonModule,
139 - SharedModule 137 + SharedModule,
  138 + SharedHomeComponentsModule
140 ], 139 ],
141 exports: [ 140 exports: [
142 EntitiesTableComponent, 141 EntitiesTableComponent,
@@ -149,7 +148,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten @@ -149,7 +148,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten
149 RelationTableComponent, 148 RelationTableComponent,
150 RelationFiltersComponent, 149 RelationFiltersComponent,
151 AlarmTableComponent, 150 AlarmTableComponent,
152 - AlarmDetailsDialogComponent,  
153 AttributeTableComponent, 151 AttributeTableComponent,
154 AliasesEntitySelectComponent, 152 AliasesEntitySelectComponent,
155 EntityAliasesDialogComponent, 153 EntityAliasesDialogComponent,
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { NgModule } from '@angular/core';
  18 +import { CommonModule } from '@angular/common';
  19 +import { SharedModule } from '@app/shared/shared.module';
  20 +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component';
  21 +
  22 +@NgModule({
  23 + entryComponents: [
  24 + AlarmDetailsDialogComponent
  25 + ],
  26 + declarations:
  27 + [
  28 + AlarmDetailsDialogComponent
  29 + ],
  30 + imports: [
  31 + CommonModule,
  32 + SharedModule
  33 + ],
  34 + exports: [
  35 + AlarmDetailsDialogComponent
  36 + ]
  37 +})
  38 +export class SharedHomeComponentsModule { }
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div fxLayout="column" class="mat-content mat-padding">
  19 + <label class="tb-title" translate>alarm.alarm-status-filter</label>
  20 + <mat-radio-group [(ngModel)]="subscription.alarmSearchStatus" fxLayout="column" fxLayoutGap="16px">
  21 + <mat-radio-button *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus" color="primary">
  22 + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
  23 + </mat-radio-button>
  24 + </mat-radio-group>
  25 +</div>
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + width: 100%;
  18 + height: 100%;
  19 + min-width: 300px;
  20 + overflow: hidden;
  21 + background: #fff;
  22 + border-radius: 4px;
  23 + box-shadow:
  24 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  25 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  26 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  27 +
  28 + .mat-content {
  29 + overflow: hidden;
  30 + background-color: #fff;
  31 + }
  32 +
  33 + .mat-padding {
  34 + padding: 16px;
  35 + }
  36 +}
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, InjectionToken } from '@angular/core';
  18 +import { IWidgetSubscription } from '@core/api/widget-api.models';
  19 +import { AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models';
  20 +
  21 +export const ALARM_STATUS_FILTER_PANEL_DATA = new InjectionToken<any>('AlarmStatusFilterPanelData');
  22 +
  23 +export interface AlarmStatusFilterPanelData {
  24 + subscription: IWidgetSubscription;
  25 +}
  26 +
  27 +@Component({
  28 + selector: 'tb-alarm-status-filter-panel',
  29 + templateUrl: './alarm-status-filter-panel.component.html',
  30 + styleUrls: ['./alarm-status-filter-panel.component.scss']
  31 +})
  32 +export class AlarmStatusFilterPanelComponent {
  33 +
  34 + subscription: IWidgetSubscription;
  35 +
  36 + alarmSearchStatuses = Object.keys(AlarmSearchStatus);
  37 + alarmSearchStatusTranslationMap = alarmSearchStatusTranslations;
  38 +
  39 + constructor(@Inject(ALARM_STATUS_FILTER_PANEL_DATA) public data: AlarmStatusFilterPanelData) {
  40 + this.subscription = this.data.subscription;
  41 + }
  42 +}
@@ -39,7 +39,7 @@ import { PageLink } from '@shared/models/page/page-link'; @@ -39,7 +39,7 @@ import { PageLink } from '@shared/models/page/page-link';
39 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; 39 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
40 import { DataSource } from '@angular/cdk/typings/collections'; 40 import { DataSource } from '@angular/cdk/typings/collections';
41 import { CollectionViewer, SelectionModel } from '@angular/cdk/collections'; 41 import { CollectionViewer, SelectionModel } from '@angular/cdk/collections';
42 -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; 42 +import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, of } from 'rxjs';
43 import { emptyPageData, PageData } from '@shared/models/page/page-data'; 43 import { emptyPageData, PageData } from '@shared/models/page/page-data';
44 import { entityTypeTranslations } from '@shared/models/entity-type.models'; 44 import { entityTypeTranslations } from '@shared/models/entity-type.models';
45 import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; 45 import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators';
@@ -77,6 +77,19 @@ import { @@ -77,6 +77,19 @@ import {
77 alarmStatusTranslations 77 alarmStatusTranslations
78 } from '@shared/models/alarm.models'; 78 } from '@shared/models/alarm.models';
79 import { DatePipe } from '@angular/common'; 79 import { DatePipe } from '@angular/common';
  80 +import {
  81 + ALARM_STATUS_FILTER_PANEL_DATA,
  82 + AlarmStatusFilterPanelComponent,
  83 + AlarmStatusFilterPanelData
  84 +} from '@home/components/widget/lib/alarm-status-filter-panel.component';
  85 +import {
  86 + AlarmDetailsDialogComponent,
  87 + AlarmDetailsDialogData
  88 +} from '@home/components/alarm/alarm-details-dialog.component';
  89 +import { MatDialog } from '@angular/material/dialog';
  90 +import { NULL_UUID } from '@shared/models/id/has-uuid';
  91 +import { DialogService } from '@core/services/dialog.service';
  92 +import { AlarmService } from '@core/http/alarm.service';
80 93
81 interface AlarmsTableWidgetSettings extends TableWidgetSettings { 94 interface AlarmsTableWidgetSettings extends TableWidgetSettings {
82 alarmsTitle: string; 95 alarmsTitle: string;
@@ -172,7 +185,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -172,7 +185,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
172 private utils: UtilsService, 185 private utils: UtilsService,
173 public translate: TranslateService, 186 public translate: TranslateService,
174 private domSanitizer: DomSanitizer, 187 private domSanitizer: DomSanitizer,
175 - private datePipe: DatePipe) { 188 + private datePipe: DatePipe,
  189 + private dialog: MatDialog,
  190 + private dialogService: DialogService,
  191 + private alarmService: AlarmService) {
176 super(store); 192 super(store);
177 193
178 const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); 194 const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder);
@@ -390,7 +406,36 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -390,7 +406,36 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
390 } 406 }
391 407
392 private editAlarmStatusFilter($event: Event) { 408 private editAlarmStatusFilter($event: Event) {
393 - // TODO: 409 + if ($event) {
  410 + $event.stopPropagation();
  411 + }
  412 + const target = $event.target || $event.srcElement || $event.currentTarget;
  413 + const config = new OverlayConfig();
  414 + config.backdropClass = 'cdk-overlay-transparent-backdrop';
  415 + config.hasBackdrop = true;
  416 + const connectedPosition: ConnectedPosition = {
  417 + originX: 'end',
  418 + originY: 'bottom',
  419 + overlayX: 'end',
  420 + overlayY: 'top'
  421 + };
  422 + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
  423 + .withPositions([connectedPosition]);
  424 +
  425 + const overlayRef = this.overlay.create(config);
  426 + overlayRef.backdropClick().subscribe(() => {
  427 + overlayRef.dispose();
  428 + });
  429 + const injectionTokens = new WeakMap<any, any>([
  430 + [ALARM_STATUS_FILTER_PANEL_DATA, {
  431 + subscription: this.subscription,
  432 + } as AlarmStatusFilterPanelData],
  433 + [OverlayRef, overlayRef]
  434 + ]);
  435 + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens);
  436 + overlayRef.attach(new ComponentPortal(AlarmStatusFilterPanelComponent,
  437 + this.viewContainerRef, injector));
  438 + this.ctx.detectChanges();
394 } 439 }
395 440
396 private enterFilterMode() { 441 private enterFilterMode() {
@@ -538,35 +583,138 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -538,35 +583,138 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
538 if ($event) { 583 if ($event) {
539 $event.stopPropagation(); 584 $event.stopPropagation();
540 } 585 }
541 - // TODO: 586 + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
  587 + this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
  588 + (AlarmDetailsDialogComponent,
  589 + {
  590 + disableClose: true,
  591 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  592 + data: {
  593 + alarmId: alarm.id.id,
  594 + allowAcknowledgment: this.allowAcknowledgment,
  595 + allowClear: this.allowClear,
  596 + displayDetails: true
  597 + }
  598 + }).afterClosed().subscribe(
  599 + (res) => {
  600 + if (res) {
  601 + this.subscription.update();
  602 + }
  603 + }
  604 + );
  605 + }
542 } 606 }
543 607
544 private ackAlarm($event: Event, alarm: AlarmInfo) { 608 private ackAlarm($event: Event, alarm: AlarmInfo) {
545 if ($event) { 609 if ($event) {
546 $event.stopPropagation(); 610 $event.stopPropagation();
547 } 611 }
548 - // TODO: 612 + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
  613 + this.dialogService.confirm(
  614 + this.translate.instant('alarm.aknowledge-alarm-title'),
  615 + this.translate.instant('alarm.aknowledge-alarm-text'),
  616 + this.translate.instant('action.no'),
  617 + this.translate.instant('action.yes')
  618 + ).subscribe((res) => {
  619 + if (res) {
  620 + if (res) {
  621 + this.alarmService.ackAlarm(alarm.id.id).subscribe(() => {
  622 + this.subscription.update();
  623 + });
  624 + }
  625 + }
  626 + });
  627 + }
549 } 628 }
550 629
551 public ackAlarms($event: Event) { 630 public ackAlarms($event: Event) {
552 if ($event) { 631 if ($event) {
553 $event.stopPropagation(); 632 $event.stopPropagation();
554 } 633 }
555 - // TODO: 634 + if (this.alarmsDatasource.selection.hasValue()) {
  635 + const alarms = this.alarmsDatasource.selection.selected.filter(
  636 + (alarm) => { return alarm.id.id !== NULL_UUID }
  637 + );
  638 + if (alarms.length) {
  639 + const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarms.length});
  640 + const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarms.length});
  641 + this.dialogService.confirm(
  642 + title,
  643 + content,
  644 + this.translate.instant('action.no'),
  645 + this.translate.instant('action.yes')
  646 + ).subscribe((res) => {
  647 + if (res) {
  648 + if (res) {
  649 + const tasks: Observable<void>[] = [];
  650 + for (const alarm of alarms) {
  651 + tasks.push(this.alarmService.ackAlarm(alarm.id.id));
  652 + }
  653 + forkJoin(tasks).subscribe(() => {
  654 + this.alarmsDatasource.clearSelection();
  655 + this.subscription.update();
  656 + });
  657 + }
  658 + }
  659 + });
  660 + }
  661 + }
556 } 662 }
557 663
558 private clearAlarm($event: Event, alarm: AlarmInfo) { 664 private clearAlarm($event: Event, alarm: AlarmInfo) {
559 if ($event) { 665 if ($event) {
560 $event.stopPropagation(); 666 $event.stopPropagation();
561 } 667 }
562 - // TODO: 668 + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) {
  669 + this.dialogService.confirm(
  670 + this.translate.instant('alarm.clear-alarm-title'),
  671 + this.translate.instant('alarm.clear-alarm-text'),
  672 + this.translate.instant('action.no'),
  673 + this.translate.instant('action.yes')
  674 + ).subscribe((res) => {
  675 + if (res) {
  676 + if (res) {
  677 + this.alarmService.clearAlarm(alarm.id.id).subscribe(() => {
  678 + this.subscription.update();
  679 + });
  680 + }
  681 + }
  682 + });
  683 + }
563 } 684 }
564 685
565 public clearAlarms($event: Event) { 686 public clearAlarms($event: Event) {
566 if ($event) { 687 if ($event) {
567 $event.stopPropagation(); 688 $event.stopPropagation();
568 } 689 }
569 - // TODO: 690 + if (this.alarmsDatasource.selection.hasValue()) {
  691 + const alarms = this.alarmsDatasource.selection.selected.filter(
  692 + (alarm) => { return alarm.id.id !== NULL_UUID }
  693 + );
  694 + if (alarms.length) {
  695 + const title = this.translate.instant('alarm.clear-alarms-title', {count: alarms.length});
  696 + const content = this.translate.instant('alarm.clear-alarms-text', {count: alarms.length});
  697 + this.dialogService.confirm(
  698 + title,
  699 + content,
  700 + this.translate.instant('action.no'),
  701 + this.translate.instant('action.yes')
  702 + ).subscribe((res) => {
  703 + if (res) {
  704 + if (res) {
  705 + const tasks: Observable<void>[] = [];
  706 + for (const alarm of alarms) {
  707 + tasks.push(this.alarmService.clearAlarm(alarm.id.id));
  708 + }
  709 + forkJoin(tasks).subscribe(() => {
  710 + this.alarmsDatasource.clearSelection();
  711 + this.subscription.update();
  712 + });
  713 + }
  714 + }
  715 + });
  716 + }
  717 + }
570 } 718 }
571 719
572 private defaultContent(key: EntityColumn, value: any): any { 720 private defaultContent(key: EntityColumn, value: any): any {
@@ -722,6 +870,13 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -722,6 +870,13 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
722 return this.selection.isSelected(alarm); 870 return this.selection.isSelected(alarm);
723 } 871 }
724 872
  873 + clearSelection() {
  874 + if (this.selection.hasValue()) {
  875 + this.selection.clear();
  876 + this.onSelectionModeChanged(false);
  877 + }
  878 + }
  879 +
725 masterToggle() { 880 masterToggle() {
726 this.alarmsSubject.pipe( 881 this.alarmsSubject.pipe(
727 tap((alarms) => { 882 tap((alarms) => {
@@ -234,6 +234,9 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { @@ -234,6 +234,9 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string {
234 '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ 234 '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+
235 'color: ' + mdDarkSecondary + ';\n'+ 235 'color: ' + mdDarkSecondary + ';\n'+
236 '}\n'+ 236 '}\n'+
  237 + '.mat-table .mat-cell button.mat-icon-button[disabled][disabled] mat-icon {\n'+
  238 + 'color: ' + mdDarkDisabled + ';\n'+
  239 + '}\n'+
237 '.mat-divider {\n'+ 240 '.mat-divider {\n'+
238 'border-top-color: ' + mdDarkDivider + ';\n'+ 241 'border-top-color: ' + mdDarkDivider + ';\n'+
239 '}\n'+ 242 '}\n'+
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { Inject, Injectable } from '@angular/core'; 17 +import { Inject, Injectable, Type } from '@angular/core';
18 import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; 18 import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
19 import { WidgetService } from '@core/http/widget.service'; 19 import { WidgetService } from '@core/http/widget.service';
20 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; 20 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
@@ -34,7 +34,6 @@ import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; @@ -34,7 +34,6 @@ import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
34 import { isFunction, isUndefined } from '@core/utils'; 34 import { isFunction, isUndefined } from '@core/utils';
35 import { TranslateService } from '@ngx-translate/core'; 35 import { TranslateService } from '@ngx-translate/core';
36 import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; 36 import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
37 -import { SharedModule } from '@shared/shared.module';  
38 import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; 37 import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
39 import { WINDOW } from '@core/services/window.service'; 38 import { WINDOW } from '@core/services/window.service';
40 39
@@ -43,6 +42,7 @@ import { TbFlot } from './lib/flot-widget'; @@ -43,6 +42,7 @@ import { TbFlot } from './lib/flot-widget';
43 import { NULL_UUID } from '@shared/models/id/has-uuid'; 42 import { NULL_UUID } from '@shared/models/id/has-uuid';
44 import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; 43 import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
45 import { TenantId } from '@app/shared/models/id/tenant-id'; 44 import { TenantId } from '@app/shared/models/id/tenant-id';
  45 +import { SharedModule } from '@shared/shared.module';
46 46
47 const tinycolor = tinycolor_; 47 const tinycolor = tinycolor_;
48 48
@@ -117,16 +117,23 @@ export class WidgetComponentService { @@ -117,16 +117,23 @@ export class WidgetComponentService {
117 const initSubject = new ReplaySubject(); 117 const initSubject = new ReplaySubject();
118 this.init$ = initSubject.asObservable(); 118 this.init$ = initSubject.asObservable();
119 const loadDefaultWidgetInfoTasks = [ 119 const loadDefaultWidgetInfoTasks = [
120 - this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type'),  
121 - this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type'), 120 + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule]),
  121 + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule]),
122 ]; 122 ];
123 forkJoin(loadDefaultWidgetInfoTasks).subscribe( 123 forkJoin(loadDefaultWidgetInfoTasks).subscribe(
124 () => { 124 () => {
125 initSubject.next(); 125 initSubject.next();
126 }, 126 },
127 - () => { 127 + (e) => {
  128 + let errorMessages = ['Failed to load default widget types!'];
  129 + if (e && e.length) {
  130 + errorMessages = errorMessages.concat(e);
  131 + }
128 console.error('Failed to load default widget types!'); 132 console.error('Failed to load default widget types!');
129 - initSubject.error('Failed to load default widget types!'); 133 + initSubject.error({
  134 + widgetInfo: this.errorWidgetType,
  135 + errorMessages
  136 + });
130 } 137 }
131 ); 138 );
132 return this.init$; 139 return this.init$;
@@ -194,7 +201,7 @@ export class WidgetComponentService { @@ -194,7 +201,7 @@ export class WidgetComponentService {
194 } 201 }
195 if (widgetControllerDescriptor) { 202 if (widgetControllerDescriptor) {
196 const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; 203 const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;
197 - this.loadWidgetResources(widgetInfo, widgetNamespace).subscribe( 204 + this.loadWidgetResources(widgetInfo, widgetNamespace, [WidgetComponentsModule]).subscribe(
198 () => { 205 () => {
199 if (widgetControllerDescriptor.settingsSchema) { 206 if (widgetControllerDescriptor.settingsSchema) {
200 widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; 207 widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;
@@ -219,7 +226,7 @@ export class WidgetComponentService { @@ -219,7 +226,7 @@ export class WidgetComponentService {
219 } 226 }
220 } 227 }
221 228
222 - private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string): Observable<any> { 229 + private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string, modules?: Type<any>[]): Observable<any> {
223 this.cssParser.cssPreviewNamespace = widgetNamespace; 230 this.cssParser.cssPreviewNamespace = widgetNamespace;
224 this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); 231 this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);
225 const resourceTasks: Observable<string>[] = []; 232 const resourceTasks: Observable<string>[] = [];
@@ -236,7 +243,7 @@ export class WidgetComponentService { @@ -236,7 +243,7 @@ export class WidgetComponentService {
236 this.dynamicComponentFactoryService.createDynamicComponentFactory( 243 this.dynamicComponentFactoryService.createDynamicComponentFactory(
237 class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, 244 class DynamicWidgetComponentInstance extends DynamicWidgetComponent {},
238 widgetInfo.templateHtml, 245 widgetInfo.templateHtml,
239 - [SharedModule, WidgetComponentsModule] 246 + modules
240 ).pipe( 247 ).pipe(
241 map((factory) => { 248 map((factory) => {
242 widgetInfo.componentFactory = factory; 249 widgetInfo.componentFactory = factory;
@@ -17,25 +17,28 @@ @@ -17,25 +17,28 @@
17 import { NgModule } from '@angular/core'; 17 import { NgModule } from '@angular/core';
18 import { CommonModule } from '@angular/common'; 18 import { CommonModule } from '@angular/common';
19 import { SharedModule } from '@app/shared/shared.module'; 19 import { SharedModule } from '@app/shared/shared.module';
20 -import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component';  
21 -import { LegendComponent } from '@home/components/widget/legend.component';  
22 import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; 20 import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component';
23 import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; 21 import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component';
24 import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component'; 22 import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component';
  23 +import { AlarmStatusFilterPanelComponent } from '@home/components/widget/lib/alarm-status-filter-panel.component';
  24 +import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
25 25
26 @NgModule({ 26 @NgModule({
27 entryComponents: [ 27 entryComponents: [
28 - DisplayColumnsPanelComponent 28 + DisplayColumnsPanelComponent,
  29 + AlarmStatusFilterPanelComponent
29 ], 30 ],
30 declarations: 31 declarations:
31 [ 32 [
32 DisplayColumnsPanelComponent, 33 DisplayColumnsPanelComponent,
  34 + AlarmStatusFilterPanelComponent,
33 EntitiesTableWidgetComponent, 35 EntitiesTableWidgetComponent,
34 AlarmsTableWidgetComponent 36 AlarmsTableWidgetComponent
35 ], 37 ],
36 imports: [ 38 imports: [
37 CommonModule, 39 CommonModule,
38 - SharedModule 40 + SharedModule,
  41 + SharedHomeComponentsModule
39 ], 42 ],
40 exports: [ 43 exports: [
41 EntitiesTableWidgetComponent, 44 EntitiesTableWidgetComponent,
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 import { Direction, SortOrder } from '@shared/models/page/sort-order'; 17 import { Direction, SortOrder } from '@shared/models/page/sort-order';
18 import { emptyPageData, PageData } from '@shared/models/page/page-data'; 18 import { emptyPageData, PageData } from '@shared/models/page/page-data';
19 -import { getDescendantProp } from '@core/utils'; 19 +import { getDescendantProp, isObject } from '@core/utils';
20 20
21 export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean; 21 export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean;
22 22
@@ -28,10 +28,16 @@ const defaultPageLinkSearchFunction: PageLinkSearchFunction<any> = @@ -28,10 +28,16 @@ const defaultPageLinkSearchFunction: PageLinkSearchFunction<any> =
28 const expected = ('' + textSearch).toLowerCase(); 28 const expected = ('' + textSearch).toLowerCase();
29 for (const key of Object.keys(entity)) { 29 for (const key of Object.keys(entity)) {
30 const val = entity[key]; 30 const val = entity[key];
31 - if (val !== null && val !== Object(val)) {  
32 - const actual = ('' + val).toLowerCase();  
33 - if (actual.indexOf(expected) !== -1) {  
34 - return true; 31 + if (val !== null) {
  32 + if (val !== Object(val)) {
  33 + const actual = ('' + val).toLowerCase();
  34 + if (actual.indexOf(expected) !== -1) {
  35 + return true;
  36 + }
  37 + } else if (isObject(val)) {
  38 + if (defaultPageLinkSearchFunction(val, textSearch)) {
  39 + return true;
  40 + }
35 } 41 }
36 } 42 }
37 } 43 }
@@ -498,6 +498,11 @@ mat-label { @@ -498,6 +498,11 @@ mat-label {
498 mat-icon { 498 mat-icon {
499 color: rgba(0, 0, 0, .54); 499 color: rgba(0, 0, 0, .54);
500 } 500 }
  501 + &[disabled][disabled] {
  502 + mat-icon {
  503 + color: rgba(0, 0, 0, .26);
  504 + }
  505 + }
501 } 506 }
502 } 507 }
503 508