Commit 8f4944fe170ed29b1415f071fdbd52351bcdd95b
1 parent
68954f8d
Material table improvements. Alarm widget implementation.
Showing
34 changed files
with
1444 additions
and
303 deletions
@@ -13,10 +13,10 @@ | @@ -13,10 +13,10 @@ | ||
13 | "sizeX": 10.5, | 13 | "sizeX": 10.5, |
14 | "sizeY": 6.5, | 14 | "sizeY": 6.5, |
15 | "resources": [], | 15 | "resources": [], |
16 | - "templateHtml": "<tb-alarms-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-alarms-table-widget>", | 16 | + "templateHtml": "<tb-alarms-table-widget \n [ctx]=\"ctx\">\n</tb-alarms-table-widget>", |
17 | "templateCss": "", | 17 | "templateCss": "", |
18 | - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", | ||
19 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", | 18 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
19 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", | ||
20 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | 20 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", |
21 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}" | 21 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}" |
22 | } | 22 | } |
@@ -19,11 +19,15 @@ import { Component, OnInit } from '@angular/core'; | @@ -19,11 +19,15 @@ import { Component, OnInit } from '@angular/core'; | ||
19 | import { environment as env } from '@env/environment'; | 19 | import { environment as env } from '@env/environment'; |
20 | 20 | ||
21 | import { TranslateService } from '@ngx-translate/core'; | 21 | import { TranslateService } from '@ngx-translate/core'; |
22 | -import { Store } from '@ngrx/store'; | 22 | +import { select, Store } from '@ngrx/store'; |
23 | import { AppState } from './core/core.state'; | 23 | import { AppState } from './core/core.state'; |
24 | import { LocalStorageService } from './core/local-storage/local-storage.service'; | 24 | import { LocalStorageService } from './core/local-storage/local-storage.service'; |
25 | import { DomSanitizer } from '@angular/platform-browser'; | 25 | import { DomSanitizer } from '@angular/platform-browser'; |
26 | import { MatIconRegistry } from '@angular/material'; | 26 | import { MatIconRegistry } from '@angular/material'; |
27 | +import { combineLatest } from 'rxjs'; | ||
28 | +import { selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; | ||
29 | +import { distinctUntilChanged, filter, map, skip } from 'rxjs/operators'; | ||
30 | +import { AuthService } from '@core/auth/auth.service'; | ||
27 | 31 | ||
28 | @Component({ | 32 | @Component({ |
29 | selector: 'tb-root', | 33 | selector: 'tb-root', |
@@ -36,7 +40,8 @@ export class AppComponent implements OnInit { | @@ -36,7 +40,8 @@ export class AppComponent implements OnInit { | ||
36 | private storageService: LocalStorageService, | 40 | private storageService: LocalStorageService, |
37 | private translate: TranslateService, | 41 | private translate: TranslateService, |
38 | private matIconRegistry: MatIconRegistry, | 42 | private matIconRegistry: MatIconRegistry, |
39 | - private domSanitizer: DomSanitizer) { | 43 | + private domSanitizer: DomSanitizer, |
44 | + private authService: AuthService) { | ||
40 | 45 | ||
41 | console.log(`ThingsBoard Version: ${env.tbVersion}`); | 46 | console.log(`ThingsBoard Version: ${env.tbVersion}`); |
42 | 47 | ||
@@ -56,6 +61,7 @@ export class AppComponent implements OnInit { | @@ -56,6 +61,7 @@ export class AppComponent implements OnInit { | ||
56 | this.storageService.testLocalStorage(); | 61 | this.storageService.testLocalStorage(); |
57 | 62 | ||
58 | this.setupTranslate(); | 63 | this.setupTranslate(); |
64 | + this.setupAuth(); | ||
59 | } | 65 | } |
60 | 66 | ||
61 | setupTranslate() { | 67 | setupTranslate() { |
@@ -69,6 +75,21 @@ export class AppComponent implements OnInit { | @@ -69,6 +75,21 @@ export class AppComponent implements OnInit { | ||
69 | this.translate.setDefaultLang(env.defaultLang); | 75 | this.translate.setDefaultLang(env.defaultLang); |
70 | } | 76 | } |
71 | 77 | ||
78 | + setupAuth() { | ||
79 | + combineLatest([ | ||
80 | + this.store.pipe(select(selectIsAuthenticated)), | ||
81 | + this.store.pipe(select(selectIsUserLoaded))] | ||
82 | + ).pipe( | ||
83 | + map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), | ||
84 | + distinctUntilChanged(), | ||
85 | + filter((data) => data.isUserLoaded ), | ||
86 | + skip(1), | ||
87 | + ).subscribe((data) => { | ||
88 | + this.authService.gotoDefaultPlace(data.isAuthenticated); | ||
89 | + }); | ||
90 | + this.authService.reloadUser(); | ||
91 | + } | ||
92 | + | ||
72 | ngOnInit() { | 93 | ngOnInit() { |
73 | } | 94 | } |
74 | 95 |
@@ -30,7 +30,7 @@ import { AlarmService } from '../http/alarm.service'; | @@ -30,7 +30,7 @@ import { AlarmService } from '../http/alarm.service'; | ||
30 | import { UtilsService } from '@core/services/utils.service'; | 30 | import { UtilsService } from '@core/services/utils.service'; |
31 | import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; | 31 | import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; |
32 | import { EntityType } from '@shared/models/entity-type.models'; | 32 | import { EntityType } from '@shared/models/entity-type.models'; |
33 | -import { AlarmSearchStatus } from '@shared/models/alarm.models'; | 33 | +import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; |
34 | import { HttpErrorResponse } from '@angular/common/http'; | 34 | import { HttpErrorResponse } from '@angular/common/http'; |
35 | import { DatasourceService } from '@core/api/datasource.service'; | 35 | import { DatasourceService } from '@core/api/datasource.service'; |
36 | import { RafService } from '@core/services/raf.service'; | 36 | import { RafService } from '@core/services/raf.service'; |
@@ -226,6 +226,7 @@ export interface IWidgetSubscription { | @@ -226,6 +226,7 @@ export interface IWidgetSubscription { | ||
226 | timeWindowConfig?: Timewindow; | 226 | timeWindowConfig?: Timewindow; |
227 | timeWindow?: WidgetTimewindow; | 227 | timeWindow?: WidgetTimewindow; |
228 | 228 | ||
229 | + alarms?: Array<AlarmInfo>; | ||
229 | alarmSource?: Datasource; | 230 | alarmSource?: Datasource; |
230 | alarmSearchStatus?: AlarmSearchStatus; | 231 | alarmSearchStatus?: AlarmSearchStatus; |
231 | alarmsPollingInterval?: number; | 232 | alarmsPollingInterval?: number; |
@@ -62,18 +62,6 @@ export class AuthService { | @@ -62,18 +62,6 @@ export class AuthService { | ||
62 | private adminService: AdminService, | 62 | private adminService: AdminService, |
63 | private translate: TranslateService | 63 | private translate: TranslateService |
64 | ) { | 64 | ) { |
65 | - combineLatest( | ||
66 | - this.store.pipe(select(selectIsAuthenticated)), | ||
67 | - this.store.pipe(select(selectIsUserLoaded)) | ||
68 | - ).pipe( | ||
69 | - map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), | ||
70 | - distinctUntilChanged(), | ||
71 | - filter((data) => data.isUserLoaded ), | ||
72 | - skip(1), | ||
73 | - ).subscribe((data) => { | ||
74 | - this.gotoDefaultPlace(data.isAuthenticated); | ||
75 | - }); | ||
76 | - this.reloadUser(); | ||
77 | } | 65 | } |
78 | 66 | ||
79 | redirectUrl: string; | 67 | redirectUrl: string; |
@@ -398,3 +398,7 @@ export function snakeCase(name: string, separator: string): string { | @@ -398,3 +398,7 @@ export function snakeCase(name: string, separator: string): string { | ||
398 | return (pos ? separator : '') + letter.toLowerCase(); | 398 | return (pos ? separator : '') + letter.toLowerCase(); |
399 | }); | 399 | }); |
400 | } | 400 | } |
401 | + | ||
402 | +export function getDescendantProp(obj: any, path: string): any { | ||
403 | + return path.split('.').reduce((acc, part) => acc && acc[part], obj) | ||
404 | +} |
@@ -84,19 +84,19 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> | @@ -84,19 +84,19 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> | ||
84 | this.columns.push( | 84 | this.columns.push( |
85 | new DateEntityTableColumn<AlarmInfo>('createdTime', 'alarm.created-time', this.datePipe, '150px')); | 85 | new DateEntityTableColumn<AlarmInfo>('createdTime', 'alarm.created-time', this.datePipe, '150px')); |
86 | this.columns.push( | 86 | this.columns.push( |
87 | - new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '100%', | 87 | + new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '25%', |
88 | (entity) => entity.originatorName, entity => ({}), false)); | 88 | (entity) => entity.originatorName, entity => ({}), false)); |
89 | this.columns.push( | 89 | this.columns.push( |
90 | - new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '100%')); | 90 | + new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '25%')); |
91 | this.columns.push( | 91 | this.columns.push( |
92 | - new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '100%', | 92 | + new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '25%', |
93 | (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), | 93 | (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), |
94 | entity => ({ | 94 | entity => ({ |
95 | fontWeight: 'bold', | 95 | fontWeight: 'bold', |
96 | color: alarmSeverityColors.get(entity.severity) | 96 | color: alarmSeverityColors.get(entity.severity) |
97 | }))); | 97 | }))); |
98 | this.columns.push( | 98 | this.columns.push( |
99 | - new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '100%', | 99 | + new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '25%', |
100 | (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); | 100 | (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); |
101 | 101 | ||
102 | this.cellActionDescriptors.push( | 102 | this.cellActionDescriptors.push( |
@@ -83,22 +83,22 @@ export class AuditLogTableConfig extends EntityTableConfig<AuditLog, TimePageLin | @@ -83,22 +83,22 @@ export class AuditLogTableConfig extends EntityTableConfig<AuditLog, TimePageLin | ||
83 | 83 | ||
84 | if (this.auditLogMode !== AuditLogMode.ENTITY) { | 84 | if (this.auditLogMode !== AuditLogMode.ENTITY) { |
85 | this.columns.push( | 85 | this.columns.push( |
86 | - new EntityTableColumn<AuditLog>('entityType', 'audit-log.entity-type', '100%', | 86 | + new EntityTableColumn<AuditLog>('entityType', 'audit-log.entity-type', '20%', |
87 | (entity) => translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)), | 87 | (entity) => translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)), |
88 | - new EntityTableColumn<AuditLog>('entityName', 'audit-log.entity-name'), | 88 | + new EntityTableColumn<AuditLog>('entityName', 'audit-log.entity-name', '20%'), |
89 | ); | 89 | ); |
90 | } | 90 | } |
91 | 91 | ||
92 | if (this.auditLogMode !== AuditLogMode.USER) { | 92 | if (this.auditLogMode !== AuditLogMode.USER) { |
93 | this.columns.push( | 93 | this.columns.push( |
94 | - new EntityTableColumn<AuditLog>('userName', 'audit-log.user') | 94 | + new EntityTableColumn<AuditLog>('userName', 'audit-log.user', '33%') |
95 | ); | 95 | ); |
96 | } | 96 | } |
97 | 97 | ||
98 | this.columns.push( | 98 | this.columns.push( |
99 | - new EntityTableColumn<AuditLog>('actionType', 'audit-log.type', '100%', | 99 | + new EntityTableColumn<AuditLog>('actionType', 'audit-log.type', '33%', |
100 | (entity) => translate.instant(actionTypeTranslations.get(entity.actionType))), | 100 | (entity) => translate.instant(actionTypeTranslations.get(entity.actionType))), |
101 | - new EntityTableColumn<AuditLog>('actionStatus', 'audit-log.status', '100%', | 101 | + new EntityTableColumn<AuditLog>('actionStatus', 'audit-log.status', '33%', |
102 | (entity) => translate.instant(actionStatusTranslations.get(entity.actionStatus))) | 102 | (entity) => translate.instant(actionStatusTranslations.get(entity.actionStatus))) |
103 | ); | 103 | ); |
104 | 104 |
@@ -153,8 +153,10 @@ | @@ -153,8 +153,10 @@ | ||
153 | </mat-cell> | 153 | </mat-cell> |
154 | </ng-container> | 154 | </ng-container> |
155 | <ng-container [matColumnDef]="column.key" *ngFor="let column of entityColumns; trackBy: trackByColumnKey;"> | 155 | <ng-container [matColumnDef]="column.key" *ngFor="let column of entityColumns; trackBy: trackByColumnKey;"> |
156 | - <mat-header-cell *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell> | ||
157 | - <mat-cell *matCellDef="let entity; let row = index" | 156 | + <mat-header-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}" |
157 | + *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell> | ||
158 | + <mat-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}" | ||
159 | + *matCellDef="let entity; let row = index" | ||
158 | [matTooltip]="cellTooltip(entity, column, row)" | 160 | [matTooltip]="cellTooltip(entity, column, row)" |
159 | matTooltipPosition="above" | 161 | matTooltipPosition="above" |
160 | [innerHTML]="cellContent(entity, column, row)" | 162 | [innerHTML]="cellContent(entity, column, row)" |
@@ -176,10 +178,10 @@ | @@ -176,10 +178,10 @@ | ||
176 | </mat-cell> | 178 | </mat-cell> |
177 | </ng-container> | 179 | </ng-container> |
178 | <ng-container matColumnDef="actions" stickyEnd> | 180 | <ng-container matColumnDef="actions" stickyEnd> |
179 | - <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 40) + 'px', maxWidth: (cellActionDescriptors.length * 40) + 'px' }"> | 181 | + <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ width: (cellActionDescriptors.length * 40) + 'px' }"> |
180 | {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} | 182 | {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} |
181 | </mat-header-cell> | 183 | </mat-header-cell> |
182 | - <mat-cell *matCellDef="let entity" [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 40) + 'px', maxWidth: (cellActionDescriptors.length * 40) + 'px' }"> | 184 | + <mat-cell *matCellDef="let entity" [ngStyle.gt-md]="{ width: (cellActionDescriptors.length * 40) + 'px' }"> |
183 | <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | 185 | <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> |
184 | <button mat-button mat-icon-button [disabled]="isLoading$ | async" | 186 | <button mat-button mat-icon-button [disabled]="isLoading$ | async" |
185 | [fxShow]="actionDescriptor.isEnabled(entity)" *ngFor="let actionDescriptor of cellActionDescriptors" | 187 | [fxShow]="actionDescriptor.isEnabled(entity)" *ngFor="let actionDescriptor of cellActionDescriptors" |
@@ -190,7 +192,7 @@ | @@ -190,7 +192,7 @@ | ||
190 | {{actionDescriptor.icon}}</mat-icon> | 192 | {{actionDescriptor.icon}}</mat-icon> |
191 | </button> | 193 | </button> |
192 | </div> | 194 | </div> |
193 | - <div fxHide fxShow.lt-lg> | 195 | + <div fxHide fxShow.lt-lg *ngIf="cellActionDescriptors.length"> |
194 | <button mat-button mat-icon-button | 196 | <button mat-button mat-icon-button |
195 | (click)="$event.stopPropagation()" | 197 | (click)="$event.stopPropagation()" |
196 | [matMenuTriggerFor]="cellActionsMenu"> | 198 | [matMenuTriggerFor]="cellActionsMenu"> |
@@ -398,9 +398,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -398,9 +398,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
398 | let res = this.headerCellStyleCache[index]; | 398 | let res = this.headerCellStyleCache[index]; |
399 | if (!res) { | 399 | if (!res) { |
400 | if (column instanceof EntityTableColumn) { | 400 | if (column instanceof EntityTableColumn) { |
401 | - res = {...column.headerCellStyleFunction(column.key), ...{maxWidth: column.maxWidth}}; | 401 | + res = {...column.headerCellStyleFunction(column.key), ...{minWidth: column.width, maxWidth: column.width, width: column.width}}; |
402 | } else { | 402 | } else { |
403 | - res = {maxWidth: column.maxWidth}; | 403 | + res = {width: column.width}; |
404 | } | 404 | } |
405 | this.headerCellStyleCache[index] = res; | 405 | this.headerCellStyleCache[index] = res; |
406 | } | 406 | } |
@@ -445,9 +445,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -445,9 +445,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
445 | let res = this.cellStyleCache[index]; | 445 | let res = this.cellStyleCache[index]; |
446 | if (!res) { | 446 | if (!res) { |
447 | if (column instanceof EntityTableColumn) { | 447 | if (column instanceof EntityTableColumn) { |
448 | - res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; | 448 | + res = {...column.cellStyleFunction(entity, column.key), ...{minWidth: column.width, maxWidth: column.width, width: column.width}}; |
449 | } else { | 449 | } else { |
450 | - res = {maxWidth: column.maxWidth}; | 450 | + res = {width: column.width}; |
451 | } | 451 | } |
452 | this.cellStyleCache[index] = res; | 452 | this.cellStyleCache[index] = res; |
453 | } | 453 | } |
@@ -109,8 +109,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | @@ -109,8 +109,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | ||
109 | updateColumns(updateTableColumns: boolean = false): void { | 109 | updateColumns(updateTableColumns: boolean = false): void { |
110 | this.columns = []; | 110 | this.columns = []; |
111 | this.columns.push( | 111 | this.columns.push( |
112 | - new DateEntityTableColumn<Event>('createdTime', 'event.event-time', this.datePipe, '150px'), | ||
113 | - new EntityTableColumn<Event>('server', 'event.server', '150px', | 112 | + new DateEntityTableColumn<Event>('createdTime', 'event.event-time', this.datePipe, '120px'), |
113 | + new EntityTableColumn<Event>('server', 'event.server', '100px', | ||
114 | (entity) => entity.body.server, entity => ({}), false)); | 114 | (entity) => entity.body.server, entity => ({}), false)); |
115 | switch (this.eventType) { | 115 | switch (this.eventType) { |
116 | case EventType.ERROR: | 116 | case EventType.ERROR: |
@@ -146,20 +146,21 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | @@ -146,20 +146,21 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | ||
146 | break; | 146 | break; |
147 | case EventType.STATS: | 147 | case EventType.STATS: |
148 | this.columns.push( | 148 | this.columns.push( |
149 | - new EntityTableColumn<Event>('messagesProcessed', 'event.messages-processed', '100%', | 149 | + new EntityTableColumn<Event>('messagesProcessed', 'event.messages-processed', '50%', |
150 | (entity) => entity.body.messagesProcessed + '', | 150 | (entity) => entity.body.messagesProcessed + '', |
151 | - entity => ({justifyContent: 'flex-end'}), | 151 | + () => ({}), |
152 | false, | 152 | false, |
153 | - key => ({justifyContent: 'flex-end'})), | ||
154 | - new EntityTableColumn<Event>('errorsOccurred', 'event.errors-occurred', '100%', | 153 | + () => ({}), () => undefined, true), |
154 | + new EntityTableColumn<Event>('errorsOccurred', 'event.errors-occurred', '50%', | ||
155 | (entity) => entity.body.errorsOccurred + '', | 155 | (entity) => entity.body.errorsOccurred + '', |
156 | - entity => ({justifyContent: 'flex-end'}), | 156 | + () => ({}), |
157 | false, | 157 | false, |
158 | - key => ({justifyContent: 'flex-end'})) | 158 | + () => ({}), () => undefined, true) |
159 | ); | 159 | ); |
160 | break; | 160 | break; |
161 | case DebugEventType.DEBUG_RULE_NODE: | 161 | case DebugEventType.DEBUG_RULE_NODE: |
162 | case DebugEventType.DEBUG_RULE_CHAIN: | 162 | case DebugEventType.DEBUG_RULE_CHAIN: |
163 | + this.columns[0].width = '100px'; | ||
163 | this.columns.push( | 164 | this.columns.push( |
164 | new EntityTableColumn<Event>('type', 'event.type', '40px', | 165 | new EntityTableColumn<Event>('type', 'event.type', '40px', |
165 | (entity) => entity.body.type, entity => ({ | 166 | (entity) => entity.body.type, entity => ({ |
@@ -173,24 +174,18 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | @@ -173,24 +174,18 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { | ||
173 | }), false, key => ({ | 174 | }), false, key => ({ |
174 | padding: '0 12px 0 0' | 175 | padding: '0 12px 0 0' |
175 | })), | 176 | })), |
176 | - new EntityTableColumn<Event>('msgId', 'event.message-id', '100%', | 177 | + new EntityTableColumn<Event>('msgId', 'event.message-id', '100px', |
177 | (entity) => entity.body.msgId, entity => ({ | 178 | (entity) => entity.body.msgId, entity => ({ |
178 | whiteSpace: 'nowrap', | 179 | whiteSpace: 'nowrap', |
179 | - padding: '0 12px 0 0', | ||
180 | - textOverflow: 'ellipsis', | ||
181 | - display: 'inline-block', | ||
182 | - lineHeight: '48px', | 180 | + padding: '0 12px 0 0' |
183 | }), false, key => ({ | 181 | }), false, key => ({ |
184 | padding: '0 12px 0 0' | 182 | padding: '0 12px 0 0' |
185 | }), | 183 | }), |
186 | entity => entity.body.msgId), | 184 | entity => entity.body.msgId), |
187 | - new EntityTableColumn<Event>('msgType', 'event.message-type', '100%', | 185 | + new EntityTableColumn<Event>('msgType', 'event.message-type', '100px', |
188 | (entity) => entity.body.msgType, entity => ({ | 186 | (entity) => entity.body.msgType, entity => ({ |
189 | whiteSpace: 'nowrap', | 187 | whiteSpace: 'nowrap', |
190 | - padding: '0 12px 0 0', | ||
191 | - textOverflow: 'ellipsis', | ||
192 | - display: 'inline-block', | ||
193 | - lineHeight: '48px', | 188 | + padding: '0 12px 0 0' |
194 | }), false, key => ({ | 189 | }), false, key => ({ |
195 | padding: '0 12px 0 0' | 190 | padding: '0 12px 0 0' |
196 | }), | 191 | }), |
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 class="tb-table-widget tb-absolute-fill"> | ||
19 | + <div fxFlex fxLayout="column" class="tb-absolute-fill"> | ||
20 | + <mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode && alarmsDatasource.selection.isEmpty()"> | ||
21 | + <div class="mat-toolbar-tools"> | ||
22 | + <button mat-button mat-icon-button | ||
23 | + matTooltip="{{ 'action.search' | translate }}" | ||
24 | + matTooltipPosition="above"> | ||
25 | + <mat-icon>search</mat-icon> | ||
26 | + </button> | ||
27 | + <mat-form-field fxFlex> | ||
28 | + <mat-label> </mat-label> | ||
29 | + <input #searchInput matInput | ||
30 | + [(ngModel)]="pageLink.textSearch" | ||
31 | + placeholder="{{ 'alarm.search' | translate }}"/> | ||
32 | + </mat-form-field> | ||
33 | + <button mat-button mat-icon-button (click)="exitFilterMode()" | ||
34 | + matTooltip="{{ 'action.close' | translate }}" | ||
35 | + matTooltipPosition="above"> | ||
36 | + <mat-icon>close</mat-icon> | ||
37 | + </button> | ||
38 | + </div> | ||
39 | + </mat-toolbar> | ||
40 | + <mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="!alarmsDatasource.selection.isEmpty()"> | ||
41 | + <div class="mat-toolbar-tools"> | ||
42 | + <span> | ||
43 | + {{ translate.get('alarm.selected-alarms', | ||
44 | + {count: alarmsDatasource.selection.selected.length}) | async }} | ||
45 | + </span> | ||
46 | + <span fxFlex></span> | ||
47 | + <button *ngIf="allowAcknowledgment" | ||
48 | + mat-button mat-icon-button [disabled]="isLoading$ | async" | ||
49 | + matTooltip="{{ 'alarm.acknowledge' | translate }}" | ||
50 | + matTooltipPosition="above" | ||
51 | + (click)="ackAlarms($event)"> | ||
52 | + <mat-icon>done</mat-icon> | ||
53 | + </button> | ||
54 | + <button mat-button mat-icon-button | ||
55 | + [disabled]="isLoading$ | async" | ||
56 | + matTooltip="{{ 'alarm.clear' | translate }}" | ||
57 | + matTooltipPosition="above" | ||
58 | + (click)="clearAlarms($event)"> | ||
59 | + <mat-icon>clear</mat-icon> | ||
60 | + </button> | ||
61 | + </div> | ||
62 | + </mat-toolbar> | ||
63 | + <div fxFlex class="table-container"> | ||
64 | + <mat-table [dataSource]="alarmsDatasource" | ||
65 | + matSort [matSortActive]="sortOrderProperty" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear> | ||
66 | + <ng-container matColumnDef="select" sticky> | ||
67 | + <mat-header-cell *matHeaderCellDef style="width: 30px;"> | ||
68 | + <mat-checkbox (change)="$event ? alarmsDatasource.masterToggle() : null" | ||
69 | + [checked]="alarmsDatasource.selection.hasValue() && (alarmsDatasource.isAllSelected() | async)" | ||
70 | + [indeterminate]="alarmsDatasource.selection.hasValue() && !(alarmsDatasource.isAllSelected() | async)"> | ||
71 | + </mat-checkbox> | ||
72 | + </mat-header-cell> | ||
73 | + <mat-cell *matCellDef="let alarm" style="width: 30px;"> | ||
74 | + <mat-checkbox (click)="$event.stopPropagation();" | ||
75 | + (change)="$event ? alarmsDatasource.toggleSelection(alarm) : null" | ||
76 | + [checked]="alarmsDatasource.isSelected(alarm)"> | ||
77 | + </mat-checkbox> | ||
78 | + </mat-cell> | ||
79 | + </ng-container> | ||
80 | + <ng-container [matColumnDef]="column.def" *ngFor="let column of columns; trackBy: trackByColumnDef;"> | ||
81 | + <mat-header-cell [ngStyle]="headerStyle(column)" *matHeaderCellDef mat-sort-header> {{ column.title }} </mat-header-cell> | ||
82 | + <mat-cell *matCellDef="let alarm;" | ||
83 | + [innerHTML]="cellContent(alarm, column)" | ||
84 | + [ngStyle]="cellStyle(alarm, column)"> | ||
85 | + </mat-cell> | ||
86 | + </ng-container> | ||
87 | + <ng-container matColumnDef="actions" stickyEnd> | ||
88 | + <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ width: (actionCellDescriptors.length * 36) + 'px' }"> | ||
89 | + </mat-header-cell> | ||
90 | + <mat-cell *matCellDef="let alarm" [ngStyle.gt-md]="{ width: (actionCellDescriptors.length * 36) + 'px' }"> | ||
91 | + <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | ||
92 | + <button mat-button mat-icon-button [disabled]="(isLoading$ | async) || !actionEnabled(alarm, actionDescriptor)" | ||
93 | + *ngFor="let actionDescriptor of actionCellDescriptors" | ||
94 | + matTooltip="{{ actionDescriptor.displayName }}" | ||
95 | + matTooltipPosition="above" | ||
96 | + (click)="onActionButtonClick($event, alarm, actionDescriptor)"> | ||
97 | + <mat-icon>{{actionDescriptor.icon}}</mat-icon> | ||
98 | + </button> | ||
99 | + </div> | ||
100 | + <div fxHide fxShow.lt-lg> | ||
101 | + <button mat-button mat-icon-button | ||
102 | + (click)="$event.stopPropagation(); ctx.detectChanges();" | ||
103 | + [matMenuTriggerFor]="cellActionsMenu"> | ||
104 | + <mat-icon class="material-icons">more_vert</mat-icon> | ||
105 | + </button> | ||
106 | + <mat-menu #cellActionsMenu="matMenu" xPosition="before"> | ||
107 | + <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors" | ||
108 | + [disabled]="(isLoading$ | async) || !actionEnabled(alarm, actionDescriptor)" | ||
109 | + (click)="onActionButtonClick($event, alarm, actionDescriptor)"> | ||
110 | + <mat-icon>{{actionDescriptor.icon}}</mat-icon> | ||
111 | + <span>{{ actionDescriptor.displayName }}</span> | ||
112 | + </button> | ||
113 | + </mat-menu> | ||
114 | + </div> | ||
115 | + </mat-cell> | ||
116 | + </ng-container> | ||
117 | + <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> | ||
118 | + <mat-row [ngClass]="{'mat-row-select': enableSelection, | ||
119 | + 'mat-selected': alarmsDatasource.isSelected(alarm), | ||
120 | + 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}" | ||
121 | + *matRowDef="let alarm; columns: displayedColumns;" | ||
122 | + (click)="onRowClick($event, alarm)"></mat-row> | ||
123 | + </mat-table> | ||
124 | + <span [fxShow]="alarmsDatasource.isEmpty() | async" | ||
125 | + fxLayoutAlign="center center" | ||
126 | + class="no-data-found" translate>alarm.no-alarms-prompt</span> | ||
127 | + </div> | ||
128 | + <mat-divider *ngIf="displayPagination"></mat-divider> | ||
129 | + <mat-paginator *ngIf="displayPagination" | ||
130 | + [length]="alarmsDatasource.total() | async" | ||
131 | + [pageIndex]="pageLink.page" | ||
132 | + [pageSize]="pageLink.pageSize" | ||
133 | + [pageSizeOptions]="pageSizeOptions"></mat-paginator> | ||
134 | + </div> | ||
135 | +</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 | +} |
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 { | ||
18 | + AfterViewInit, | ||
19 | + Component, | ||
20 | + ElementRef, | ||
21 | + EventEmitter, | ||
22 | + Input, | ||
23 | + NgZone, | ||
24 | + OnInit, | ||
25 | + ViewChild, | ||
26 | + ViewContainerRef | ||
27 | +} from '@angular/core'; | ||
28 | +import { PageComponent } from '@shared/components/page.component'; | ||
29 | +import { Store } from '@ngrx/store'; | ||
30 | +import { AppState } from '@core/core.state'; | ||
31 | +import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; | ||
32 | +import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; | ||
33 | +import { IWidgetSubscription } from '@core/api/widget-api.models'; | ||
34 | +import { UtilsService } from '@core/services/utils.service'; | ||
35 | +import { TranslateService } from '@ngx-translate/core'; | ||
36 | +import { deepClone, isDefined, isNumber } from '@core/utils'; | ||
37 | +import cssjs from '@core/css/css'; | ||
38 | +import { PageLink } from '@shared/models/page/page-link'; | ||
39 | +import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; | ||
40 | +import { DataSource } from '@angular/cdk/typings/collections'; | ||
41 | +import { CollectionViewer, SelectionModel } from '@angular/cdk/collections'; | ||
42 | +import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; | ||
43 | +import { emptyPageData, PageData } from '@shared/models/page/page-data'; | ||
44 | +import { entityTypeTranslations } from '@shared/models/entity-type.models'; | ||
45 | +import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; | ||
46 | +import { MatPaginator } from '@angular/material/paginator'; | ||
47 | +import { MatSort } from '@angular/material/sort'; | ||
48 | +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | ||
49 | +import { | ||
50 | + CellContentInfo, | ||
51 | + CellStyleInfo, | ||
52 | + constructTableCssString, | ||
53 | + DisplayColumn, | ||
54 | + EntityColumn, | ||
55 | + fromAlarmColumnDef, | ||
56 | + getAlarmValue, | ||
57 | + getCellContentInfo, | ||
58 | + getCellStyleInfo, | ||
59 | + getColumnWidth, | ||
60 | + TableWidgetDataKeySettings, | ||
61 | + TableWidgetSettings, | ||
62 | + toAlarmColumnDef | ||
63 | +} from '@home/components/widget/lib/table-widget.models'; | ||
64 | +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | ||
65 | +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | ||
66 | +import { | ||
67 | + DISPLAY_COLUMNS_PANEL_DATA, | ||
68 | + DisplayColumnsPanelComponent, | ||
69 | + DisplayColumnsPanelData | ||
70 | +} from '@home/components/widget/lib/display-columns-panel.component'; | ||
71 | +import { | ||
72 | + alarmFields, | ||
73 | + AlarmInfo, | ||
74 | + alarmSeverityColors, | ||
75 | + alarmSeverityTranslations, | ||
76 | + AlarmStatus, | ||
77 | + alarmStatusTranslations | ||
78 | +} from '@shared/models/alarm.models'; | ||
79 | +import { DatePipe } from '@angular/common'; | ||
80 | + | ||
81 | +interface AlarmsTableWidgetSettings extends TableWidgetSettings { | ||
82 | + alarmsTitle: string; | ||
83 | + enableSelection: boolean; | ||
84 | + enableStatusFilter: boolean; | ||
85 | + displayDetails: boolean; | ||
86 | + allowAcknowledgment: boolean; | ||
87 | + allowClear: boolean; | ||
88 | +} | ||
89 | + | ||
90 | +interface AlarmsTableDataKeySettings extends TableWidgetDataKeySettings { | ||
91 | +} | ||
92 | + | ||
93 | +interface AlarmWidgetActionDescriptor extends WidgetActionDescriptor { | ||
94 | + details?: boolean; | ||
95 | + acknowledge?: boolean; | ||
96 | + clear?: boolean; | ||
97 | +} | ||
98 | + | ||
99 | +@Component({ | ||
100 | + selector: 'tb-alarms-table-widget', | ||
101 | + templateUrl: './alarms-table-widget.component.html', | ||
102 | + styleUrls: ['./alarms-table-widget.component.scss', './table-widget.scss'] | ||
103 | +}) | ||
104 | +export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { | ||
105 | + | ||
106 | + @Input() | ||
107 | + ctx: WidgetContext; | ||
108 | + | ||
109 | + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; | ||
110 | + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; | ||
111 | + @ViewChild(MatSort, {static: false}) sort: MatSort; | ||
112 | + | ||
113 | + public enableSelection = true; | ||
114 | + public displayPagination = true; | ||
115 | + public pageSizeOptions; | ||
116 | + public pageLink: PageLink; | ||
117 | + public sortOrderProperty: string; | ||
118 | + public textSearchMode = false; | ||
119 | + public columns: Array<EntityColumn> = []; | ||
120 | + public displayedColumns: string[] = []; | ||
121 | + public actionCellDescriptors: AlarmWidgetActionDescriptor[] = []; | ||
122 | + public alarmsDatasource: AlarmsDatasource; | ||
123 | + | ||
124 | + private settings: AlarmsTableWidgetSettings; | ||
125 | + private widgetConfig: WidgetConfig; | ||
126 | + private subscription: IWidgetSubscription; | ||
127 | + private alarmSource: Datasource; | ||
128 | + | ||
129 | + private displayDetails = true; | ||
130 | + private allowAcknowledgment = true; | ||
131 | + private allowClear = true; | ||
132 | + | ||
133 | + private defaultPageSize = 10; | ||
134 | + private defaultSortOrder = '-' + alarmFields.createdTime.value; | ||
135 | + | ||
136 | + private contentsInfo: {[key: string]: CellContentInfo} = {}; | ||
137 | + private stylesInfo: {[key: string]: CellStyleInfo} = {}; | ||
138 | + private columnWidth: {[key: string]: string} = {}; | ||
139 | + | ||
140 | + private searchAction: WidgetAction = { | ||
141 | + name: 'action.search', | ||
142 | + show: true, | ||
143 | + icon: 'search', | ||
144 | + onAction: () => { | ||
145 | + this.enterFilterMode(); | ||
146 | + } | ||
147 | + }; | ||
148 | + | ||
149 | + private columnDisplayAction: WidgetAction = { | ||
150 | + name: 'entity.columns-to-display', | ||
151 | + show: true, | ||
152 | + icon: 'view_column', | ||
153 | + onAction: ($event) => { | ||
154 | + this.editColumnsToDisplay($event); | ||
155 | + } | ||
156 | + }; | ||
157 | + | ||
158 | + private statusFilterAction: WidgetAction = { | ||
159 | + name: 'alarm.alarm-status-filter', | ||
160 | + show: true, | ||
161 | + onAction: ($event) => { | ||
162 | + this.editAlarmStatusFilter($event); | ||
163 | + }, | ||
164 | + icon: 'filter_list' | ||
165 | + }; | ||
166 | + | ||
167 | + constructor(protected store: Store<AppState>, | ||
168 | + private elementRef: ElementRef, | ||
169 | + private ngZone: NgZone, | ||
170 | + private overlay: Overlay, | ||
171 | + private viewContainerRef: ViewContainerRef, | ||
172 | + private utils: UtilsService, | ||
173 | + public translate: TranslateService, | ||
174 | + private domSanitizer: DomSanitizer, | ||
175 | + private datePipe: DatePipe) { | ||
176 | + super(store); | ||
177 | + | ||
178 | + const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); | ||
179 | + this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); | ||
180 | + } | ||
181 | + | ||
182 | + ngOnInit(): void { | ||
183 | + this.ctx.$scope.alarmsTableWidget = this; | ||
184 | + this.settings = this.ctx.settings; | ||
185 | + this.widgetConfig = this.ctx.widgetConfig; | ||
186 | + this.subscription = this.ctx.defaultSubscription; | ||
187 | + this.alarmSource = this.subscription.alarmSource; | ||
188 | + this.initializeConfig(); | ||
189 | + this.updateAlarmSource(); | ||
190 | + this.ctx.updateWidgetParams(); | ||
191 | + } | ||
192 | + | ||
193 | + ngAfterViewInit(): void { | ||
194 | + fromEvent(this.searchInputField.nativeElement, 'keyup') | ||
195 | + .pipe( | ||
196 | + debounceTime(150), | ||
197 | + distinctUntilChanged(), | ||
198 | + tap(() => { | ||
199 | + if (this.displayPagination) { | ||
200 | + this.paginator.pageIndex = 0; | ||
201 | + } | ||
202 | + this.updateData(); | ||
203 | + }) | ||
204 | + ) | ||
205 | + .subscribe(); | ||
206 | + | ||
207 | + if (this.displayPagination) { | ||
208 | + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); | ||
209 | + } | ||
210 | + (this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange) | ||
211 | + .pipe( | ||
212 | + tap(() => this.updateData()) | ||
213 | + ) | ||
214 | + .subscribe(); | ||
215 | + this.updateData(); | ||
216 | + } | ||
217 | + | ||
218 | + public onDataUpdated() { | ||
219 | + this.ngZone.run(() => { | ||
220 | + this.alarmsDatasource.updateAlarms(this.subscription.alarms); | ||
221 | + this.ctx.detectChanges(); | ||
222 | + }); | ||
223 | + } | ||
224 | + | ||
225 | + private initializeConfig() { | ||
226 | + this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction]; | ||
227 | + | ||
228 | + this.displayDetails = isDefined(this.settings.displayDetails) ? this.settings.displayDetails : true; | ||
229 | + this.allowAcknowledgment = isDefined(this.settings.allowAcknowledgment) ? this.settings.allowAcknowledgment : true; | ||
230 | + this.allowClear = isDefined(this.settings.allowClear) ? this.settings.allowClear : true; | ||
231 | + | ||
232 | + if (this.displayDetails) { | ||
233 | + this.actionCellDescriptors.push( | ||
234 | + { | ||
235 | + displayName: this.translate.instant('alarm.details'), | ||
236 | + icon: 'more_horiz', | ||
237 | + details: true | ||
238 | + } as AlarmWidgetActionDescriptor | ||
239 | + ); | ||
240 | + } | ||
241 | + | ||
242 | + if (this.allowAcknowledgment) { | ||
243 | + this.actionCellDescriptors.push( | ||
244 | + { | ||
245 | + displayName: this.translate.instant('alarm.acknowledge'), | ||
246 | + icon: 'done', | ||
247 | + acknowledge: true | ||
248 | + } as AlarmWidgetActionDescriptor | ||
249 | + ); | ||
250 | + } | ||
251 | + | ||
252 | + if (this.allowClear) { | ||
253 | + this.actionCellDescriptors.push( | ||
254 | + { | ||
255 | + displayName: this.translate.instant('alarm.clear'), | ||
256 | + icon: 'clear', | ||
257 | + clear: true | ||
258 | + } as AlarmWidgetActionDescriptor | ||
259 | + ); | ||
260 | + } | ||
261 | + | ||
262 | + this.actionCellDescriptors = this.actionCellDescriptors.concat(this.ctx.actionsApi.getActionDescriptors('actionCellButton')); | ||
263 | + | ||
264 | + let alarmsTitle: string; | ||
265 | + | ||
266 | + if (this.settings.alarmsTitle && this.settings.alarmsTitle.length) { | ||
267 | + alarmsTitle = this.utils.customTranslation(this.settings.alarmsTitle, this.settings.alarmsTitle); | ||
268 | + } else { | ||
269 | + alarmsTitle = this.translate.instant('alarm.alarms'); | ||
270 | + } | ||
271 | + | ||
272 | + this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle); | ||
273 | + | ||
274 | + this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true; | ||
275 | + if (!this.allowAcknowledgment && !this.allowClear) { | ||
276 | + this.enableSelection = false; | ||
277 | + } | ||
278 | + | ||
279 | + this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; | ||
280 | + this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; | ||
281 | + this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true; | ||
282 | + this.statusFilterAction.show = isDefined(this.settings.enableStatusFilter) ? this.settings.enableStatusFilter : true; | ||
283 | + | ||
284 | + const pageSize = this.settings.defaultPageSize; | ||
285 | + if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { | ||
286 | + this.defaultPageSize = pageSize; | ||
287 | + } | ||
288 | + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; | ||
289 | + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; | ||
290 | + | ||
291 | + const cssString = constructTableCssString(this.widgetConfig); | ||
292 | + const cssParser = new cssjs(); | ||
293 | + cssParser.testMode = false; | ||
294 | + const namespace = 'alarms-table-' + this.utils.hashCode(cssString); | ||
295 | + cssParser.cssPreviewNamespace = namespace; | ||
296 | + cssParser.createStyleElement(namespace, cssString); | ||
297 | + $(this.elementRef.nativeElement).addClass(namespace); | ||
298 | + } | ||
299 | + | ||
300 | + private updateAlarmSource() { | ||
301 | + | ||
302 | + if (this.enableSelection) { | ||
303 | + this.displayedColumns.push('select'); | ||
304 | + } | ||
305 | + | ||
306 | + if (this.alarmSource) { | ||
307 | + this.alarmSource.dataKeys.forEach((_dataKey) => { | ||
308 | + const dataKey: EntityColumn = deepClone(_dataKey) as EntityColumn; | ||
309 | + dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); | ||
310 | + dataKey.def = 'def' + this.columns.length; | ||
311 | + const keySettings: AlarmsTableDataKeySettings = dataKey.settings; | ||
312 | + | ||
313 | + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); | ||
314 | + this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); | ||
315 | + this.columnWidth[dataKey.def] = getColumnWidth(keySettings); | ||
316 | + this.columns.push(dataKey); | ||
317 | + }); | ||
318 | + this.displayedColumns.push(...this.columns.map(column => column.def)); | ||
319 | + } | ||
320 | + if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { | ||
321 | + this.defaultSortOrder = this.settings.defaultSortOrder; | ||
322 | + } | ||
323 | + this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); | ||
324 | + this.sortOrderProperty = toAlarmColumnDef(this.pageLink.sortOrder.property, this.columns); | ||
325 | + | ||
326 | + if (this.actionCellDescriptors.length) { | ||
327 | + this.displayedColumns.push('actions'); | ||
328 | + } | ||
329 | + this.alarmsDatasource = new AlarmsDatasource(); | ||
330 | + if (this.enableSelection) { | ||
331 | + this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => { | ||
332 | + const hideTitlePanel = selectionMode || this.textSearchMode; | ||
333 | + if (this.ctx.hideTitlePanel !== hideTitlePanel) { | ||
334 | + this.ctx.hideTitlePanel = hideTitlePanel; | ||
335 | + this.ctx.detectChanges(true); | ||
336 | + } else { | ||
337 | + this.ctx.detectChanges(); | ||
338 | + } | ||
339 | + }); | ||
340 | + } | ||
341 | + } | ||
342 | + | ||
343 | + private editColumnsToDisplay($event: Event) { | ||
344 | + if ($event) { | ||
345 | + $event.stopPropagation(); | ||
346 | + } | ||
347 | + const target = $event.target || $event.srcElement || $event.currentTarget; | ||
348 | + const config = new OverlayConfig(); | ||
349 | + config.backdropClass = 'cdk-overlay-transparent-backdrop'; | ||
350 | + config.hasBackdrop = true; | ||
351 | + const connectedPosition: ConnectedPosition = { | ||
352 | + originX: 'end', | ||
353 | + originY: 'bottom', | ||
354 | + overlayX: 'end', | ||
355 | + overlayY: 'top' | ||
356 | + }; | ||
357 | + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) | ||
358 | + .withPositions([connectedPosition]); | ||
359 | + | ||
360 | + const overlayRef = this.overlay.create(config); | ||
361 | + overlayRef.backdropClick().subscribe(() => { | ||
362 | + overlayRef.dispose(); | ||
363 | + }); | ||
364 | + | ||
365 | + const columns: DisplayColumn[] = this.columns.map(column => { | ||
366 | + return { | ||
367 | + title: column.title, | ||
368 | + def: column.def, | ||
369 | + display: this.displayedColumns.indexOf(column.def) > -1 | ||
370 | + } | ||
371 | + }); | ||
372 | + | ||
373 | + const injectionTokens = new WeakMap<any, any>([ | ||
374 | + [DISPLAY_COLUMNS_PANEL_DATA, { | ||
375 | + columns, | ||
376 | + columnsUpdated: (newColumns) => { | ||
377 | + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); | ||
378 | + if (this.enableSelection) { | ||
379 | + this.displayedColumns.unshift('select'); | ||
380 | + } | ||
381 | + this.displayedColumns.push('actions'); | ||
382 | + } | ||
383 | + } as DisplayColumnsPanelData], | ||
384 | + [OverlayRef, overlayRef] | ||
385 | + ]); | ||
386 | + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | ||
387 | + overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, | ||
388 | + this.viewContainerRef, injector)); | ||
389 | + this.ctx.detectChanges(); | ||
390 | + } | ||
391 | + | ||
392 | + private editAlarmStatusFilter($event: Event) { | ||
393 | + // TODO: | ||
394 | + } | ||
395 | + | ||
396 | + private enterFilterMode() { | ||
397 | + this.textSearchMode = true; | ||
398 | + this.pageLink.textSearch = ''; | ||
399 | + this.ctx.hideTitlePanel = true; | ||
400 | + this.ctx.detectChanges(true); | ||
401 | + setTimeout(() => { | ||
402 | + this.searchInputField.nativeElement.focus(); | ||
403 | + this.searchInputField.nativeElement.setSelectionRange(0, 0); | ||
404 | + }, 10); | ||
405 | + } | ||
406 | + | ||
407 | + exitFilterMode() { | ||
408 | + this.textSearchMode = false; | ||
409 | + this.pageLink.textSearch = null; | ||
410 | + if (this.displayPagination) { | ||
411 | + this.paginator.pageIndex = 0; | ||
412 | + } | ||
413 | + this.updateData(); | ||
414 | + this.ctx.hideTitlePanel = false; | ||
415 | + this.ctx.detectChanges(true); | ||
416 | + } | ||
417 | + | ||
418 | + private updateData() { | ||
419 | + if (this.displayPagination) { | ||
420 | + this.pageLink.page = this.paginator.pageIndex; | ||
421 | + this.pageLink.pageSize = this.paginator.pageSize; | ||
422 | + } else { | ||
423 | + this.pageLink.page = 0; | ||
424 | + } | ||
425 | + this.pageLink.sortOrder.property = fromAlarmColumnDef(this.sort.active, this.columns); | ||
426 | + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; | ||
427 | + this.alarmsDatasource.loadAlarms(this.pageLink); | ||
428 | + this.ctx.detectChanges(); | ||
429 | + } | ||
430 | + | ||
431 | + public trackByColumnDef(index, column: EntityColumn) { | ||
432 | + return column.def; | ||
433 | + } | ||
434 | + | ||
435 | + public headerStyle(key: EntityColumn): any { | ||
436 | + const columnWidth = this.columnWidth[key.def]; | ||
437 | + return { | ||
438 | + width: columnWidth | ||
439 | + } | ||
440 | + } | ||
441 | + | ||
442 | + public cellStyle(alarm: AlarmInfo, key: EntityColumn): any { | ||
443 | + let style: any = {}; | ||
444 | + if (alarm && key) { | ||
445 | + const styleInfo = this.stylesInfo[key.def]; | ||
446 | + const value = getAlarmValue(alarm, key); | ||
447 | + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { | ||
448 | + try { | ||
449 | + style = styleInfo.cellStyleFunction(value); | ||
450 | + } catch (e) { | ||
451 | + style = {}; | ||
452 | + } | ||
453 | + } else { | ||
454 | + style = this.defaultStyle(key, value); | ||
455 | + } | ||
456 | + } | ||
457 | + if (!style.width) { | ||
458 | + const columnWidth = this.columnWidth[key.def]; | ||
459 | + style.width = columnWidth; | ||
460 | + } | ||
461 | + return style; | ||
462 | + } | ||
463 | + | ||
464 | + public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml { | ||
465 | + let strContent = ''; | ||
466 | + if (alarm && key) { | ||
467 | + const contentInfo = this.contentsInfo[key.def]; | ||
468 | + const value = getAlarmValue(alarm, key); | ||
469 | + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { | ||
470 | + if (isDefined(value)) { | ||
471 | + strContent = '' + value; | ||
472 | + } | ||
473 | + var content = strContent; | ||
474 | + try { | ||
475 | + content = contentInfo.cellContentFunction(value, alarm, this.ctx); | ||
476 | + } catch (e) { | ||
477 | + content = strContent; | ||
478 | + } | ||
479 | + } else { | ||
480 | + content = this.defaultContent(key, value); | ||
481 | + } | ||
482 | + return this.domSanitizer.bypassSecurityTrustHtml(content); | ||
483 | + } else { | ||
484 | + return strContent; | ||
485 | + } | ||
486 | + } | ||
487 | + | ||
488 | + public onRowClick($event: Event, alarm: AlarmInfo) { | ||
489 | + if ($event) { | ||
490 | + $event.stopPropagation(); | ||
491 | + } | ||
492 | + this.alarmsDatasource.toggleCurrentAlarm(alarm); | ||
493 | + const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick'); | ||
494 | + if (descriptors.length) { | ||
495 | + let entityId; | ||
496 | + let entityName; | ||
497 | + if (alarm && alarm.originator) { | ||
498 | + entityId = alarm.originator; | ||
499 | + entityName = alarm.originatorName; | ||
500 | + } | ||
501 | + this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {alarm}); | ||
502 | + } | ||
503 | + } | ||
504 | + | ||
505 | + public onActionButtonClick($event: Event, alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor) { | ||
506 | + if (actionDescriptor.details) { | ||
507 | + this.openAlarmDetails($event, alarm); | ||
508 | + } else if (actionDescriptor.acknowledge) { | ||
509 | + this.ackAlarm($event, alarm); | ||
510 | + } else if (actionDescriptor.clear) { | ||
511 | + this.clearAlarm($event, alarm); | ||
512 | + } else { | ||
513 | + if ($event) { | ||
514 | + $event.stopPropagation(); | ||
515 | + } | ||
516 | + let entityId; | ||
517 | + let entityName; | ||
518 | + if (alarm && alarm.originator) { | ||
519 | + entityId = alarm.originator; | ||
520 | + entityName = alarm.originatorName; | ||
521 | + } | ||
522 | + this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm}); | ||
523 | + } | ||
524 | + } | ||
525 | + | ||
526 | + public actionEnabled(alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean { | ||
527 | + if (actionDescriptor.acknowledge) { | ||
528 | + return (alarm.status === AlarmStatus.ACTIVE_UNACK || | ||
529 | + alarm.status === AlarmStatus.CLEARED_UNACK); | ||
530 | + } else if (actionDescriptor.clear) { | ||
531 | + return (alarm.status === AlarmStatus.ACTIVE_ACK || | ||
532 | + alarm.status === AlarmStatus.ACTIVE_UNACK); | ||
533 | + } | ||
534 | + return true; | ||
535 | + } | ||
536 | + | ||
537 | + private openAlarmDetails($event: Event, alarm: AlarmInfo) { | ||
538 | + if ($event) { | ||
539 | + $event.stopPropagation(); | ||
540 | + } | ||
541 | + // TODO: | ||
542 | + } | ||
543 | + | ||
544 | + private ackAlarm($event: Event, alarm: AlarmInfo) { | ||
545 | + if ($event) { | ||
546 | + $event.stopPropagation(); | ||
547 | + } | ||
548 | + // TODO: | ||
549 | + } | ||
550 | + | ||
551 | + public ackAlarms($event: Event) { | ||
552 | + if ($event) { | ||
553 | + $event.stopPropagation(); | ||
554 | + } | ||
555 | + // TODO: | ||
556 | + } | ||
557 | + | ||
558 | + private clearAlarm($event: Event, alarm: AlarmInfo) { | ||
559 | + if ($event) { | ||
560 | + $event.stopPropagation(); | ||
561 | + } | ||
562 | + // TODO: | ||
563 | + } | ||
564 | + | ||
565 | + public clearAlarms($event: Event) { | ||
566 | + if ($event) { | ||
567 | + $event.stopPropagation(); | ||
568 | + } | ||
569 | + // TODO: | ||
570 | + } | ||
571 | + | ||
572 | + private defaultContent(key: EntityColumn, value: any): any { | ||
573 | + if (isDefined(value)) { | ||
574 | + const alarmField = alarmFields[key.name]; | ||
575 | + if (alarmField) { | ||
576 | + if (alarmField.time) { | ||
577 | + return this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss'); | ||
578 | + } else if (alarmField.value === alarmFields.severity.value) { | ||
579 | + return this.translate.instant(alarmSeverityTranslations.get(value)); | ||
580 | + } else if (alarmField.value === alarmFields.status.value) { | ||
581 | + return this.translate.instant(alarmStatusTranslations.get(value)); | ||
582 | + } else if (alarmField.value === alarmFields.originatorType.value) { | ||
583 | + return this.translate.instant(entityTypeTranslations.get(value).type); | ||
584 | + } | ||
585 | + else { | ||
586 | + return value; | ||
587 | + } | ||
588 | + } else { | ||
589 | + return value; | ||
590 | + } | ||
591 | + } else { | ||
592 | + return ''; | ||
593 | + } | ||
594 | + } | ||
595 | + | ||
596 | + private defaultStyle(key: EntityColumn, value: any): any { | ||
597 | + if (isDefined(value)) { | ||
598 | + const alarmField = alarmFields[key.name]; | ||
599 | + if (alarmField) { | ||
600 | + if (alarmField.value == alarmFields.severity.value) { | ||
601 | + return { | ||
602 | + fontWeight: 'bold', | ||
603 | + color: alarmSeverityColors.get(value) | ||
604 | + }; | ||
605 | + } else { | ||
606 | + return {}; | ||
607 | + } | ||
608 | + } else { | ||
609 | + return {}; | ||
610 | + } | ||
611 | + } else { | ||
612 | + return {}; | ||
613 | + } | ||
614 | + } | ||
615 | + | ||
616 | +} | ||
617 | + | ||
618 | +class AlarmsDatasource implements DataSource<AlarmInfo> { | ||
619 | + | ||
620 | + private alarmsSubject = new BehaviorSubject<AlarmInfo[]>([]); | ||
621 | + private pageDataSubject = new BehaviorSubject<PageData<AlarmInfo>>(emptyPageData<AlarmInfo>()); | ||
622 | + | ||
623 | + public selection = new SelectionModel<AlarmInfo>(true, [], false); | ||
624 | + | ||
625 | + private selectionModeChanged = new EventEmitter<boolean>(); | ||
626 | + | ||
627 | + public selectionModeChanged$ = this.selectionModeChanged.asObservable(); | ||
628 | + | ||
629 | + private allAlarms: Array<AlarmInfo> = []; | ||
630 | + private allAlarmsSubject = new BehaviorSubject<AlarmInfo[]>([]); | ||
631 | + private allAlarms$: Observable<Array<AlarmInfo>> = this.allAlarmsSubject.asObservable(); | ||
632 | + | ||
633 | + private currentAlarm: AlarmInfo = null; | ||
634 | + | ||
635 | + constructor() { | ||
636 | + } | ||
637 | + | ||
638 | + connect(collectionViewer: CollectionViewer): Observable<AlarmInfo[] | ReadonlyArray<AlarmInfo>> { | ||
639 | + return this.alarmsSubject.asObservable(); | ||
640 | + } | ||
641 | + | ||
642 | + disconnect(collectionViewer: CollectionViewer): void { | ||
643 | + this.alarmsSubject.complete(); | ||
644 | + this.pageDataSubject.complete(); | ||
645 | + } | ||
646 | + | ||
647 | + loadAlarms(pageLink: PageLink) { | ||
648 | + if (this.selection.hasValue()) { | ||
649 | + this.selection.clear(); | ||
650 | + this.onSelectionModeChanged(false); | ||
651 | + } | ||
652 | + this.fetchAlarms(pageLink).pipe( | ||
653 | + catchError(() => of(emptyPageData<AlarmInfo>())), | ||
654 | + ).subscribe( | ||
655 | + (pageData) => { | ||
656 | + this.alarmsSubject.next(pageData.data); | ||
657 | + this.pageDataSubject.next(pageData); | ||
658 | + } | ||
659 | + ); | ||
660 | + } | ||
661 | + | ||
662 | + updateAlarms(alarms: AlarmInfo[]) { | ||
663 | + alarms.forEach((newAlarm) => { | ||
664 | + const existingAlarmIndex = this.allAlarms.findIndex(alarm => alarm.id.id === newAlarm.id.id); | ||
665 | + if (existingAlarmIndex > -1) { | ||
666 | + Object.assign(this.allAlarms[existingAlarmIndex], newAlarm); | ||
667 | + } else { | ||
668 | + this.allAlarms.push(newAlarm); | ||
669 | + } | ||
670 | + }); | ||
671 | + for (let i = this.allAlarms.length - 1; i >= 0; i--) { | ||
672 | + const oldAlarm = this.allAlarms[i]; | ||
673 | + const newAlarmIndex = alarms.findIndex(alarm => alarm.id.id === oldAlarm.id.id); | ||
674 | + if (newAlarmIndex === -1) { | ||
675 | + this.allAlarms.splice(i, 1); | ||
676 | + } | ||
677 | + } | ||
678 | + if (this.selection.hasValue()) { | ||
679 | + const toRemove: AlarmInfo[] = []; | ||
680 | + this.selection.selected.forEach((selectedAlarm) => { | ||
681 | + const existingAlarm = this.allAlarms.find(alarm => alarm.id.id === selectedAlarm.id.id); | ||
682 | + if (!existingAlarm) { | ||
683 | + toRemove.push(selectedAlarm); | ||
684 | + } | ||
685 | + }); | ||
686 | + this.selection.deselect(...toRemove); | ||
687 | + if (this.selection.isEmpty()) { | ||
688 | + this.onSelectionModeChanged(false); | ||
689 | + } | ||
690 | + } | ||
691 | + this.allAlarmsSubject.next(this.allAlarms); | ||
692 | + } | ||
693 | + | ||
694 | + isAllSelected(): Observable<boolean> { | ||
695 | + const numSelected = this.selection.selected.length; | ||
696 | + return this.alarmsSubject.pipe( | ||
697 | + map((alarms) => numSelected === alarms.length) | ||
698 | + ); | ||
699 | + } | ||
700 | + | ||
701 | + isEmpty(): Observable<boolean> { | ||
702 | + return this.alarmsSubject.pipe( | ||
703 | + map((alarms) => !alarms.length) | ||
704 | + ); | ||
705 | + } | ||
706 | + | ||
707 | + total(): Observable<number> { | ||
708 | + return this.pageDataSubject.pipe( | ||
709 | + map((pageData) => pageData.totalElements) | ||
710 | + ); | ||
711 | + } | ||
712 | + | ||
713 | + toggleSelection(alarm: AlarmInfo) { | ||
714 | + const hasValue = this.selection.hasValue(); | ||
715 | + this.selection.toggle(alarm); | ||
716 | + if (hasValue !== this.selection.hasValue()) { | ||
717 | + this.onSelectionModeChanged(this.selection.hasValue()); | ||
718 | + } | ||
719 | + } | ||
720 | + | ||
721 | + isSelected(alarm: AlarmInfo): boolean { | ||
722 | + return this.selection.isSelected(alarm); | ||
723 | + } | ||
724 | + | ||
725 | + masterToggle() { | ||
726 | + this.alarmsSubject.pipe( | ||
727 | + tap((alarms) => { | ||
728 | + const numSelected = this.selection.selected.length; | ||
729 | + if (numSelected === alarms.length) { | ||
730 | + this.selection.clear(); | ||
731 | + if (numSelected > 0) { | ||
732 | + this.onSelectionModeChanged(false); | ||
733 | + } | ||
734 | + } else { | ||
735 | + alarms.forEach(row => { | ||
736 | + this.selection.select(row); | ||
737 | + }); | ||
738 | + if (numSelected === 0) { | ||
739 | + this.onSelectionModeChanged(true); | ||
740 | + } | ||
741 | + } | ||
742 | + }), | ||
743 | + take(1) | ||
744 | + ).subscribe(); | ||
745 | + } | ||
746 | + | ||
747 | + public toggleCurrentAlarm(alarm: AlarmInfo): boolean { | ||
748 | + if (this.currentAlarm !== alarm) { | ||
749 | + this.currentAlarm = alarm; | ||
750 | + return true; | ||
751 | + } else { | ||
752 | + return false; | ||
753 | + } | ||
754 | + } | ||
755 | + | ||
756 | + public isCurrentAlarm(alarm: AlarmInfo): boolean { | ||
757 | + return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) && | ||
758 | + (this.currentAlarm.id.id === alarm.id.id); | ||
759 | + } | ||
760 | + | ||
761 | + private onSelectionModeChanged(selectionMode: boolean) { | ||
762 | + this.selectionModeChanged.emit(selectionMode); | ||
763 | + } | ||
764 | + | ||
765 | + private fetchAlarms(pageLink: PageLink): Observable<PageData<AlarmInfo>> { | ||
766 | + return this.allAlarms$.pipe( | ||
767 | + map((data) => pageLink.filterData(data)) | ||
768 | + ); | ||
769 | + } | ||
770 | +} |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div class="tb-entity-table tb-absolute-fill"> | 18 | +<div class="tb-table-widget tb-absolute-fill"> |
19 | <div fxFlex fxLayout="column" class="tb-absolute-fill"> | 19 | <div fxFlex fxLayout="column" class="tb-absolute-fill"> |
20 | <mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode"> | 20 | <mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode"> |
21 | <div class="mat-toolbar-tools"> | 21 | <div class="mat-toolbar-tools"> |
@@ -39,8 +39,8 @@ | @@ -39,8 +39,8 @@ | ||
39 | </mat-toolbar> | 39 | </mat-toolbar> |
40 | <div fxFlex class="table-container"> | 40 | <div fxFlex class="table-container"> |
41 | <mat-table [dataSource]="entityDatasource" | 41 | <mat-table [dataSource]="entityDatasource" |
42 | - matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear> | ||
43 | - <ng-container [matColumnDef]="column.label" *ngFor="let column of columns; trackBy: trackByColumnLabel;"> | 42 | + matSort [matSortActive]="sortOrderProperty" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear> |
43 | + <ng-container [matColumnDef]="column.def" *ngFor="let column of columns; trackBy: trackByColumnDef;"> | ||
44 | <mat-header-cell [ngStyle]="headerStyle(column)" *matHeaderCellDef mat-sort-header> {{ column.title }} </mat-header-cell> | 44 | <mat-header-cell [ngStyle]="headerStyle(column)" *matHeaderCellDef mat-sort-header> {{ column.title }} </mat-header-cell> |
45 | <mat-cell *matCellDef="let entity;" | 45 | <mat-cell *matCellDef="let entity;" |
46 | [innerHTML]="cellContent(entity, column)" | 46 | [innerHTML]="cellContent(entity, column)" |
@@ -16,24 +16,4 @@ | @@ -16,24 +16,4 @@ | ||
16 | :host { | 16 | :host { |
17 | width: 100%; | 17 | width: 100%; |
18 | height: 100%; | 18 | height: 100%; |
19 | - .tb-entity-table { | ||
20 | - .mat-table, .mat-paginator, mat-toolbar.mat-table-toolbar { | ||
21 | - background: transparent; | ||
22 | - } | ||
23 | - mat-toolbar { | ||
24 | - height: 39px; | ||
25 | - max-height: 39px; | ||
26 | - .mat-toolbar-tools { | ||
27 | - height: 39px; | ||
28 | - max-height: 39px; | ||
29 | - } | ||
30 | - } | ||
31 | - .table-container { | ||
32 | - overflow: auto; | ||
33 | - } | ||
34 | - } | ||
35 | -} | ||
36 | - | ||
37 | -:host ::ng-deep .mat-sort-header-sorted .mat-sort-header-arrow { | ||
38 | - opacity: 1 !important; | ||
39 | } | 19 | } |
@@ -14,7 +14,16 @@ | @@ -14,7 +14,16 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | 17 | +import { |
18 | + AfterViewInit, | ||
19 | + Component, | ||
20 | + ElementRef, | ||
21 | + Input, | ||
22 | + NgZone, | ||
23 | + OnInit, | ||
24 | + ViewChild, | ||
25 | + ViewContainerRef | ||
26 | +} from '@angular/core'; | ||
18 | import { PageComponent } from '@shared/components/page.component'; | 27 | import { PageComponent } from '@shared/components/page.component'; |
19 | import { Store } from '@ngrx/store'; | 28 | import { Store } from '@ngrx/store'; |
20 | import { AppState } from '@core/core.state'; | 29 | import { AppState } from '@core/core.state'; |
@@ -31,7 +40,6 @@ import { IWidgetSubscription } from '@core/api/widget-api.models'; | @@ -31,7 +40,6 @@ import { IWidgetSubscription } from '@core/api/widget-api.models'; | ||
31 | import { UtilsService } from '@core/services/utils.service'; | 40 | import { UtilsService } from '@core/services/utils.service'; |
32 | import { TranslateService } from '@ngx-translate/core'; | 41 | import { TranslateService } from '@ngx-translate/core'; |
33 | import { deepClone, isDefined, isNumber } from '@core/utils'; | 42 | import { deepClone, isDefined, isNumber } from '@core/utils'; |
34 | -import * as tinycolor_ from 'tinycolor2'; | ||
35 | import cssjs from '@core/css/css'; | 43 | import cssjs from '@core/css/css'; |
36 | import { PageLink } from '@shared/models/page/page-link'; | 44 | import { PageLink } from '@shared/models/page/page-link'; |
37 | import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; | 45 | import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; |
@@ -49,10 +57,18 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | @@ -49,10 +57,18 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | ||
49 | import { | 57 | import { |
50 | CellContentInfo, | 58 | CellContentInfo, |
51 | CellStyleInfo, | 59 | CellStyleInfo, |
60 | + constructTableCssString, | ||
52 | DisplayColumn, | 61 | DisplayColumn, |
53 | EntityColumn, | 62 | EntityColumn, |
54 | EntityData, | 63 | EntityData, |
55 | - getEntityValue | 64 | + fromEntityColumnDef, |
65 | + getCellContentInfo, | ||
66 | + getCellStyleInfo, | ||
67 | + getColumnWidth, | ||
68 | + getEntityValue, | ||
69 | + TableWidgetDataKeySettings, | ||
70 | + TableWidgetSettings, | ||
71 | + toEntityColumnDef | ||
56 | } from '@home/components/widget/lib/table-widget.models'; | 72 | } from '@home/components/widget/lib/table-widget.models'; |
57 | import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | 73 | import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
58 | import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | 74 | import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; |
@@ -62,32 +78,20 @@ import { | @@ -62,32 +78,20 @@ import { | ||
62 | DisplayColumnsPanelData | 78 | DisplayColumnsPanelData |
63 | } from '@home/components/widget/lib/display-columns-panel.component'; | 79 | } from '@home/components/widget/lib/display-columns-panel.component'; |
64 | 80 | ||
65 | -const tinycolor = tinycolor_; | ||
66 | - | ||
67 | -interface EntitiesTableWidgetSettings { | 81 | +interface EntitiesTableWidgetSettings extends TableWidgetSettings { |
68 | entitiesTitle: string; | 82 | entitiesTitle: string; |
69 | - enableSearch: boolean; | ||
70 | - enableSelectColumnDisplay: boolean; | ||
71 | displayEntityName: boolean; | 83 | displayEntityName: boolean; |
72 | entityNameColumnTitle: string; | 84 | entityNameColumnTitle: string; |
73 | displayEntityType: boolean; | 85 | displayEntityType: boolean; |
74 | - displayPagination: boolean; | ||
75 | - defaultPageSize: number; | ||
76 | - defaultSortOrder: string; | ||
77 | } | 86 | } |
78 | 87 | ||
79 | -interface EntitiesTableDataKeySettings { | ||
80 | - columnWidth: string; | ||
81 | - useCellStyleFunction: boolean; | ||
82 | - cellStyleFunction: string; | ||
83 | - useCellContentFunction: boolean; | ||
84 | - cellContentFunction: string; | 88 | +interface EntitiesTableDataKeySettings extends TableWidgetDataKeySettings { |
85 | } | 89 | } |
86 | 90 | ||
87 | @Component({ | 91 | @Component({ |
88 | selector: 'tb-entities-table-widget', | 92 | selector: 'tb-entities-table-widget', |
89 | templateUrl: './entities-table-widget.component.html', | 93 | templateUrl: './entities-table-widget.component.html', |
90 | - styleUrls: ['./entities-table-widget.component.scss'] | 94 | + styleUrls: ['./entities-table-widget.component.scss', './table-widget.scss'] |
91 | }) | 95 | }) |
92 | export class EntitiesTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { | 96 | export class EntitiesTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { |
93 | 97 | ||
@@ -101,6 +105,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -101,6 +105,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
101 | public displayPagination = true; | 105 | public displayPagination = true; |
102 | public pageSizeOptions; | 106 | public pageSizeOptions; |
103 | public pageLink: PageLink; | 107 | public pageLink: PageLink; |
108 | + public sortOrderProperty: string; | ||
104 | public textSearchMode = false; | 109 | public textSearchMode = false; |
105 | public columns: Array<EntityColumn> = []; | 110 | public columns: Array<EntityColumn> = []; |
106 | public displayedColumns: string[] = []; | 111 | public displayedColumns: string[] = []; |
@@ -138,6 +143,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -138,6 +143,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
138 | 143 | ||
139 | constructor(protected store: Store<AppState>, | 144 | constructor(protected store: Store<AppState>, |
140 | private elementRef: ElementRef, | 145 | private elementRef: ElementRef, |
146 | + private ngZone: NgZone, | ||
141 | private overlay: Overlay, | 147 | private overlay: Overlay, |
142 | private viewContainerRef: ViewContainerRef, | 148 | private viewContainerRef: ViewContainerRef, |
143 | private utils: UtilsService, | 149 | private utils: UtilsService, |
@@ -185,13 +191,17 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -185,13 +191,17 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
185 | } | 191 | } |
186 | 192 | ||
187 | public onDataUpdated() { | 193 | public onDataUpdated() { |
188 | - this.entityDatasource.updateEntitiesData(this.subscription.data); | ||
189 | - this.ctx.detectChanges(); | 194 | + this.ngZone.run(() => { |
195 | + this.entityDatasource.updateEntitiesData(this.subscription.data); | ||
196 | + this.ctx.detectChanges(); | ||
197 | + }); | ||
190 | } | 198 | } |
191 | 199 | ||
192 | private initializeConfig() { | 200 | private initializeConfig() { |
193 | this.ctx.widgetActions = [this.searchAction, this.columnDisplayAction]; | 201 | this.ctx.widgetActions = [this.searchAction, this.columnDisplayAction]; |
194 | 202 | ||
203 | + this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); | ||
204 | + | ||
195 | let entitiesTitle: string; | 205 | let entitiesTitle: string; |
196 | 206 | ||
197 | if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { | 207 | if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { |
@@ -212,78 +222,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -212,78 +222,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
212 | this.defaultPageSize = pageSize; | 222 | this.defaultPageSize = pageSize; |
213 | } | 223 | } |
214 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; | 224 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; |
215 | - | ||
216 | - if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { | ||
217 | - this.defaultSortOrder = this.settings.defaultSortOrder; | ||
218 | - } | ||
219 | - | ||
220 | this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; | 225 | this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; |
221 | - this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); | ||
222 | - | ||
223 | - const origColor = this.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; | ||
224 | - const origBackgroundColor = this.widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; | ||
225 | - const defaultColor = tinycolor(origColor); | ||
226 | - const mdDark = defaultColor.setAlpha(0.87).toRgbString(); | ||
227 | - const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); | ||
228 | - const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); | ||
229 | - const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); | ||
230 | - | ||
231 | - const cssString = | ||
232 | - '.mat-input-element::placeholder {\n' + | ||
233 | - ' color: ' + mdDarkSecondary + ';\n'+ | ||
234 | - '}\n' + | ||
235 | - '.mat-input-element::-moz-placeholder {\n' + | ||
236 | - ' color: ' + mdDarkSecondary + ';\n'+ | ||
237 | - '}\n' + | ||
238 | - '.mat-input-element::-webkit-input-placeholder {\n' + | ||
239 | - ' color: ' + mdDarkSecondary + ';\n'+ | ||
240 | - '}\n' + | ||
241 | - '.mat-input-element:-ms-input-placeholder {\n' + | ||
242 | - ' color: ' + mdDarkSecondary + ';\n'+ | ||
243 | - '}\n' + | ||
244 | - 'mat-toolbar.mat-table-toolbar {\n'+ | ||
245 | - 'color: ' + mdDark + ';\n'+ | ||
246 | - '}\n'+ | ||
247 | - 'mat-toolbar.mat-table-toolbar button.mat-icon-button mat-icon {\n'+ | ||
248 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
249 | - '}\n'+ | ||
250 | - '.mat-table .mat-header-row {\n'+ | ||
251 | - 'background-color: ' + origBackgroundColor + ';\n'+ | ||
252 | - '}\n'+ | ||
253 | - '.mat-table .mat-header-cell {\n'+ | ||
254 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
255 | - '}\n'+ | ||
256 | - '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ | ||
257 | - 'color: ' + mdDarkDisabled + ';\n'+ | ||
258 | - '}\n'+ | ||
259 | - '.mat-table .mat-row, .mat-table .mat-header-row {\n'+ | ||
260 | - 'border-bottom-color: '+mdDarkDivider+';\n'+ | ||
261 | - '}\n'+ | ||
262 | - '.mat-table .mat-row:not(.tb-current-entity):not(:hover) .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ | ||
263 | - 'background-color: ' + origBackgroundColor + ';\n'+ | ||
264 | - '}\n'+ | ||
265 | - '.mat-table .mat-cell {\n'+ | ||
266 | - 'color: ' + mdDark + ';\n'+ | ||
267 | - '}\n'+ | ||
268 | - '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ | ||
269 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
270 | - '}\n'+ | ||
271 | - '.mat-divider {\n'+ | ||
272 | - 'border-top-color: ' + mdDarkDivider + ';\n'+ | ||
273 | - '}\n'+ | ||
274 | - '.mat-paginator {\n'+ | ||
275 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
276 | - '}\n'+ | ||
277 | - '.mat-paginator button.mat-icon-button {\n'+ | ||
278 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
279 | - '}\n'+ | ||
280 | - '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ | ||
281 | - 'color: ' + mdDarkDisabled + ';\n'+ | ||
282 | - '}\n'+ | ||
283 | - '.mat-paginator .mat-select-value {\n'+ | ||
284 | - 'color: ' + mdDarkSecondary + ';\n'+ | ||
285 | - '}'; | ||
286 | 226 | ||
227 | + const cssString = constructTableCssString(this.widgetConfig); | ||
287 | const cssParser = new cssjs(); | 228 | const cssParser = new cssjs(); |
288 | cssParser.testMode = false; | 229 | cssParser.testMode = false; |
289 | const namespace = 'entities-table-' + this.utils.hashCode(cssString); | 230 | const namespace = 'entities-table-' + this.utils.hashCode(cssString); |
@@ -294,8 +235,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -294,8 +235,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
294 | 235 | ||
295 | private updateDatasources() { | 236 | private updateDatasources() { |
296 | 237 | ||
297 | - this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); | ||
298 | - | ||
299 | const displayEntityName = isDefined(this.settings.displayEntityName) ? this.settings.displayEntityName : true; | 238 | const displayEntityName = isDefined(this.settings.displayEntityName) ? this.settings.displayEntityName : true; |
300 | let entityNameColumnTitle: string; | 239 | let entityNameColumnTitle: string; |
301 | if (this.settings.entityNameColumnTitle && this.settings.entityNameColumnTitle.length) { | 240 | if (this.settings.entityNameColumnTitle && this.settings.entityNameColumnTitle.length) { |
@@ -306,11 +245,11 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -306,11 +245,11 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
306 | const displayEntityType = isDefined(this.settings.displayEntityType) ? this.settings.displayEntityType : true; | 245 | const displayEntityType = isDefined(this.settings.displayEntityType) ? this.settings.displayEntityType : true; |
307 | 246 | ||
308 | if (displayEntityName) { | 247 | if (displayEntityName) { |
309 | - this.displayedColumns.push('entityName'); | ||
310 | this.columns.push( | 248 | this.columns.push( |
311 | { | 249 | { |
312 | name: 'entityName', | 250 | name: 'entityName', |
313 | label: 'entityName', | 251 | label: 'entityName', |
252 | + def: 'entityName', | ||
314 | title: entityNameColumnTitle | 253 | title: entityNameColumnTitle |
315 | } as EntityColumn | 254 | } as EntityColumn |
316 | ); | 255 | ); |
@@ -320,14 +259,14 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -320,14 +259,14 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
320 | this.stylesInfo['entityName'] = { | 259 | this.stylesInfo['entityName'] = { |
321 | useCellStyleFunction: false | 260 | useCellStyleFunction: false |
322 | }; | 261 | }; |
323 | - this.columnWidth['entityName'] = '100px'; | 262 | + this.columnWidth['entityName'] = '0px'; |
324 | } | 263 | } |
325 | if (displayEntityType) { | 264 | if (displayEntityType) { |
326 | - this.displayedColumns.push('entityType'); | ||
327 | this.columns.push( | 265 | this.columns.push( |
328 | { | 266 | { |
329 | name: 'entityType', | 267 | name: 'entityType', |
330 | label: 'entityType', | 268 | label: 'entityType', |
269 | + def: 'entityType', | ||
331 | title: this.translate.instant('entity.entity-type'), | 270 | title: this.translate.instant('entity.entity-type'), |
332 | } as EntityColumn | 271 | } as EntityColumn |
333 | ); | 272 | ); |
@@ -337,7 +276,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -337,7 +276,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
337 | this.stylesInfo['entityType'] = { | 276 | this.stylesInfo['entityType'] = { |
338 | useCellStyleFunction: false | 277 | useCellStyleFunction: false |
339 | }; | 278 | }; |
340 | - this.columnWidth['entityType'] = '100px'; | 279 | + this.columnWidth['entityType'] = '0px'; |
341 | } | 280 | } |
342 | 281 | ||
343 | const dataKeys: Array<DataKey> = []; | 282 | const dataKeys: Array<DataKey> = []; |
@@ -353,55 +292,24 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -353,55 +292,24 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
353 | dataKeys.push(dataKey); | 292 | dataKeys.push(dataKey); |
354 | 293 | ||
355 | dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); | 294 | dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); |
295 | + dataKey.def = 'def' + this.columns.length; | ||
356 | const keySettings: EntitiesTableDataKeySettings = dataKey.settings; | 296 | const keySettings: EntitiesTableDataKeySettings = dataKey.settings; |
357 | 297 | ||
358 | - let cellStyleFunction: Function = null; | ||
359 | - let useCellStyleFunction = false; | ||
360 | - | ||
361 | - if (keySettings.useCellStyleFunction === true) { | ||
362 | - if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { | ||
363 | - try { | ||
364 | - cellStyleFunction = new Function('value', keySettings.cellStyleFunction); | ||
365 | - useCellStyleFunction = true; | ||
366 | - } catch (e) { | ||
367 | - cellStyleFunction = null; | ||
368 | - useCellStyleFunction = false; | ||
369 | - } | ||
370 | - } | ||
371 | - } | ||
372 | - this.stylesInfo[dataKey.label] = { | ||
373 | - useCellStyleFunction, | ||
374 | - cellStyleFunction | ||
375 | - }; | ||
376 | - | ||
377 | - let cellContentFunction: Function = null; | ||
378 | - let useCellContentFunction = false; | ||
379 | - | ||
380 | - if (keySettings.useCellContentFunction === true) { | ||
381 | - if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { | ||
382 | - try { | ||
383 | - cellContentFunction = new Function('value, entity, ctx', keySettings.cellContentFunction); | ||
384 | - useCellContentFunction = true; | ||
385 | - } catch (e) { | ||
386 | - cellContentFunction = null; | ||
387 | - useCellContentFunction = false; | ||
388 | - } | ||
389 | - } | ||
390 | - } | ||
391 | - | ||
392 | - this.contentsInfo[dataKey.label] = { | ||
393 | - useCellContentFunction, | ||
394 | - cellContentFunction, | ||
395 | - units: dataKey.units, | ||
396 | - decimals: dataKey.decimals | ||
397 | - }; | ||
398 | - | ||
399 | - const columnWidth = isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; | ||
400 | - this.columnWidth[dataKey.label] = columnWidth; | ||
401 | - this.displayedColumns.push(dataKey.label); | 298 | + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); |
299 | + this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); | ||
300 | + this.contentsInfo[dataKey.def].units = dataKey.units; | ||
301 | + this.contentsInfo[dataKey.def].decimals = dataKey.decimals; | ||
302 | + this.columnWidth[dataKey.def] = getColumnWidth(keySettings); | ||
402 | this.columns.push(dataKey); | 303 | this.columns.push(dataKey); |
403 | }); | 304 | }); |
305 | + this.displayedColumns.push(...this.columns.map(column => column.def)); | ||
306 | + } | ||
307 | + | ||
308 | + if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { | ||
309 | + this.defaultSortOrder = this.settings.defaultSortOrder; | ||
404 | } | 310 | } |
311 | + this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); | ||
312 | + this.sortOrderProperty = toEntityColumnDef(this.pageLink.sortOrder.property, this.columns); | ||
405 | 313 | ||
406 | if (this.actionCellDescriptors.length) { | 314 | if (this.actionCellDescriptors.length) { |
407 | this.displayedColumns.push('actions'); | 315 | this.displayedColumns.push('actions'); |
@@ -435,8 +343,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -435,8 +343,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
435 | const columns: DisplayColumn[] = this.columns.map(column => { | 343 | const columns: DisplayColumn[] = this.columns.map(column => { |
436 | return { | 344 | return { |
437 | title: column.title, | 345 | title: column.title, |
438 | - label: column.label, | ||
439 | - display: this.displayedColumns.indexOf(column.label) > -1 | 346 | + def: column.def, |
347 | + display: this.displayedColumns.indexOf(column.def) > -1 | ||
440 | } | 348 | } |
441 | }); | 349 | }); |
442 | 350 | ||
@@ -444,7 +352,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -444,7 +352,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
444 | [DISPLAY_COLUMNS_PANEL_DATA, { | 352 | [DISPLAY_COLUMNS_PANEL_DATA, { |
445 | columns, | 353 | columns, |
446 | columnsUpdated: (newColumns) => { | 354 | columnsUpdated: (newColumns) => { |
447 | - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.label); | 355 | + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); |
448 | this.displayedColumns.push('actions'); | 356 | this.displayedColumns.push('actions'); |
449 | } | 357 | } |
450 | } as DisplayColumnsPanelData], | 358 | } as DisplayColumnsPanelData], |
@@ -485,24 +393,27 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -485,24 +393,27 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
485 | } else { | 393 | } else { |
486 | this.pageLink.page = 0; | 394 | this.pageLink.page = 0; |
487 | } | 395 | } |
488 | - this.pageLink.sortOrder.property = this.sort.active; | 396 | + this.pageLink.sortOrder.property = fromEntityColumnDef(this.sort.active, this.columns); |
489 | this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; | 397 | this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; |
490 | this.entityDatasource.loadEntities(this.pageLink); | 398 | this.entityDatasource.loadEntities(this.pageLink); |
491 | this.ctx.detectChanges(); | 399 | this.ctx.detectChanges(); |
492 | } | 400 | } |
493 | 401 | ||
494 | - public trackByColumnLabel(index, column: EntityColumn) { | ||
495 | - return column.label; | 402 | + public trackByColumnDef(index, column: EntityColumn) { |
403 | + return column.def; | ||
496 | } | 404 | } |
497 | 405 | ||
498 | public headerStyle(key: EntityColumn): any { | 406 | public headerStyle(key: EntityColumn): any { |
499 | - return this.widthStyle(key); | 407 | + const columnWidth = this.columnWidth[key.def]; |
408 | + return { | ||
409 | + width: columnWidth | ||
410 | + } | ||
500 | } | 411 | } |
501 | 412 | ||
502 | public cellStyle(entity: EntityData, key: EntityColumn): any { | 413 | public cellStyle(entity: EntityData, key: EntityColumn): any { |
503 | let style: any = {}; | 414 | let style: any = {}; |
504 | if (entity && key) { | 415 | if (entity && key) { |
505 | - const styleInfo = this.stylesInfo[key.label]; | 416 | + const styleInfo = this.stylesInfo[key.def]; |
506 | const value = getEntityValue(entity, key); | 417 | const value = getEntityValue(entity, key); |
507 | if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { | 418 | if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { |
508 | try { | 419 | try { |
@@ -514,20 +425,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -514,20 +425,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
514 | style = this.defaultStyle(key, value); | 425 | style = this.defaultStyle(key, value); |
515 | } | 426 | } |
516 | } | 427 | } |
517 | - const widthStyle = this.widthStyle(key); | ||
518 | - style = {...style, ...widthStyle}; | ||
519 | - return style; | ||
520 | - } | ||
521 | - | ||
522 | - private widthStyle(key: EntityColumn): any { | ||
523 | - let style: any = {}; | ||
524 | - const columnWidth = this.columnWidth[key.label]; | ||
525 | - if (columnWidth !== "0px") { | ||
526 | - style.minWidth = columnWidth; | 428 | + if (!style.width) { |
429 | + const columnWidth = this.columnWidth[key.def]; | ||
527 | style.width = columnWidth; | 430 | style.width = columnWidth; |
528 | - } else { | ||
529 | - style.minWidth = "auto"; | ||
530 | - style.width = "auto"; | ||
531 | } | 431 | } |
532 | return style; | 432 | return style; |
533 | } | 433 | } |
@@ -535,7 +435,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | @@ -535,7 +435,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni | ||
535 | public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { | 435 | public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { |
536 | let strContent = ''; | 436 | let strContent = ''; |
537 | if (entity && key) { | 437 | if (entity && key) { |
538 | - const contentInfo = this.contentsInfo[key.label]; | 438 | + const contentInfo = this.contentsInfo[key.def]; |
539 | const value = getEntityValue(entity, key); | 439 | const value = getEntityValue(entity, key); |
540 | if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { | 440 | if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { |
541 | if (isDefined(value)) { | 441 | if (isDefined(value)) { |
@@ -15,7 +15,28 @@ | @@ -15,7 +15,28 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import { EntityId } from '@shared/models/id/entity-id'; | 17 | import { EntityId } from '@shared/models/id/entity-id'; |
18 | -import { DataKey } from '@shared/models/widget.models'; | 18 | +import { DataKey, WidgetConfig } from '@shared/models/widget.models'; |
19 | +import { getDescendantProp, isDefined } from '@core/utils'; | ||
20 | +import { alarmFields, AlarmInfo } from '@shared/models/alarm.models'; | ||
21 | +import * as tinycolor_ from 'tinycolor2'; | ||
22 | + | ||
23 | +const tinycolor = tinycolor_; | ||
24 | + | ||
25 | +export interface TableWidgetSettings { | ||
26 | + enableSearch: boolean; | ||
27 | + enableSelectColumnDisplay: boolean; | ||
28 | + displayPagination: boolean; | ||
29 | + defaultPageSize: number; | ||
30 | + defaultSortOrder: string; | ||
31 | +} | ||
32 | + | ||
33 | +export interface TableWidgetDataKeySettings { | ||
34 | + columnWidth: string; | ||
35 | + useCellStyleFunction: boolean; | ||
36 | + cellStyleFunction: string; | ||
37 | + useCellContentFunction: boolean; | ||
38 | + cellContentFunction: string; | ||
39 | +} | ||
19 | 40 | ||
20 | export interface EntityData { | 41 | export interface EntityData { |
21 | id: EntityId; | 42 | id: EntityId; |
@@ -25,12 +46,13 @@ export interface EntityData { | @@ -25,12 +46,13 @@ export interface EntityData { | ||
25 | } | 46 | } |
26 | 47 | ||
27 | export interface EntityColumn extends DataKey { | 48 | export interface EntityColumn extends DataKey { |
49 | + def: string; | ||
28 | title: string; | 50 | title: string; |
29 | } | 51 | } |
30 | 52 | ||
31 | export interface DisplayColumn { | 53 | export interface DisplayColumn { |
32 | title: string; | 54 | title: string; |
33 | - label: string; | 55 | + def: string; |
34 | display: boolean; | 56 | display: boolean; |
35 | } | 57 | } |
36 | 58 | ||
@@ -46,10 +68,186 @@ export interface CellStyleInfo { | @@ -46,10 +68,186 @@ export interface CellStyleInfo { | ||
46 | cellStyleFunction?: Function; | 68 | cellStyleFunction?: Function; |
47 | } | 69 | } |
48 | 70 | ||
71 | +export function findColumnProperty(searchProperty: string, searchValue: string, columnProperty: string, columns: EntityColumn[]): string { | ||
72 | + let res = searchValue; | ||
73 | + const column = columns.find(column => column[searchProperty] === searchValue); | ||
74 | + if (column) { | ||
75 | + res = column[columnProperty]; | ||
76 | + } | ||
77 | + return res; | ||
78 | +} | ||
79 | + | ||
80 | +export function toEntityColumnDef(label: string, columns: EntityColumn[]): string { | ||
81 | + return findColumnProperty('label', label, 'def', columns); | ||
82 | +} | ||
83 | + | ||
84 | +export function fromEntityColumnDef(def: string, columns: EntityColumn[]): string { | ||
85 | + return findColumnProperty('def', def, 'label', columns); | ||
86 | +} | ||
87 | + | ||
88 | +export function toAlarmColumnDef(name: string, columns: EntityColumn[]): string { | ||
89 | + return findColumnProperty('name', name, 'def', columns); | ||
90 | +} | ||
91 | + | ||
92 | +export function fromAlarmColumnDef(def: string, columns: EntityColumn[]): string { | ||
93 | + return findColumnProperty('def', def, 'name', columns); | ||
94 | +} | ||
95 | + | ||
49 | export function getEntityValue(entity: any, key: DataKey): any { | 96 | export function getEntityValue(entity: any, key: DataKey): any { |
50 | return getDescendantProp(entity, key.label); | 97 | return getDescendantProp(entity, key.label); |
51 | } | 98 | } |
52 | 99 | ||
53 | -export function getDescendantProp(obj: any, path: string): any { | ||
54 | - return path.split('.').reduce((acc, part) => acc && acc[part], obj) | 100 | +export function getAlarmValue(alarm: AlarmInfo, key: EntityColumn) { |
101 | + const alarmField = alarmFields[key.name]; | ||
102 | + if (alarmField) { | ||
103 | + return getDescendantProp(alarm, alarmField.value); | ||
104 | + } else { | ||
105 | + return getDescendantProp(alarm, key.name); | ||
106 | + } | ||
107 | +} | ||
108 | + | ||
109 | +export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellStyleInfo { | ||
110 | + let cellStyleFunction: Function = null; | ||
111 | + let useCellStyleFunction = false; | ||
112 | + | ||
113 | + if (keySettings.useCellStyleFunction === true) { | ||
114 | + if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { | ||
115 | + try { | ||
116 | + cellStyleFunction = new Function('value', keySettings.cellStyleFunction); | ||
117 | + useCellStyleFunction = true; | ||
118 | + } catch (e) { | ||
119 | + cellStyleFunction = null; | ||
120 | + useCellStyleFunction = false; | ||
121 | + } | ||
122 | + } | ||
123 | + } | ||
124 | + return { | ||
125 | + useCellStyleFunction, | ||
126 | + cellStyleFunction | ||
127 | + }; | ||
128 | +} | ||
129 | + | ||
130 | +export function getCellContentInfo(keySettings: TableWidgetDataKeySettings, ...args: string[]): CellContentInfo { | ||
131 | + let cellContentFunction: Function = null; | ||
132 | + let useCellContentFunction = false; | ||
133 | + | ||
134 | + if (keySettings.useCellContentFunction === true) { | ||
135 | + if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { | ||
136 | + try { | ||
137 | + cellContentFunction = new Function(...args, keySettings.cellContentFunction); | ||
138 | + useCellContentFunction = true; | ||
139 | + } catch (e) { | ||
140 | + cellContentFunction = null; | ||
141 | + useCellContentFunction = false; | ||
142 | + } | ||
143 | + } | ||
144 | + } | ||
145 | + return { | ||
146 | + cellContentFunction, | ||
147 | + useCellContentFunction | ||
148 | + }; | ||
149 | +} | ||
150 | + | ||
151 | +export function getColumnWidth(keySettings: TableWidgetDataKeySettings): string { | ||
152 | + return isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; | ||
153 | +} | ||
154 | + | ||
155 | +export function constructTableCssString(widgetConfig: WidgetConfig): string { | ||
156 | + const origColor = widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; | ||
157 | + const origBackgroundColor = widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; | ||
158 | + const currentEntityColor = 'rgba(221, 221, 221, 0.65)'; | ||
159 | + const currentEntityStickyColor = tinycolor.mix(origBackgroundColor, | ||
160 | + tinycolor(currentEntityColor).setAlpha(1), 65).toRgbString(); | ||
161 | + const selectedColor = 'rgba(221, 221, 221, 0.5)'; | ||
162 | + const selectedStickyColor = tinycolor.mix(origBackgroundColor, | ||
163 | + tinycolor(selectedColor).setAlpha(1), 50).toRgbString(); | ||
164 | + const hoverColor = 'rgba(221, 221, 221, 0.3)'; | ||
165 | + const hoverStickyColor = tinycolor.mix(origBackgroundColor, | ||
166 | + tinycolor(hoverColor).setAlpha(1), 30).toRgbString(); | ||
167 | + const defaultColor = tinycolor(origColor); | ||
168 | + const mdDark = defaultColor.setAlpha(0.87).toRgbString(); | ||
169 | + const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); | ||
170 | + const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); | ||
171 | + const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); | ||
172 | + | ||
173 | + const cssString = | ||
174 | + '.mat-input-element::placeholder {\n' + | ||
175 | + ' color: ' + mdDarkSecondary + ';\n'+ | ||
176 | + '}\n' + | ||
177 | + '.mat-input-element::-moz-placeholder {\n' + | ||
178 | + ' color: ' + mdDarkSecondary + ';\n'+ | ||
179 | + '}\n' + | ||
180 | + '.mat-input-element::-webkit-input-placeholder {\n' + | ||
181 | + ' color: ' + mdDarkSecondary + ';\n'+ | ||
182 | + '}\n' + | ||
183 | + '.mat-input-element:-ms-input-placeholder {\n' + | ||
184 | + ' color: ' + mdDarkSecondary + ';\n'+ | ||
185 | + '}\n' + | ||
186 | + 'mat-toolbar.mat-table-toolbar {\n'+ | ||
187 | + 'color: ' + mdDark + ';\n'+ | ||
188 | + '}\n'+ | ||
189 | + 'mat-toolbar.mat-table-toolbar:not([color="primary"]) button.mat-icon-button mat-icon {\n'+ | ||
190 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
191 | + '}\n'+ | ||
192 | + '.mat-table .mat-header-row {\n'+ | ||
193 | + 'background-color: ' + origBackgroundColor + ';\n'+ | ||
194 | + '}\n'+ | ||
195 | + '.mat-table .mat-header-cell {\n'+ | ||
196 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
197 | + '}\n'+ | ||
198 | + '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ | ||
199 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
200 | + '}\n'+ | ||
201 | + '.mat-table .mat-cell, .mat-table .mat-header-cell {\n'+ | ||
202 | + 'border-bottom-color: '+mdDarkDivider+';\n'+ | ||
203 | + '}\n'+ | ||
204 | + '.mat-table .mat-cell .mat-checkbox-frame, .mat-table .mat-header-cell .mat-checkbox-frame {\n'+ | ||
205 | + 'border-color: '+mdDarkSecondary+';\n'+ | ||
206 | + '}\n'+ | ||
207 | + '.mat-table .mat-row .mat-cell.mat-table-sticky {\n'+ | ||
208 | + 'transition: background-color .2s;\n'+ | ||
209 | + '}\n'+ | ||
210 | + '.mat-table .mat-row.tb-current-entity {\n'+ | ||
211 | + 'background-color: ' + currentEntityColor + ';\n'+ | ||
212 | + '}\n'+ | ||
213 | + '.mat-table .mat-row.tb-current-entity .mat-cell.mat-table-sticky {\n'+ | ||
214 | + 'background-color: ' + currentEntityStickyColor + ';\n'+ | ||
215 | + '}\n'+ | ||
216 | + '.mat-table .mat-row:hover:not(.tb-current-entity) {\n'+ | ||
217 | + 'background-color: ' + hoverColor + ';\n'+ | ||
218 | + '}\n'+ | ||
219 | + '.mat-table .mat-row:hover:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ | ||
220 | + 'background-color: ' + hoverStickyColor + ';\n'+ | ||
221 | + '}\n'+ | ||
222 | + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) {\n'+ | ||
223 | + 'background-color: ' + selectedColor + ';\n'+ | ||
224 | + '}\n'+ | ||
225 | + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ | ||
226 | + 'background-color: ' + selectedStickyColor + ';\n'+ | ||
227 | + '}\n'+ | ||
228 | + '.mat-table .mat-row .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ | ||
229 | + 'background-color: ' + origBackgroundColor + ';\n'+ | ||
230 | + '}\n'+ | ||
231 | + '.mat-table .mat-cell {\n'+ | ||
232 | + 'color: ' + mdDark + ';\n'+ | ||
233 | + '}\n'+ | ||
234 | + '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ | ||
235 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
236 | + '}\n'+ | ||
237 | + '.mat-divider {\n'+ | ||
238 | + 'border-top-color: ' + mdDarkDivider + ';\n'+ | ||
239 | + '}\n'+ | ||
240 | + '.mat-paginator {\n'+ | ||
241 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
242 | + '}\n'+ | ||
243 | + '.mat-paginator button.mat-icon-button {\n'+ | ||
244 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
245 | + '}\n'+ | ||
246 | + '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ | ||
247 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
248 | + '}\n'+ | ||
249 | + '.mat-paginator .mat-select-value {\n'+ | ||
250 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
251 | + '}'; | ||
252 | + return cssString; | ||
55 | } | 253 | } |
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 | + .tb-table-widget { | ||
18 | + .mat-table, .mat-paginator, mat-toolbar.mat-table-toolbar:not([color="primary"]) { | ||
19 | + background: transparent; | ||
20 | + } | ||
21 | + mat-toolbar { | ||
22 | + height: 39px; | ||
23 | + max-height: 39px; | ||
24 | + .mat-toolbar-tools { | ||
25 | + height: 39px; | ||
26 | + max-height: 39px; | ||
27 | + } | ||
28 | + } | ||
29 | + .table-container { | ||
30 | + overflow: auto; | ||
31 | + } | ||
32 | + | ||
33 | + .mat-row:not(.mat-row-select), .mat-header-row:not(.mat-row-select) { | ||
34 | + mat-cell:nth-child(n+2):nth-last-child(n+2), mat-footer-cell:nth-child(n+2):nth-last-child(n+2), mat-header-cell:nth-child(n+2):nth-last-child(n+2) { | ||
35 | + padding: 0px 5px; | ||
36 | + } | ||
37 | + } | ||
38 | + | ||
39 | + .mat-row.mat-row-select, .mat-header-row.mat-row-select { | ||
40 | + mat-cell:nth-child(2), mat-footer-cell:nth-child(2), mat-header-cell:nth-child(2) { | ||
41 | + padding: 0px 5px; | ||
42 | + } | ||
43 | + mat-cell:nth-child(n+3):nth-last-child(n+2), mat-footer-cell:nth-child(n+3):nth-last-child(n+2), mat-header-cell:nth-child(n+3):nth-last-child(n+2) { | ||
44 | + padding: 0px 5px; | ||
45 | + } | ||
46 | + } | ||
47 | + } | ||
48 | +} | ||
49 | + | ||
50 | +:host-context(.tb-has-timewindow) { | ||
51 | + .tb-table-widget { | ||
52 | + mat-toolbar { | ||
53 | + height: 65px; | ||
54 | + max-height: 65px; | ||
55 | + .mat-toolbar-tools { | ||
56 | + height: 65px; | ||
57 | + max-height: 65px; | ||
58 | + } | ||
59 | + } | ||
60 | + } | ||
61 | +} |
@@ -21,6 +21,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail | @@ -21,6 +21,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail | ||
21 | import { LegendComponent } from '@home/components/widget/legend.component'; | 21 | import { LegendComponent } from '@home/components/widget/legend.component'; |
22 | import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; | 22 | import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; |
23 | import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; | 23 | import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; |
24 | +import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component'; | ||
24 | 25 | ||
25 | @NgModule({ | 26 | @NgModule({ |
26 | entryComponents: [ | 27 | entryComponents: [ |
@@ -29,14 +30,16 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa | @@ -29,14 +30,16 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa | ||
29 | declarations: | 30 | declarations: |
30 | [ | 31 | [ |
31 | DisplayColumnsPanelComponent, | 32 | DisplayColumnsPanelComponent, |
32 | - EntitiesTableWidgetComponent | 33 | + EntitiesTableWidgetComponent, |
34 | + AlarmsTableWidgetComponent | ||
33 | ], | 35 | ], |
34 | imports: [ | 36 | imports: [ |
35 | CommonModule, | 37 | CommonModule, |
36 | SharedModule | 38 | SharedModule |
37 | ], | 39 | ], |
38 | exports: [ | 40 | exports: [ |
39 | - EntitiesTableWidgetComponent | 41 | + EntitiesTableWidgetComponent, |
42 | + AlarmsTableWidgetComponent | ||
40 | ] | 43 | ] |
41 | }) | 44 | }) |
42 | export class WidgetComponentsModule { } | 45 | export class WidgetComponentsModule { } |
@@ -365,6 +365,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | @@ -365,6 +365,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | ||
365 | this.handleWidgetException(e); | 365 | this.handleWidgetException(e); |
366 | } | 366 | } |
367 | } | 367 | } |
368 | + this.widgetContext.destroyed = true; | ||
368 | this.destroyDynamicWidgetComponent(); | 369 | this.destroyDynamicWidgetComponent(); |
369 | } | 370 | } |
370 | 371 |
@@ -75,7 +75,7 @@ export class BaseEntityTableColumn<T extends BaseData<HasId>> { | @@ -75,7 +75,7 @@ export class BaseEntityTableColumn<T extends BaseData<HasId>> { | ||
75 | constructor(public type: EntityTableColumnType, | 75 | constructor(public type: EntityTableColumnType, |
76 | public key: string, | 76 | public key: string, |
77 | public title: string, | 77 | public title: string, |
78 | - public maxWidth: string = '100%', | 78 | + public width: string = '0px', |
79 | public sortable: boolean = true) { | 79 | public sortable: boolean = true) { |
80 | } | 80 | } |
81 | } | 81 | } |
@@ -83,13 +83,14 @@ export class BaseEntityTableColumn<T extends BaseData<HasId>> { | @@ -83,13 +83,14 @@ export class BaseEntityTableColumn<T extends BaseData<HasId>> { | ||
83 | export class EntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> { | 83 | export class EntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> { |
84 | constructor(public key: string, | 84 | constructor(public key: string, |
85 | public title: string, | 85 | public title: string, |
86 | - public maxWidth: string = '100%', | 86 | + public width: string = '0px', |
87 | public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property], | 87 | public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property], |
88 | public cellStyleFunction: CellStyleFunction<T> = () => ({}), | 88 | public cellStyleFunction: CellStyleFunction<T> = () => ({}), |
89 | public sortable: boolean = true, | 89 | public sortable: boolean = true, |
90 | public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({}), | 90 | public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({}), |
91 | - public cellTooltipFunction: CellTooltipFunction<T> = () => undefined) { | ||
92 | - super('content', key, title, maxWidth, sortable); | 91 | + public cellTooltipFunction: CellTooltipFunction<T> = () => undefined, |
92 | + public isNumberColumn: boolean = false) { | ||
93 | + super('content', key, title, width, sortable); | ||
93 | } | 94 | } |
94 | } | 95 | } |
95 | 96 | ||
@@ -97,8 +98,8 @@ export class EntityActionTableColumn<T extends BaseData<HasId>> extends BaseEnti | @@ -97,8 +98,8 @@ export class EntityActionTableColumn<T extends BaseData<HasId>> extends BaseEnti | ||
97 | constructor(public key: string, | 98 | constructor(public key: string, |
98 | public title: string, | 99 | public title: string, |
99 | public actionDescriptor: CellActionDescriptor<T>, | 100 | public actionDescriptor: CellActionDescriptor<T>, |
100 | - public maxWidth: string = '100%') { | ||
101 | - super('action', key, title, maxWidth, false); | 101 | + public width: string = '0px') { |
102 | + super('action', key, title, width, false); | ||
102 | } | 103 | } |
103 | } | 104 | } |
104 | 105 | ||
@@ -106,12 +107,12 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl | @@ -106,12 +107,12 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl | ||
106 | constructor(key: string, | 107 | constructor(key: string, |
107 | title: string, | 108 | title: string, |
108 | datePipe: DatePipe, | 109 | datePipe: DatePipe, |
109 | - maxWidth: string = '100%', | 110 | + width: string = '0px', |
110 | dateFormat: string = 'yyyy-MM-dd HH:mm:ss', | 111 | dateFormat: string = 'yyyy-MM-dd HH:mm:ss', |
111 | cellStyleFunction: CellStyleFunction<T> = () => ({})) { | 112 | cellStyleFunction: CellStyleFunction<T> = () => ({})) { |
112 | super(key, | 113 | super(key, |
113 | title, | 114 | title, |
114 | - maxWidth, | 115 | + width, |
115 | (entity, property) => datePipe.transform(entity[property], dateFormat), | 116 | (entity, property) => datePipe.transform(entity[property], dateFormat), |
116 | cellStyleFunction); | 117 | cellStyleFunction); |
117 | } | 118 | } |
@@ -114,19 +114,24 @@ export class WidgetContext { | @@ -114,19 +114,24 @@ export class WidgetContext { | ||
114 | private _changeDetector: ChangeDetectorRef; | 114 | private _changeDetector: ChangeDetectorRef; |
115 | 115 | ||
116 | detectChanges(updateWidgetParams: boolean = false) { | 116 | detectChanges(updateWidgetParams: boolean = false) { |
117 | - if (updateWidgetParams) { | ||
118 | - this.dashboardWidget.updateWidgetParams(); | 117 | + if (!this.destroyed) { |
118 | + if (updateWidgetParams) { | ||
119 | + this.dashboardWidget.updateWidgetParams(); | ||
120 | + } | ||
121 | + this._changeDetector.detectChanges(); | ||
119 | } | 122 | } |
120 | - this._changeDetector.detectChanges(); | ||
121 | } | 123 | } |
122 | 124 | ||
123 | updateWidgetParams() { | 125 | updateWidgetParams() { |
124 | - setTimeout(() => { | ||
125 | - this.dashboardWidget.updateWidgetParams(); | ||
126 | - }, 0); | 126 | + if (!this.destroyed) { |
127 | + setTimeout(() => { | ||
128 | + this.dashboardWidget.updateWidgetParams(); | ||
129 | + }, 0); | ||
130 | + } | ||
127 | } | 131 | } |
128 | 132 | ||
129 | inited = false; | 133 | inited = false; |
134 | + destroyed = false; | ||
130 | 135 | ||
131 | subscriptions: {[id: string]: IWidgetSubscription} = {}; | 136 | subscriptions: {[id: string]: IWidgetSubscription} = {}; |
132 | defaultSubscription: IWidgetSubscription = null; | 137 | defaultSubscription: IWidgetSubscription = null; |
@@ -146,12 +146,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse | @@ -146,12 +146,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse | ||
146 | configureColumns(assetScope: string): Array<EntityTableColumn<AssetInfo>> { | 146 | configureColumns(assetScope: string): Array<EntityTableColumn<AssetInfo>> { |
147 | const columns: Array<EntityTableColumn<AssetInfo>> = [ | 147 | const columns: Array<EntityTableColumn<AssetInfo>> = [ |
148 | new DateEntityTableColumn<AssetInfo>('createdTime', 'asset.created-time', this.datePipe, '150px'), | 148 | new DateEntityTableColumn<AssetInfo>('createdTime', 'asset.created-time', this.datePipe, '150px'), |
149 | - new EntityTableColumn<AssetInfo>('name', 'asset.name'), | ||
150 | - new EntityTableColumn<AssetInfo>('type', 'asset.asset-type'), | 149 | + new EntityTableColumn<AssetInfo>('name', 'asset.name', '33%'), |
150 | + new EntityTableColumn<AssetInfo>('type', 'asset.asset-type', '33%'), | ||
151 | ]; | 151 | ]; |
152 | if (assetScope === 'tenant') { | 152 | if (assetScope === 'tenant') { |
153 | columns.push( | 153 | columns.push( |
154 | - new EntityTableColumn<AssetInfo>('customerTitle', 'customer.customer'), | 154 | + new EntityTableColumn<AssetInfo>('customerTitle', 'customer.customer', '33%'), |
155 | new EntityTableColumn<AssetInfo>('customerIsPublic', 'asset.public', '60px', | 155 | new EntityTableColumn<AssetInfo>('customerIsPublic', 'asset.public', '60px', |
156 | entity => { | 156 | entity => { |
157 | return checkBoxCell(entity.customerIsPublic); | 157 | return checkBoxCell(entity.customerIsPublic); |
@@ -55,10 +55,10 @@ export class CustomersTableConfigResolver implements Resolve<EntityTableConfig<C | @@ -55,10 +55,10 @@ export class CustomersTableConfigResolver implements Resolve<EntityTableConfig<C | ||
55 | 55 | ||
56 | this.config.columns.push( | 56 | this.config.columns.push( |
57 | new DateEntityTableColumn<Customer>('createdTime', 'customer.created-time', this.datePipe, '150px'), | 57 | new DateEntityTableColumn<Customer>('createdTime', 'customer.created-time', this.datePipe, '150px'), |
58 | - new EntityTableColumn<Customer>('title', 'customer.title'), | ||
59 | - new EntityTableColumn<Customer>('email', 'contact.email'), | ||
60 | - new EntityTableColumn<Customer>('country', 'contact.country'), | ||
61 | - new EntityTableColumn<Customer>('city', 'contact.city') | 58 | + new EntityTableColumn<Customer>('title', 'customer.title', '25%'), |
59 | + new EntityTableColumn<Customer>('email', 'contact.email', '25%'), | ||
60 | + new EntityTableColumn<Customer>('country', 'contact.country', '25%'), | ||
61 | + new EntityTableColumn<Customer>('city', 'contact.city', '25%') | ||
62 | ); | 62 | ); |
63 | 63 | ||
64 | this.config.cellActionDescriptors.push( | 64 | this.config.cellActionDescriptors.push( |
@@ -143,12 +143,12 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig< | @@ -143,12 +143,12 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig< | ||
143 | configureColumns(dashboardScope: string): Array<EntityTableColumn<DashboardInfo>> { | 143 | configureColumns(dashboardScope: string): Array<EntityTableColumn<DashboardInfo>> { |
144 | const columns: Array<EntityTableColumn<DashboardInfo>> = [ | 144 | const columns: Array<EntityTableColumn<DashboardInfo>> = [ |
145 | new DateEntityTableColumn<DashboardInfo>('createdTime', 'dashboard.created-time', this.datePipe, '150px'), | 145 | new DateEntityTableColumn<DashboardInfo>('createdTime', 'dashboard.created-time', this.datePipe, '150px'), |
146 | - new EntityTableColumn<DashboardInfo>('title', 'dashboard.title') | 146 | + new EntityTableColumn<DashboardInfo>('title', 'dashboard.title', '50%') |
147 | ]; | 147 | ]; |
148 | if (dashboardScope === 'tenant') { | 148 | if (dashboardScope === 'tenant') { |
149 | columns.push( | 149 | columns.push( |
150 | new EntityTableColumn<DashboardInfo>('customersTitle', 'dashboard.assignedToCustomers', | 150 | new EntityTableColumn<DashboardInfo>('customersTitle', 'dashboard.assignedToCustomers', |
151 | - '100%', entity => { | 151 | + '50%', entity => { |
152 | return getDashboardAssignedCustomersText(entity); | 152 | return getDashboardAssignedCustomersText(entity); |
153 | }, () => ({}), false), | 153 | }, () => ({}), false), |
154 | new EntityTableColumn<DashboardInfo>('dashboardIsPublic', 'dashboard.public', '60px', | 154 | new EntityTableColumn<DashboardInfo>('dashboardIsPublic', 'dashboard.public', '60px', |
@@ -150,13 +150,13 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -150,13 +150,13 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
150 | configureColumns(deviceScope: string): Array<EntityTableColumn<DeviceInfo>> { | 150 | configureColumns(deviceScope: string): Array<EntityTableColumn<DeviceInfo>> { |
151 | const columns: Array<EntityTableColumn<DeviceInfo>> = [ | 151 | const columns: Array<EntityTableColumn<DeviceInfo>> = [ |
152 | new DateEntityTableColumn<DeviceInfo>('createdTime', 'device.created-time', this.datePipe, '150px'), | 152 | new DateEntityTableColumn<DeviceInfo>('createdTime', 'device.created-time', this.datePipe, '150px'), |
153 | - new EntityTableColumn<DeviceInfo>('name', 'device.name'), | ||
154 | - new EntityTableColumn<DeviceInfo>('type', 'device.device-type'), | ||
155 | - new EntityTableColumn<DeviceInfo>('label', 'device.label') | 153 | + new EntityTableColumn<DeviceInfo>('name', 'device.name', '25%'), |
154 | + new EntityTableColumn<DeviceInfo>('type', 'device.device-type', '25%'), | ||
155 | + new EntityTableColumn<DeviceInfo>('label', 'device.label', '25%') | ||
156 | ]; | 156 | ]; |
157 | if (deviceScope === 'tenant') { | 157 | if (deviceScope === 'tenant') { |
158 | columns.push( | 158 | columns.push( |
159 | - new EntityTableColumn<DeviceInfo>('customerTitle', 'customer.customer'), | 159 | + new EntityTableColumn<DeviceInfo>('customerTitle', 'customer.customer', '25%'), |
160 | new EntityTableColumn<DeviceInfo>('customerIsPublic', 'device.public', '60px', | 160 | new EntityTableColumn<DeviceInfo>('customerIsPublic', 'device.public', '60px', |
161 | entity => { | 161 | entity => { |
162 | return checkBoxCell(entity.customerIsPublic); | 162 | return checkBoxCell(entity.customerIsPublic); |
@@ -147,12 +147,12 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig | @@ -147,12 +147,12 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig | ||
147 | configureColumns(entityViewScope: string): Array<EntityTableColumn<EntityViewInfo>> { | 147 | configureColumns(entityViewScope: string): Array<EntityTableColumn<EntityViewInfo>> { |
148 | const columns: Array<EntityTableColumn<EntityViewInfo>> = [ | 148 | const columns: Array<EntityTableColumn<EntityViewInfo>> = [ |
149 | new DateEntityTableColumn<EntityViewInfo>('createdTime', 'entity-view.created-time', this.datePipe, '150px'), | 149 | new DateEntityTableColumn<EntityViewInfo>('createdTime', 'entity-view.created-time', this.datePipe, '150px'), |
150 | - new EntityTableColumn<EntityViewInfo>('name', 'entity-view.name'), | ||
151 | - new EntityTableColumn<EntityViewInfo>('type', 'entity-view.entity-view-type'), | 150 | + new EntityTableColumn<EntityViewInfo>('name', 'entity-view.name', '33%'), |
151 | + new EntityTableColumn<EntityViewInfo>('type', 'entity-view.entity-view-type', '33%'), | ||
152 | ]; | 152 | ]; |
153 | if (entityViewScope === 'tenant') { | 153 | if (entityViewScope === 'tenant') { |
154 | columns.push( | 154 | columns.push( |
155 | - new EntityTableColumn<EntityViewInfo>('customerTitle', 'customer.customer'), | 155 | + new EntityTableColumn<EntityViewInfo>('customerTitle', 'customer.customer', '33%'), |
156 | new EntityTableColumn<EntityViewInfo>('customerIsPublic', 'entity-view.public', '60px', | 156 | new EntityTableColumn<EntityViewInfo>('customerIsPublic', 'entity-view.public', '60px', |
157 | entity => { | 157 | entity => { |
158 | return checkBoxCell(entity.customerIsPublic); | 158 | return checkBoxCell(entity.customerIsPublic); |
@@ -55,10 +55,10 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten | @@ -55,10 +55,10 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten | ||
55 | 55 | ||
56 | this.config.columns.push( | 56 | this.config.columns.push( |
57 | new DateEntityTableColumn<Tenant>('createdTime', 'tenant.created-time', this.datePipe, '150px'), | 57 | new DateEntityTableColumn<Tenant>('createdTime', 'tenant.created-time', this.datePipe, '150px'), |
58 | - new EntityTableColumn<Tenant>('title', 'tenant.title'), | ||
59 | - new EntityTableColumn<Tenant>('email', 'contact.email'), | ||
60 | - new EntityTableColumn<Tenant>('country', 'contact.country'), | ||
61 | - new EntityTableColumn<Tenant>('city', 'contact.city') | 58 | + new EntityTableColumn<Tenant>('title', 'tenant.title', '25%'), |
59 | + new EntityTableColumn<Tenant>('email', 'contact.email', '25%'), | ||
60 | + new EntityTableColumn<Tenant>('country', 'contact.country', '25%'), | ||
61 | + new EntityTableColumn<Tenant>('city', 'contact.city', '25%') | ||
62 | ); | 62 | ); |
63 | 63 | ||
64 | this.config.cellActionDescriptors.push( | 64 | this.config.cellActionDescriptors.push( |
@@ -90,9 +90,9 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User> | @@ -90,9 +90,9 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User> | ||
90 | 90 | ||
91 | this.config.columns.push( | 91 | this.config.columns.push( |
92 | new DateEntityTableColumn<User>('createdTime', 'user.created-time', this.datePipe, '150px'), | 92 | new DateEntityTableColumn<User>('createdTime', 'user.created-time', this.datePipe, '150px'), |
93 | - new EntityTableColumn<User>('firstName', 'user.first-name'), | ||
94 | - new EntityTableColumn<User>('lastName', 'user.last-name'), | ||
95 | - new EntityTableColumn<User>('email', 'user.email') | 93 | + new EntityTableColumn<User>('firstName', 'user.first-name', '33%'), |
94 | + new EntityTableColumn<User>('lastName', 'user.last-name', '33%'), | ||
95 | + new EntityTableColumn<User>('email', 'user.email', '33%') | ||
96 | ); | 96 | ); |
97 | 97 | ||
98 | this.config.deleteEnabled = user => user && user.id && user.id.id !== this.authUser.id.id; | 98 | this.config.deleteEnabled = user => user && user.id && user.id.id !== this.authUser.id.id; |
@@ -58,7 +58,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | @@ -58,7 +58,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | ||
58 | 58 | ||
59 | this.config.columns.push( | 59 | this.config.columns.push( |
60 | new DateEntityTableColumn<WidgetsBundle>('createdTime', 'widgets-bundle.created-time', this.datePipe, '150px'), | 60 | new DateEntityTableColumn<WidgetsBundle>('createdTime', 'widgets-bundle.created-time', this.datePipe, '150px'), |
61 | - new EntityTableColumn<WidgetsBundle>('title', 'widgets-bundle.title'), | 61 | + new EntityTableColumn<WidgetsBundle>('title', 'widgets-bundle.title', '100%'), |
62 | new EntityTableColumn<WidgetsBundle>('tenantId', 'widgets-bundle.system', '60px', | 62 | new EntityTableColumn<WidgetsBundle>('tenantId', 'widgets-bundle.system', '60px', |
63 | entity => { | 63 | entity => { |
64 | return checkBoxCell(entity.tenantId.id === NULL_UUID); | 64 | return checkBoxCell(entity.tenantId.id === NULL_UUID); |
@@ -106,6 +106,7 @@ export interface AlarmInfo extends Alarm { | @@ -106,6 +106,7 @@ export interface AlarmInfo extends Alarm { | ||
106 | } | 106 | } |
107 | 107 | ||
108 | export const simulatedAlarm: AlarmInfo = { | 108 | export const simulatedAlarm: AlarmInfo = { |
109 | + id: new AlarmId(NULL_UUID), | ||
109 | tenantId: new TenantId(NULL_UUID), | 110 | tenantId: new TenantId(NULL_UUID), |
110 | createdTime: new Date().getTime(), | 111 | createdTime: new Date().getTime(), |
111 | startTs: new Date().getTime(), | 112 | startTs: new Date().getTime(), |
@@ -16,6 +16,7 @@ | @@ -16,6 +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 | 20 | ||
20 | export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean; | 21 | export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean; |
21 | 22 | ||
@@ -69,8 +70,8 @@ export class PageLink { | @@ -69,8 +70,8 @@ export class PageLink { | ||
69 | public sort(item1: any, item2: any): number { | 70 | public sort(item1: any, item2: any): number { |
70 | if (this.sortOrder) { | 71 | if (this.sortOrder) { |
71 | const property = this.sortOrder.property; | 72 | const property = this.sortOrder.property; |
72 | - const item1Value = item1[property]; | ||
73 | - const item2Value = item2[property]; | 73 | + const item1Value = getDescendantProp(item1, property); |
74 | + const item2Value = getDescendantProp(item2, property); | ||
74 | let result = 0; | 75 | let result = 0; |
75 | if (item1Value !== item2Value) { | 76 | if (item1Value !== item2Value) { |
76 | if (typeof item1Value === 'number' && typeof item2Value === 'number') { | 77 | if (typeof item1Value === 'number' && typeof item2Value === 'number') { |
@@ -472,6 +472,16 @@ mat-label { | @@ -472,6 +472,16 @@ mat-label { | ||
472 | } | 472 | } |
473 | } | 473 | } |
474 | 474 | ||
475 | + // Material table | ||
476 | + | ||
477 | + mat-toolbar.mat-primary { | ||
478 | + button.mat-icon-button { | ||
479 | + mat-icon { | ||
480 | + color: white; | ||
481 | + } | ||
482 | + } | ||
483 | + } | ||
484 | + | ||
475 | mat-toolbar.mat-table-toolbar { | 485 | mat-toolbar.mat-table-toolbar { |
476 | background: #fff; | 486 | background: #fff; |
477 | padding: 0 24px; | 487 | padding: 0 24px; |
@@ -483,7 +493,7 @@ mat-label { | @@ -483,7 +493,7 @@ mat-label { | ||
483 | } | 493 | } |
484 | } | 494 | } |
485 | 495 | ||
486 | - mat-toolbar.mat-table-toolbar, .mat-cell { | 496 | + mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell { |
487 | button.mat-icon-button { | 497 | button.mat-icon-button { |
488 | mat-icon { | 498 | mat-icon { |
489 | color: rgba(0, 0, 0, .54); | 499 | color: rgba(0, 0, 0, .54); |
@@ -491,28 +501,40 @@ mat-label { | @@ -491,28 +501,40 @@ mat-label { | ||
491 | } | 501 | } |
492 | } | 502 | } |
493 | 503 | ||
494 | - .mat-cell { | ||
495 | - mat-icon { | ||
496 | - color: rgba(0, 0, 0, .54); | ||
497 | - } | 504 | + .mat-table { |
505 | + width: 100%; | ||
506 | + max-width: 100%; | ||
507 | + margin-bottom: 1rem; | ||
508 | + display: table; | ||
509 | + border-collapse: separate; | ||
510 | + margin: 0px; | ||
498 | } | 511 | } |
499 | 512 | ||
500 | - mat-toolbar.mat-primary { | ||
501 | - button.mat-icon-button { | ||
502 | - mat-icon { | ||
503 | - color: white; | 513 | + .mat-row, |
514 | + .mat-header-row { | ||
515 | + display: table-row; | ||
516 | + } | ||
517 | + | ||
518 | + | ||
519 | + .mat-header-row.mat-table-sticky { | ||
520 | + .mat-header-cell { | ||
521 | + position: sticky; | ||
522 | + top: 0; | ||
523 | + z-index: 10; | ||
524 | + background: inherit; | ||
525 | + &.mat-table-sticky { | ||
526 | + z-index: 11 !important; | ||
504 | } | 527 | } |
505 | } | 528 | } |
506 | } | 529 | } |
507 | 530 | ||
508 | - | ||
509 | .mat-row { | 531 | .mat-row { |
510 | transition: background-color .2s; | 532 | transition: background-color .2s; |
511 | &:hover:not(.tb-current-entity) { | 533 | &:hover:not(.tb-current-entity) { |
512 | - background-color: rgba(221, 221, 221, 0.3); | 534 | + background-color: #f4f4f4; |
513 | } | 535 | } |
514 | &.tb-current-entity { | 536 | &.tb-current-entity { |
515 | - background-color: rgba(221, 221, 221, 0.65); | 537 | + background-color: #e9e9e9; |
516 | } | 538 | } |
517 | } | 539 | } |
518 | 540 | ||
@@ -541,12 +563,20 @@ mat-label { | @@ -541,12 +563,20 @@ mat-label { | ||
541 | } | 563 | } |
542 | } | 564 | } |
543 | 565 | ||
566 | + .mat-cell, | ||
544 | .mat-header-cell { | 567 | .mat-header-cell { |
545 | - white-space: nowrap; | ||
546 | - } | ||
547 | - | ||
548 | - .mat-cell, .mat-header-cell { | ||
549 | min-width: 40px; | 568 | min-width: 40px; |
569 | + word-wrap: initial; | ||
570 | + display: table-cell; | ||
571 | + line-break: unset; | ||
572 | + width: 0px; | ||
573 | + overflow: hidden; | ||
574 | + vertical-align: middle; | ||
575 | + border-width: 0; | ||
576 | + border-bottom-width: 1px; | ||
577 | + border-bottom-color: rgba(0, 0, 0, 0.12); | ||
578 | + border-style: solid; | ||
579 | + text-overflow: ellipsis; | ||
550 | &:last-child { | 580 | &:last-child { |
551 | padding: 0 12px 0 0; | 581 | padding: 0 12px 0 0; |
552 | } | 582 | } |
@@ -561,8 +591,28 @@ mat-label { | @@ -561,8 +591,28 @@ mat-label { | ||
561 | text-overflow: ellipsis; | 591 | text-overflow: ellipsis; |
562 | white-space: nowrap; | 592 | white-space: nowrap; |
563 | } | 593 | } |
564 | - &.mat-table-sticky { | ||
565 | - background: transparent; | 594 | + } |
595 | + | ||
596 | + .mat-header-cell { | ||
597 | + white-space: nowrap; | ||
598 | + button.mat-sort-header-button { | ||
599 | + text-overflow: ellipsis; | ||
600 | + overflow: hidden; | ||
601 | + white-space: nowrap; | ||
602 | + } | ||
603 | + &.mat-number-cell { | ||
604 | + .mat-sort-header-container { | ||
605 | + justify-content: flex-end; | ||
606 | + } | ||
607 | + } | ||
608 | + } | ||
609 | + | ||
610 | + .mat-cell { | ||
611 | + &.mat-number-cell { | ||
612 | + text-align: end; | ||
613 | + } | ||
614 | + mat-icon { | ||
615 | + color: rgba(0, 0, 0, .54); | ||
566 | } | 616 | } |
567 | } | 617 | } |
568 | 618 | ||
@@ -575,6 +625,10 @@ mat-label { | @@ -575,6 +625,10 @@ mat-label { | ||
575 | height: 20px; | 625 | height: 20px; |
576 | } | 626 | } |
577 | 627 | ||
628 | + .mat-sort-header-sorted .mat-sort-header-arrow { | ||
629 | + opacity: 1 !important; | ||
630 | + } | ||
631 | + | ||
578 | .mat-toolbar-tools { | 632 | .mat-toolbar-tools { |
579 | font-size: 20px; | 633 | font-size: 20px; |
580 | letter-spacing: .005em; | 634 | letter-spacing: .005em; |