Showing
12 changed files
with
351 additions
and
31 deletions
@@ -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 { } |
ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html
0 → 100644
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> |
ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.scss
0 → 100644
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 | } |