From 8ea7058da49e42439b94802515ca098e3a6d05a4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 12 Sep 2018 12:54:29 +0300 Subject: [PATCH] Alarms and Entities table widgets improvements. --- ui/src/app/api/subscription.js | 8 + ui/src/app/locale/locale.constant-en_US.json | 10 +- .../widget/lib/alarm-status-filter-panel.scss | 31 +++ .../lib/alarm-status-filter-panel.tpl.html | 28 +++ ui/src/app/widget/lib/alarms-table-widget.js | 190 ++++++++++++++++-- .../app/widget/lib/alarms-table-widget.scss | 30 +++ .../widget/lib/alarms-table-widget.tpl.html | 40 ++-- .../app/widget/lib/display-columns-panel.scss | 31 +++ .../widget/lib/display-columns-panel.tpl.html | 24 +++ .../app/widget/lib/entities-table-widget.js | 79 +++++++- .../app/widget/lib/entities-table-widget.scss | 30 +++ .../widget/lib/entities-table-widget.tpl.html | 27 ++- 12 files changed, 486 insertions(+), 42 deletions(-) create mode 100644 ui/src/app/widget/lib/alarm-status-filter-panel.scss create mode 100644 ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html create mode 100644 ui/src/app/widget/lib/display-columns-panel.scss create mode 100644 ui/src/app/widget/lib/display-columns-panel.tpl.html diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 350b1ef0b7..a66c4b1efa 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -267,6 +267,14 @@ export default class Subscription { } else { this.startWatchingTimewindow(); } + registration = this.ctx.$scope.$watch(function () { + return subscription.alarmSearchStatus; + }, function (newAlarmSearchStatus, prevAlarmSearchStatus) { + if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) { + subscription.update(); + } + }, true); + this.registrations.push(registration); } initDataSubscription() { diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 6321d63f46..2caf01331b 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -133,8 +133,13 @@ "min-polling-interval-message": "At least 1 sec polling interval is allowed.", "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Acknowledge Alarm", + "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", - "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?" + "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Clear Alarm", + "clear-alarm-text": "Are you sure you want to clear Alarm?", + "alarm-status-filter": "Alarm Status Filter" }, "alias": { "add": "Add alias", @@ -748,7 +753,8 @@ "entity-name": "Entity name", "details": "Entity details", "no-entities-prompt": "No entities found", - "no-data": "No data to display" + "no-data": "No data to display", + "columns-to-display": "Columns to Display" }, "event": { "event-type": "Event type", diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.scss b/ui/src/app/widget/lib/alarm-status-filter-panel.scss new file mode 100644 index 0000000000..4bf6eeef11 --- /dev/null +++ b/ui/src/app/widget/lib/alarm-status-filter-panel.scss @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-alarm-status-filter-panel { + min-width: 300px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + md-content { + overflow: hidden; + background-color: #fff; + } +} diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html new file mode 100644 index 0000000000..45e8414ef8 --- /dev/null +++ b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html @@ -0,0 +1,28 @@ + + + + + + + {{ ('alarm.search-status.' + searchStatus) | translate }} + + + diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js index 0696a7b873..6ae17e0459 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.js +++ b/ui/src/app/widget/lib/alarms-table-widget.js @@ -14,11 +14,15 @@ * limitations under the License. */ import './alarms-table-widget.scss'; +import './display-columns-panel.scss'; +import './alarm-status-filter-panel.scss'; /* eslint-disable import/no-unresolved, import/default */ import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html'; +import displayColumnsPanelTemplate from './display-columns-panel.tpl.html'; +import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -45,7 +49,7 @@ function AlarmsTableWidget() { } /*@ngInject*/ -function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) { +function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) { var vm = this; vm.stylesInfo = {}; @@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia vm.selectedAlarms = [] vm.alarmSource = null; + vm.alarmSearchStatus = null; vm.allAlarms = []; vm.currentAlarm = null; @@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia vm.onPaginate = onPaginate; vm.onRowClick = onRowClick; vm.onActionButtonClick = onActionButtonClick; + vm.actionEnabled = actionEnabled; vm.isCurrent = isCurrent; vm.openAlarmDetails = openAlarmDetails; vm.ackAlarms = ackAlarms; + vm.ackAlarm = ackAlarm; vm.clearAlarms = clearAlarms; + vm.clearAlarm = clearAlarm; vm.cellStyle = cellStyle; vm.cellContent = cellContent; + vm.editAlarmStatusFilter = editAlarmStatusFilter; + vm.editColumnsToDisplay = editColumnsToDisplay; + $scope.$watch('vm.ctx', function() { if (vm.ctx) { vm.settings = vm.ctx.settings; @@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia vm.ctx.widgetActions = [ vm.searchAction ]; - vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); + vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; + vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; + vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true; + + if (vm.displayDetails) { + vm.actionCellDescriptors.push( + { + displayName: $translate.instant('alarm.details'), + icon: 'more_horiz', + details: true + } + ); + } + + if (vm.allowAcknowledgment) { + vm.actionCellDescriptors.push( + { + displayName: $translate.instant('alarm.acknowledge'), + icon: 'done', + acknowledge: true + } + ); + } + + if (vm.allowClear) { + vm.actionCellDescriptors.push( + { + displayName: $translate.instant('alarm.clear'), + icon: 'clear', + clear: true + } + ); + } + + vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton')); if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle); @@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true; vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; - vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; - vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; - vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true; if (!vm.allowAcknowledgment && !vm.allowClear) { vm.enableSelection = false; } @@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia } function onActionButtonClick($event, alarm, actionDescriptor) { - if ($event) { - $event.stopPropagation(); + if (actionDescriptor.details) { + vm.openAlarmDetails($event, alarm); + } else if (actionDescriptor.acknowledge) { + vm.ackAlarm($event, alarm); + } else if (actionDescriptor.clear) { + vm.clearAlarm($event, alarm); + } else { + if ($event) { + $event.stopPropagation(); + } + var entityId; + var entityName; + if (alarm && alarm.originator) { + entityId = alarm.originator; + entityName = alarm.originatorName; + } + vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm}); } - var entityId; - var entityName; - if (alarm && alarm.originator) { - entityId = alarm.originator; - entityName = alarm.originatorName; + } + + function actionEnabled(alarm, actionDescriptor) { + if (actionDescriptor.acknowledge) { + return (alarm.status == types.alarmStatus.activeUnack || + alarm.status == types.alarmStatus.clearedUnack); + } else if (actionDescriptor.clear) { + return (alarm.status == types.alarmStatus.activeAck || + alarm.status == types.alarmStatus.activeUnack); } - vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm }); + return true; } function isCurrent(alarm) { @@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia } } + function ackAlarm($event, alarm) { + if ($event) { + $event.stopPropagation(); + } + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('alarm.aknowledge-alarm-title')) + .htmlContent($translate.instant('alarm.aknowledge-alarm-text')) + .ariaLabel($translate.instant('alarm.acknowledge')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + alarmService.ackAlarm(alarm.id.id).then(function () { + vm.selectedAlarms = []; + vm.subscription.update(); + }); + }); + } + function clearAlarms($event) { if ($event) { $event.stopPropagation(); @@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia } } + function clearAlarm($event, alarm) { + if ($event) { + $event.stopPropagation(); + } + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('alarm.clear-alarm-title')) + .htmlContent($translate.instant('alarm.clear-alarm-text')) + .ariaLabel($translate.instant('alarm.clear')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + alarmService.clearAlarm(alarm.id.id).then(function () { + vm.selectedAlarms = []; + vm.subscription.update(); + }); + }); + } function updateAlarms(preserveSelections) { if (!preserveSelections) { @@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia } } + function editAlarmStatusFilter($event) { + var element = angular.element($event.target); + var position = $mdPanel.newPanelPosition() + .relativeTo(element) + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); + var config = { + attachTo: angular.element($document[0].body), + controller: AlarmStatusFilterPanelController, + controllerAs: 'vm', + templateUrl: alarmStatusFilterPanelTemplate, + panelClass: 'tb-alarm-status-filter-panel', + position: position, + fullscreen: false, + locals: { + 'subscription': vm.subscription + }, + openFrom: $event, + clickOutsideToClose: true, + escapeToClose: true, + focusOnOpen: false + }; + $mdPanel.open(config); + } + + function editColumnsToDisplay($event) { + var element = angular.element($event.target); + var position = $mdPanel.newPanelPosition() + .relativeTo(element) + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); + var config = { + attachTo: angular.element($document[0].body), + controller: DisplayColumnsPanelController, + controllerAs: 'vm', + templateUrl: displayColumnsPanelTemplate, + panelClass: 'tb-display-columns-panel', + position: position, + fullscreen: false, + locals: { + 'columns': vm.alarmSource.dataKeys + }, + openFrom: $event, + clickOutsideToClose: true, + escapeToClose: true, + focusOnOpen: false + }; + $mdPanel.open(config); + } + function updateAlarmSource() { vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle); @@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia var dataKey = vm.alarmSource.dataKeys[d]; dataKey.title = utils.customTranslation(dataKey.label, dataKey.label); + dataKey.display = true; var keySettings = dataKey.settings; @@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia } } -} \ No newline at end of file +} + +/*@ngInject*/ +function DisplayColumnsPanelController(columns) { //eslint-disable-line + + var vm = this; + vm.columns = columns; +} + +/*@ngInject*/ +function AlarmStatusFilterPanelController(subscription, types) { //eslint-disable-line + + var vm = this; + vm.types = types; + vm.subscription = subscription; +} diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss index 2922996700..a03152360f 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.scss +++ b/ui/src/app/widget/lib/alarms-table-widget.scss @@ -44,6 +44,27 @@ &.tb-data-table { table.md-table, table.md-table.md-row-select { + th.md-column { + &.tb-action-cell { + .md-button { + /* stylelint-disable-next-line selector-max-class */ + &.md-icon-button { + width: 36px; + height: 36px; + padding: 6px; + margin: 0; + /* stylelint-disable-next-line selector-max-class */ + md-icon { + width: 24px; + height: 24px; + font-size: 24px !important; + line-height: 24px !important; + } + } + } + } + } + tbody { tr { td { @@ -51,6 +72,15 @@ width: 36px; min-width: 36px; max-width: 36px; + + .md-button[disabled] { + &.md-icon-button { + /* stylelint-disable-next-line selector-max-class */ + md-icon { + color: rgba(0, 0, 0, .38); + } + } + } } } } diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html index 8480058bc2..39843e19ea 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html +++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html @@ -62,33 +62,45 @@ - - - + + - - - - - - - + + - - - -
{{ key.title }}  {{ key.title }} + + filter_list + + + {{'alarm.alarm-status-filter' | translate}} + + + + view_column + + + {{'entity.columns-to-display' | translate}} + + +
- - more_horiz - - {{ 'alarm.details' | translate }} - - - {{actionDescriptor.icon}} diff --git a/ui/src/app/widget/lib/display-columns-panel.scss b/ui/src/app/widget/lib/display-columns-panel.scss new file mode 100644 index 0000000000..2e518cba96 --- /dev/null +++ b/ui/src/app/widget/lib/display-columns-panel.scss @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-display-columns-panel { + min-width: 300px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + md-content { + overflow: hidden; + background-color: #fff; + } +} diff --git a/ui/src/app/widget/lib/display-columns-panel.tpl.html b/ui/src/app/widget/lib/display-columns-panel.tpl.html new file mode 100644 index 0000000000..42e2471603 --- /dev/null +++ b/ui/src/app/widget/lib/display-columns-panel.tpl.html @@ -0,0 +1,24 @@ + + + + + {{ column.title }} + + diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js index d0b629d290..620f313749 100644 --- a/ui/src/app/widget/lib/entities-table-widget.js +++ b/ui/src/app/widget/lib/entities-table-widget.js @@ -14,11 +14,13 @@ * limitations under the License. */ import './entities-table-widget.scss'; +import './display-columns-panel.scss'; /* eslint-disable import/no-unresolved, import/default */ import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html'; //import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html'; +import displayColumnsPanelTemplate from './display-columns-panel.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -45,7 +47,7 @@ function EntitiesTableWidget() { } /*@ngInject*/ -function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) { +function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) { var vm = this; vm.stylesInfo = {}; @@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra vm.cellStyle = cellStyle; vm.cellContent = cellContent; + vm.editColumnsToDisplay = editColumnsToDisplay; + $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { vm.settings = vm.ctx.settings; @@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra } } + function editColumnsToDisplay($event) { + var element = angular.element($event.target); + var position = $mdPanel.newPanelPosition() + .relativeTo(element) + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); + var config = { + attachTo: angular.element($document[0].body), + controller: DisplayColumnsPanelController, + controllerAs: 'vm', + templateUrl: displayColumnsPanelTemplate, + panelClass: 'tb-display-columns-panel', + position: position, + fullscreen: false, + locals: { + 'columns': vm.columns + }, + openFrom: $event, + clickOutsideToClose: true, + escapeToClose: true, + focusOnOpen: false + }; + $mdPanel.open(config); + } + function updateDatasources() { vm.stylesInfo = {}; vm.contentsInfo = {}; vm.columnWidth = {}; vm.dataKeys = []; + vm.columns = []; vm.allEntities = []; var datasource; @@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle); + if (vm.displayEntityName) { + vm.columns.push( + { + name: 'entityName', + label: 'entityName', + title: vm.entityNameColumnTitle, + display: true + } + ); + vm.contentsInfo['entityName'] = { + useCellContentFunction: false + }; + vm.stylesInfo['entityName'] = { + useCellStyleFunction: false + }; + vm.columnWidth['entityName'] = '0px'; + } + + if (vm.displayEntityType) { + vm.columns.push( + { + name: 'entityType', + label: 'entityType', + title: $translate.instant('entity.entity-type'), + display: true + } + ); + vm.contentsInfo['entityType'] = { + useCellContentFunction: false + }; + vm.stylesInfo['entityType'] = { + useCellStyleFunction: false + }; + vm.columnWidth['entityType'] = '0px'; + } + for (var d = 0; d < datasource.dataKeys.length; d++ ) { dataKey = angular.copy(datasource.dataKeys[d]); if (dataKey.type == types.dataKeyType.function) { @@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; vm.columnWidth[dataKey.label] = columnWidth; + + dataKey.display = true; + vm.columns.push(dataKey); } for (var i=0;i
{{vm.entityNameColumnTitle}}entity.entity-type{{ key.title }} {{ column.title }} + + view_column + + + {{'entity.columns-to-display' | translate}} + + +
{{entity.entityName}}{{entity.entityType}} +