Commit 8ea7058da49e42439b94802515ca098e3a6d05a4
1 parent
b46d6b03
Alarms and Entities table widgets improvements.
Showing
12 changed files
with
486 additions
and
42 deletions
... | ... | @@ -267,6 +267,14 @@ export default class Subscription { |
267 | 267 | } else { |
268 | 268 | this.startWatchingTimewindow(); |
269 | 269 | } |
270 | + registration = this.ctx.$scope.$watch(function () { | |
271 | + return subscription.alarmSearchStatus; | |
272 | + }, function (newAlarmSearchStatus, prevAlarmSearchStatus) { | |
273 | + if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) { | |
274 | + subscription.update(); | |
275 | + } | |
276 | + }, true); | |
277 | + this.registrations.push(registration); | |
270 | 278 | } |
271 | 279 | |
272 | 280 | initDataSubscription() { | ... | ... |
... | ... | @@ -133,8 +133,13 @@ |
133 | 133 | "min-polling-interval-message": "At least 1 sec polling interval is allowed.", |
134 | 134 | "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", |
135 | 135 | "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", |
136 | + "aknowledge-alarm-title": "Acknowledge Alarm", | |
137 | + "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", | |
136 | 138 | "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", |
137 | - "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?" | |
139 | + "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", | |
140 | + "clear-alarm-title": "Clear Alarm", | |
141 | + "clear-alarm-text": "Are you sure you want to clear Alarm?", | |
142 | + "alarm-status-filter": "Alarm Status Filter" | |
138 | 143 | }, |
139 | 144 | "alias": { |
140 | 145 | "add": "Add alias", |
... | ... | @@ -748,7 +753,8 @@ |
748 | 753 | "entity-name": "Entity name", |
749 | 754 | "details": "Entity details", |
750 | 755 | "no-entities-prompt": "No entities found", |
751 | - "no-data": "No data to display" | |
756 | + "no-data": "No data to display", | |
757 | + "columns-to-display": "Columns to Display" | |
752 | 758 | }, |
753 | 759 | "event": { |
754 | 760 | "event-type": "Event type", | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 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 | +.tb-alarm-status-filter-panel { | |
18 | + min-width: 300px; | |
19 | + overflow: hidden; | |
20 | + background: #fff; | |
21 | + border-radius: 4px; | |
22 | + box-shadow: | |
23 | + 0 7px 8px -4px rgba(0, 0, 0, .2), | |
24 | + 0 13px 19px 2px rgba(0, 0, 0, .14), | |
25 | + 0 5px 24px 4px rgba(0, 0, 0, .12); | |
26 | + | |
27 | + md-content { | |
28 | + overflow: hidden; | |
29 | + background-color: #fff; | |
30 | + } | |
31 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2018 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 | + | |
19 | +<md-content style="height: 100%" flex layout="column" class="md-padding"> | |
20 | + <label class="tb-title" translate>alarm.alarm-status-filter</label> | |
21 | + <md-radio-group ng-model="vm.subscription.alarmSearchStatus" class="md-primary"> | |
22 | + <md-radio-button ng-value="searchStatus" | |
23 | + aria-label="{{ ('alarm.search-status.' + searchStatus) | translate }}" | |
24 | + class="md-primary md-align-top-left md-radio-interactive" ng-repeat="searchStatus in vm.types.alarmSearchStatus"> | |
25 | + {{ ('alarm.search-status.' + searchStatus) | translate }} | |
26 | + </md-radio-button> | |
27 | + </md-radio-group> | |
28 | +</md-content> | ... | ... |
... | ... | @@ -14,11 +14,15 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | import './alarms-table-widget.scss'; |
17 | +import './display-columns-panel.scss'; | |
18 | +import './alarm-status-filter-panel.scss'; | |
17 | 19 | |
18 | 20 | /* eslint-disable import/no-unresolved, import/default */ |
19 | 21 | |
20 | 22 | import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; |
21 | 23 | import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html'; |
24 | +import displayColumnsPanelTemplate from './display-columns-panel.tpl.html'; | |
25 | +import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html'; | |
22 | 26 | |
23 | 27 | /* eslint-enable import/no-unresolved, import/default */ |
24 | 28 | |
... | ... | @@ -45,7 +49,7 @@ function AlarmsTableWidget() { |
45 | 49 | } |
46 | 50 | |
47 | 51 | /*@ngInject*/ |
48 | -function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) { | |
52 | +function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) { | |
49 | 53 | var vm = this; |
50 | 54 | |
51 | 55 | vm.stylesInfo = {}; |
... | ... | @@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
60 | 64 | vm.selectedAlarms = [] |
61 | 65 | |
62 | 66 | vm.alarmSource = null; |
67 | + vm.alarmSearchStatus = null; | |
63 | 68 | vm.allAlarms = []; |
64 | 69 | |
65 | 70 | vm.currentAlarm = null; |
... | ... | @@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
95 | 100 | vm.onPaginate = onPaginate; |
96 | 101 | vm.onRowClick = onRowClick; |
97 | 102 | vm.onActionButtonClick = onActionButtonClick; |
103 | + vm.actionEnabled = actionEnabled; | |
98 | 104 | vm.isCurrent = isCurrent; |
99 | 105 | vm.openAlarmDetails = openAlarmDetails; |
100 | 106 | vm.ackAlarms = ackAlarms; |
107 | + vm.ackAlarm = ackAlarm; | |
101 | 108 | vm.clearAlarms = clearAlarms; |
109 | + vm.clearAlarm = clearAlarm; | |
102 | 110 | |
103 | 111 | vm.cellStyle = cellStyle; |
104 | 112 | vm.cellContent = cellContent; |
105 | 113 | |
114 | + vm.editAlarmStatusFilter = editAlarmStatusFilter; | |
115 | + vm.editColumnsToDisplay = editColumnsToDisplay; | |
116 | + | |
106 | 117 | $scope.$watch('vm.ctx', function() { |
107 | 118 | if (vm.ctx) { |
108 | 119 | vm.settings = vm.ctx.settings; |
... | ... | @@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
158 | 169 | |
159 | 170 | vm.ctx.widgetActions = [ vm.searchAction ]; |
160 | 171 | |
161 | - vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); | |
172 | + vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; | |
173 | + vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; | |
174 | + vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true; | |
175 | + | |
176 | + if (vm.displayDetails) { | |
177 | + vm.actionCellDescriptors.push( | |
178 | + { | |
179 | + displayName: $translate.instant('alarm.details'), | |
180 | + icon: 'more_horiz', | |
181 | + details: true | |
182 | + } | |
183 | + ); | |
184 | + } | |
185 | + | |
186 | + if (vm.allowAcknowledgment) { | |
187 | + vm.actionCellDescriptors.push( | |
188 | + { | |
189 | + displayName: $translate.instant('alarm.acknowledge'), | |
190 | + icon: 'done', | |
191 | + acknowledge: true | |
192 | + } | |
193 | + ); | |
194 | + } | |
195 | + | |
196 | + if (vm.allowClear) { | |
197 | + vm.actionCellDescriptors.push( | |
198 | + { | |
199 | + displayName: $translate.instant('alarm.clear'), | |
200 | + icon: 'clear', | |
201 | + clear: true | |
202 | + } | |
203 | + ); | |
204 | + } | |
205 | + | |
206 | + vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton')); | |
162 | 207 | |
163 | 208 | if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { |
164 | 209 | vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle); |
... | ... | @@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
170 | 215 | |
171 | 216 | vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true; |
172 | 217 | vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; |
173 | - vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; | |
174 | - vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; | |
175 | - vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true; | |
176 | 218 | if (!vm.allowAcknowledgment && !vm.allowClear) { |
177 | 219 | vm.enableSelection = false; |
178 | 220 | } |
... | ... | @@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
305 | 347 | } |
306 | 348 | |
307 | 349 | function onActionButtonClick($event, alarm, actionDescriptor) { |
308 | - if ($event) { | |
309 | - $event.stopPropagation(); | |
350 | + if (actionDescriptor.details) { | |
351 | + vm.openAlarmDetails($event, alarm); | |
352 | + } else if (actionDescriptor.acknowledge) { | |
353 | + vm.ackAlarm($event, alarm); | |
354 | + } else if (actionDescriptor.clear) { | |
355 | + vm.clearAlarm($event, alarm); | |
356 | + } else { | |
357 | + if ($event) { | |
358 | + $event.stopPropagation(); | |
359 | + } | |
360 | + var entityId; | |
361 | + var entityName; | |
362 | + if (alarm && alarm.originator) { | |
363 | + entityId = alarm.originator; | |
364 | + entityName = alarm.originatorName; | |
365 | + } | |
366 | + vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm}); | |
310 | 367 | } |
311 | - var entityId; | |
312 | - var entityName; | |
313 | - if (alarm && alarm.originator) { | |
314 | - entityId = alarm.originator; | |
315 | - entityName = alarm.originatorName; | |
368 | + } | |
369 | + | |
370 | + function actionEnabled(alarm, actionDescriptor) { | |
371 | + if (actionDescriptor.acknowledge) { | |
372 | + return (alarm.status == types.alarmStatus.activeUnack || | |
373 | + alarm.status == types.alarmStatus.clearedUnack); | |
374 | + } else if (actionDescriptor.clear) { | |
375 | + return (alarm.status == types.alarmStatus.activeAck || | |
376 | + alarm.status == types.alarmStatus.activeUnack); | |
316 | 377 | } |
317 | - vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm }); | |
378 | + return true; | |
318 | 379 | } |
319 | 380 | |
320 | 381 | function isCurrent(alarm) { |
... | ... | @@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
387 | 448 | } |
388 | 449 | } |
389 | 450 | |
451 | + function ackAlarm($event, alarm) { | |
452 | + if ($event) { | |
453 | + $event.stopPropagation(); | |
454 | + } | |
455 | + var confirm = $mdDialog.confirm() | |
456 | + .targetEvent($event) | |
457 | + .title($translate.instant('alarm.aknowledge-alarm-title')) | |
458 | + .htmlContent($translate.instant('alarm.aknowledge-alarm-text')) | |
459 | + .ariaLabel($translate.instant('alarm.acknowledge')) | |
460 | + .cancel($translate.instant('action.no')) | |
461 | + .ok($translate.instant('action.yes')); | |
462 | + $mdDialog.show(confirm).then(function () { | |
463 | + alarmService.ackAlarm(alarm.id.id).then(function () { | |
464 | + vm.selectedAlarms = []; | |
465 | + vm.subscription.update(); | |
466 | + }); | |
467 | + }); | |
468 | + } | |
469 | + | |
390 | 470 | function clearAlarms($event) { |
391 | 471 | if ($event) { |
392 | 472 | $event.stopPropagation(); |
... | ... | @@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
420 | 500 | } |
421 | 501 | } |
422 | 502 | |
503 | + function clearAlarm($event, alarm) { | |
504 | + if ($event) { | |
505 | + $event.stopPropagation(); | |
506 | + } | |
507 | + var confirm = $mdDialog.confirm() | |
508 | + .targetEvent($event) | |
509 | + .title($translate.instant('alarm.clear-alarm-title')) | |
510 | + .htmlContent($translate.instant('alarm.clear-alarm-text')) | |
511 | + .ariaLabel($translate.instant('alarm.clear')) | |
512 | + .cancel($translate.instant('action.no')) | |
513 | + .ok($translate.instant('action.yes')); | |
514 | + $mdDialog.show(confirm).then(function () { | |
515 | + alarmService.clearAlarm(alarm.id.id).then(function () { | |
516 | + vm.selectedAlarms = []; | |
517 | + vm.subscription.update(); | |
518 | + }); | |
519 | + }); | |
520 | + } | |
423 | 521 | |
424 | 522 | function updateAlarms(preserveSelections) { |
425 | 523 | if (!preserveSelections) { |
... | ... | @@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
558 | 656 | } |
559 | 657 | } |
560 | 658 | |
659 | + function editAlarmStatusFilter($event) { | |
660 | + var element = angular.element($event.target); | |
661 | + var position = $mdPanel.newPanelPosition() | |
662 | + .relativeTo(element) | |
663 | + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); | |
664 | + var config = { | |
665 | + attachTo: angular.element($document[0].body), | |
666 | + controller: AlarmStatusFilterPanelController, | |
667 | + controllerAs: 'vm', | |
668 | + templateUrl: alarmStatusFilterPanelTemplate, | |
669 | + panelClass: 'tb-alarm-status-filter-panel', | |
670 | + position: position, | |
671 | + fullscreen: false, | |
672 | + locals: { | |
673 | + 'subscription': vm.subscription | |
674 | + }, | |
675 | + openFrom: $event, | |
676 | + clickOutsideToClose: true, | |
677 | + escapeToClose: true, | |
678 | + focusOnOpen: false | |
679 | + }; | |
680 | + $mdPanel.open(config); | |
681 | + } | |
682 | + | |
683 | + function editColumnsToDisplay($event) { | |
684 | + var element = angular.element($event.target); | |
685 | + var position = $mdPanel.newPanelPosition() | |
686 | + .relativeTo(element) | |
687 | + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); | |
688 | + var config = { | |
689 | + attachTo: angular.element($document[0].body), | |
690 | + controller: DisplayColumnsPanelController, | |
691 | + controllerAs: 'vm', | |
692 | + templateUrl: displayColumnsPanelTemplate, | |
693 | + panelClass: 'tb-display-columns-panel', | |
694 | + position: position, | |
695 | + fullscreen: false, | |
696 | + locals: { | |
697 | + 'columns': vm.alarmSource.dataKeys | |
698 | + }, | |
699 | + openFrom: $event, | |
700 | + clickOutsideToClose: true, | |
701 | + escapeToClose: true, | |
702 | + focusOnOpen: false | |
703 | + }; | |
704 | + $mdPanel.open(config); | |
705 | + } | |
706 | + | |
561 | 707 | function updateAlarmSource() { |
562 | 708 | |
563 | 709 | vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle); |
... | ... | @@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
570 | 716 | var dataKey = vm.alarmSource.dataKeys[d]; |
571 | 717 | |
572 | 718 | dataKey.title = utils.customTranslation(dataKey.label, dataKey.label); |
719 | + dataKey.display = true; | |
573 | 720 | |
574 | 721 | var keySettings = dataKey.settings; |
575 | 722 | |
... | ... | @@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
618 | 765 | } |
619 | 766 | } |
620 | 767 | |
621 | -} | |
\ No newline at end of file | ||
768 | +} | |
769 | + | |
770 | +/*@ngInject*/ | |
771 | +function DisplayColumnsPanelController(columns) { //eslint-disable-line | |
772 | + | |
773 | + var vm = this; | |
774 | + vm.columns = columns; | |
775 | +} | |
776 | + | |
777 | +/*@ngInject*/ | |
778 | +function AlarmStatusFilterPanelController(subscription, types) { //eslint-disable-line | |
779 | + | |
780 | + var vm = this; | |
781 | + vm.types = types; | |
782 | + vm.subscription = subscription; | |
783 | +} | ... | ... |
... | ... | @@ -44,6 +44,27 @@ |
44 | 44 | &.tb-data-table { |
45 | 45 | table.md-table, |
46 | 46 | table.md-table.md-row-select { |
47 | + th.md-column { | |
48 | + &.tb-action-cell { | |
49 | + .md-button { | |
50 | + /* stylelint-disable-next-line selector-max-class */ | |
51 | + &.md-icon-button { | |
52 | + width: 36px; | |
53 | + height: 36px; | |
54 | + padding: 6px; | |
55 | + margin: 0; | |
56 | + /* stylelint-disable-next-line selector-max-class */ | |
57 | + md-icon { | |
58 | + width: 24px; | |
59 | + height: 24px; | |
60 | + font-size: 24px !important; | |
61 | + line-height: 24px !important; | |
62 | + } | |
63 | + } | |
64 | + } | |
65 | + } | |
66 | + } | |
67 | + | |
47 | 68 | tbody { |
48 | 69 | tr { |
49 | 70 | td { |
... | ... | @@ -51,6 +72,15 @@ |
51 | 72 | width: 36px; |
52 | 73 | min-width: 36px; |
53 | 74 | max-width: 36px; |
75 | + | |
76 | + .md-button[disabled] { | |
77 | + &.md-icon-button { | |
78 | + /* stylelint-disable-next-line selector-max-class */ | |
79 | + md-icon { | |
80 | + color: rgba(0, 0, 0, .38); | |
81 | + } | |
82 | + } | |
83 | + } | |
54 | 84 | } |
55 | 85 | } |
56 | 86 | } | ... | ... |
... | ... | @@ -62,33 +62,45 @@ |
62 | 62 | <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms"> |
63 | 63 | <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
64 | 64 | <tr md-row> |
65 | - <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th> | |
66 | - <th md-column ng-if="vm.displayDetails"><span> </span></th> | |
67 | - <th md-column ng-if="vm.actionCellDescriptors.length"><span> </span></th> | |
65 | + <th ng-if="key.display" md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th> | |
66 | + <th md-column class="tb-action-cell" layout="row" layout-align="end center"> | |
67 | + <md-button class="md-icon-button" | |
68 | + aria-label="{{'alarm.alarm-status-filter' | translate}}" | |
69 | + ng-click="vm.editAlarmStatusFilter($event)"> | |
70 | + <md-icon aria-label="{{'alarm.alarm-status-filter' | translate}}" | |
71 | + class="material-icons">filter_list | |
72 | + </md-icon> | |
73 | + <md-tooltip md-direction="top"> | |
74 | + {{'alarm.alarm-status-filter' | translate}} | |
75 | + </md-tooltip> | |
76 | + </md-button> | |
77 | + <md-button class="md-icon-button" | |
78 | + aria-label="{{'entity.columns-to-display' | translate}}" | |
79 | + ng-click="vm.editColumnsToDisplay($event)"> | |
80 | + <md-icon aria-label="{{'entity.columns-to-display' | translate}}" | |
81 | + class="material-icons">view_column | |
82 | + </md-icon> | |
83 | + <md-tooltip md-direction="top"> | |
84 | + {{'entity.columns-to-display' | translate}} | |
85 | + </md-tooltip> | |
86 | + </md-button> | |
87 | + </th> | |
68 | 88 | </tr> |
69 | 89 | </thead> |
70 | 90 | <tbody md-body> |
71 | 91 | <tr ng-show="vm.alarms.length" md-row md-select="alarm" |
72 | 92 | md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms" |
73 | 93 | ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}"> |
74 | - <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys" | |
94 | + <td ng-if="key.display" md-cell flex ng-repeat="key in vm.alarmSource.dataKeys" | |
75 | 95 | ng-style="vm.cellStyle(alarm, key)" |
76 | 96 | ng-bind-html="vm.cellContent(alarm, key)"> |
77 | 97 | </td> |
78 | - <td md-cell ng-if="vm.displayDetails" class="tb-action-cell"> | |
79 | - <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}" | |
80 | - ng-click="vm.openAlarmDetails($event, alarm)" ng-disabled="$root.loading"> | |
81 | - <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon> | |
82 | - <md-tooltip md-direction="top"> | |
83 | - {{ 'alarm.details' | translate }} | |
84 | - </md-tooltip> | |
85 | - </md-button> | |
86 | - </td> | |
87 | - <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell" | |
98 | + <td md-cell class="tb-action-cell" | |
88 | 99 | ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px', |
89 | 100 | maxWidth: vm.actionCellDescriptors.length*36+'px', |
90 | 101 | width: vm.actionCellDescriptors.length*36+'px'}"> |
91 | 102 | <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors" |
103 | + ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)" | |
92 | 104 | aria-label="{{ actionDescriptor.displayName }}" |
93 | 105 | ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading"> |
94 | 106 | <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 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 | +.tb-display-columns-panel { | |
18 | + min-width: 300px; | |
19 | + overflow: hidden; | |
20 | + background: #fff; | |
21 | + border-radius: 4px; | |
22 | + box-shadow: | |
23 | + 0 7px 8px -4px rgba(0, 0, 0, .2), | |
24 | + 0 13px 19px 2px rgba(0, 0, 0, .14), | |
25 | + 0 5px 24px 4px rgba(0, 0, 0, .12); | |
26 | + | |
27 | + md-content { | |
28 | + overflow: hidden; | |
29 | + background-color: #fff; | |
30 | + } | |
31 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2018 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 | + | |
19 | +<md-content style="height: 100%" flex layout="column" class="md-padding"> | |
20 | + <label class="tb-title" translate>entity.columns-to-display</label> | |
21 | + <md-checkbox aria-label="{{ 'entity.columns-to-display' | translate }}" ng-repeat="column in vm.columns" | |
22 | + ng-model="column.display">{{ column.title }} | |
23 | + </md-checkbox> | |
24 | +</md-content> | ... | ... |
... | ... | @@ -14,11 +14,13 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | import './entities-table-widget.scss'; |
17 | +import './display-columns-panel.scss'; | |
17 | 18 | |
18 | 19 | /* eslint-disable import/no-unresolved, import/default */ |
19 | 20 | |
20 | 21 | import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html'; |
21 | 22 | //import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html'; |
23 | +import displayColumnsPanelTemplate from './display-columns-panel.tpl.html'; | |
22 | 24 | |
23 | 25 | /* eslint-enable import/no-unresolved, import/default */ |
24 | 26 | |
... | ... | @@ -45,7 +47,7 @@ function EntitiesTableWidget() { |
45 | 47 | } |
46 | 48 | |
47 | 49 | /*@ngInject*/ |
48 | -function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) { | |
50 | +function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) { | |
49 | 51 | var vm = this; |
50 | 52 | |
51 | 53 | vm.stylesInfo = {}; |
... | ... | @@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
98 | 100 | vm.cellStyle = cellStyle; |
99 | 101 | vm.cellContent = cellContent; |
100 | 102 | |
103 | + vm.editColumnsToDisplay = editColumnsToDisplay; | |
104 | + | |
101 | 105 | $scope.$watch('vm.ctx', function() { |
102 | 106 | if (vm.ctx && vm.ctx.defaultSubscription) { |
103 | 107 | vm.settings = vm.ctx.settings; |
... | ... | @@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
414 | 418 | } |
415 | 419 | } |
416 | 420 | |
421 | + function editColumnsToDisplay($event) { | |
422 | + var element = angular.element($event.target); | |
423 | + var position = $mdPanel.newPanelPosition() | |
424 | + .relativeTo(element) | |
425 | + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); | |
426 | + var config = { | |
427 | + attachTo: angular.element($document[0].body), | |
428 | + controller: DisplayColumnsPanelController, | |
429 | + controllerAs: 'vm', | |
430 | + templateUrl: displayColumnsPanelTemplate, | |
431 | + panelClass: 'tb-display-columns-panel', | |
432 | + position: position, | |
433 | + fullscreen: false, | |
434 | + locals: { | |
435 | + 'columns': vm.columns | |
436 | + }, | |
437 | + openFrom: $event, | |
438 | + clickOutsideToClose: true, | |
439 | + escapeToClose: true, | |
440 | + focusOnOpen: false | |
441 | + }; | |
442 | + $mdPanel.open(config); | |
443 | + } | |
444 | + | |
417 | 445 | function updateDatasources() { |
418 | 446 | |
419 | 447 | vm.stylesInfo = {}; |
420 | 448 | vm.contentsInfo = {}; |
421 | 449 | vm.columnWidth = {}; |
422 | 450 | vm.dataKeys = []; |
451 | + vm.columns = []; | |
423 | 452 | vm.allEntities = []; |
424 | 453 | |
425 | 454 | var datasource; |
... | ... | @@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
429 | 458 | |
430 | 459 | vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle); |
431 | 460 | |
461 | + if (vm.displayEntityName) { | |
462 | + vm.columns.push( | |
463 | + { | |
464 | + name: 'entityName', | |
465 | + label: 'entityName', | |
466 | + title: vm.entityNameColumnTitle, | |
467 | + display: true | |
468 | + } | |
469 | + ); | |
470 | + vm.contentsInfo['entityName'] = { | |
471 | + useCellContentFunction: false | |
472 | + }; | |
473 | + vm.stylesInfo['entityName'] = { | |
474 | + useCellStyleFunction: false | |
475 | + }; | |
476 | + vm.columnWidth['entityName'] = '0px'; | |
477 | + } | |
478 | + | |
479 | + if (vm.displayEntityType) { | |
480 | + vm.columns.push( | |
481 | + { | |
482 | + name: 'entityType', | |
483 | + label: 'entityType', | |
484 | + title: $translate.instant('entity.entity-type'), | |
485 | + display: true | |
486 | + } | |
487 | + ); | |
488 | + vm.contentsInfo['entityType'] = { | |
489 | + useCellContentFunction: false | |
490 | + }; | |
491 | + vm.stylesInfo['entityType'] = { | |
492 | + useCellStyleFunction: false | |
493 | + }; | |
494 | + vm.columnWidth['entityType'] = '0px'; | |
495 | + } | |
496 | + | |
432 | 497 | for (var d = 0; d < datasource.dataKeys.length; d++ ) { |
433 | 498 | dataKey = angular.copy(datasource.dataKeys[d]); |
434 | 499 | if (dataKey.type == types.dataKeyType.function) { |
... | ... | @@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
482 | 547 | |
483 | 548 | var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; |
484 | 549 | vm.columnWidth[dataKey.label] = columnWidth; |
550 | + | |
551 | + dataKey.display = true; | |
552 | + vm.columns.push(dataKey); | |
485 | 553 | } |
486 | 554 | |
487 | 555 | for (var i=0;i<vm.datasources.length;i++) { |
... | ... | @@ -511,4 +579,11 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
511 | 579 | |
512 | 580 | } |
513 | 581 | |
514 | -} | |
\ No newline at end of file | ||
582 | +} | |
583 | + | |
584 | +/*@ngInject*/ | |
585 | +function DisplayColumnsPanelController(columns) { //eslint-disable-line | |
586 | + | |
587 | + var vm = this; | |
588 | + vm.columns = columns; | |
589 | +} | ... | ... |
... | ... | @@ -44,6 +44,27 @@ |
44 | 44 | &.tb-data-table { |
45 | 45 | table.md-table, |
46 | 46 | table.md-table.md-row-select { |
47 | + th.md-column { | |
48 | + &.tb-action-cell { | |
49 | + .md-button { | |
50 | + /* stylelint-disable-next-line selector-max-class */ | |
51 | + &.md-icon-button { | |
52 | + width: 36px; | |
53 | + height: 36px; | |
54 | + padding: 6px; | |
55 | + margin: 0; | |
56 | + /* stylelint-disable-next-line selector-max-class */ | |
57 | + md-icon { | |
58 | + width: 24px; | |
59 | + height: 24px; | |
60 | + font-size: 24px !important; | |
61 | + line-height: 24px !important; | |
62 | + } | |
63 | + } | |
64 | + } | |
65 | + } | |
66 | + } | |
67 | + | |
47 | 68 | tbody { |
48 | 69 | tr { |
49 | 70 | td { |
... | ... | @@ -51,6 +72,15 @@ |
51 | 72 | width: 36px; |
52 | 73 | min-width: 36px; |
53 | 74 | max-width: 36px; |
75 | + | |
76 | + .md-button[disabled] { | |
77 | + &.md-icon-button { | |
78 | + /* stylelint-disable-next-line selector-max-class */ | |
79 | + md-icon { | |
80 | + color: rgba(0, 0, 0, .38); | |
81 | + } | |
82 | + } | |
83 | + } | |
54 | 84 | } |
55 | 85 | } |
56 | 86 | } | ... | ... |
... | ... | @@ -41,23 +41,30 @@ |
41 | 41 | <table md-table> |
42 | 42 | <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
43 | 43 | <tr md-row> |
44 | - <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th> | |
45 | - <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th> | |
46 | - <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th> | |
47 | - <th md-column ng-if="vm.actionCellDescriptors.length"><span> </span></th> | |
44 | + <th ng-if="column.display" md-column md-order-by="{{ column.name }}" ng-repeat="column in vm.columns"><span>{{ column.title }}</span></th> | |
45 | + <th md-column class="tb-action-cell" layout="row" layout-align="end center"> | |
46 | + <md-button class="md-icon-button" | |
47 | + aria-label="{{'entity.columns-to-display' | translate}}" | |
48 | + ng-click="vm.editColumnsToDisplay($event)"> | |
49 | + <md-icon aria-label="{{'entity.columns-to-display' | translate}}" | |
50 | + class="material-icons">view_column | |
51 | + </md-icon> | |
52 | + <md-tooltip md-direction="top"> | |
53 | + {{'entity.columns-to-display' | translate}} | |
54 | + </md-tooltip> | |
55 | + </md-button> | |
56 | + </th> | |
48 | 57 | </tr> |
49 | 58 | </thead> |
50 | 59 | <tbody md-body> |
51 | 60 | <tr ng-show="vm.entities.length" md-row md-select="entity" |
52 | 61 | md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities" |
53 | 62 | ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}"> |
54 | - <td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td> | |
55 | - <td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td> | |
56 | - <td md-cell flex ng-repeat="key in vm.dataKeys" | |
57 | - ng-style="vm.cellStyle(entity, key)" | |
58 | - ng-bind-html="vm.cellContent(entity, key)"> | |
63 | + <td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns" | |
64 | + ng-style="vm.cellStyle(entity, column)" | |
65 | + ng-bind-html="vm.cellContent(entity, column)"> | |
59 | 66 | </td> |
60 | - <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell" | |
67 | + <td md-cell class="tb-action-cell" | |
61 | 68 | ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px', |
62 | 69 | maxWidth: vm.actionCellDescriptors.length*36+'px', |
63 | 70 | width: vm.actionCellDescriptors.length*36+'px'}"> | ... | ... |