Commit eda21c94efabb9f206273e5a4f114db898c4458b

Authored by Igor Kulikov
1 parent 531f4245

TB-63: Alarms widget implementation

@@ -23,11 +23,14 @@ import './alarm-details-dialog.scss'; @@ -23,11 +23,14 @@ import './alarm-details-dialog.scss';
23 const js_beautify = beautify.js; 23 const js_beautify = beautify.js;
24 24
25 /*@ngInject*/ 25 /*@ngInject*/
26 -export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) { 26 +export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types,
  27 + alarmService, alarmId, allowAcknowledgment, allowClear, showingCallback) {
27 28
28 var vm = this; 29 var vm = this;
29 30
30 vm.alarmId = alarmId; 31 vm.alarmId = alarmId;
  32 + vm.allowAcknowledgment = allowAcknowledgment;
  33 + vm.allowClear = allowClear;
31 vm.types = types; 34 vm.types = types;
32 vm.alarm = null; 35 vm.alarm = null;
33 36
@@ -84,16 +84,16 @@ @@ -84,16 +84,16 @@
84 </div> 84 </div>
85 </md-dialog-content> 85 </md-dialog-content>
86 <md-dialog-actions layout="row"> 86 <md-dialog-actions layout="row">
87 - <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||  
88 - vm.alarm.status==vm.types.alarmStatus.clearedUnack" 87 + <md-button ng-if="vm.allowAcknowledgment && (vm.alarm.status==vm.types.alarmStatus.activeUnack ||
  88 + vm.alarm.status==vm.types.alarmStatus.clearedUnack)"
89 class="md-raised md-primary" 89 class="md-raised md-primary"
90 ng-disabled="loading" 90 ng-disabled="loading"
91 ng-click="vm.acknowledge()" 91 ng-click="vm.acknowledge()"
92 style="margin-right:20px;">{{ 'alarm.acknowledge' | 92 style="margin-right:20px;">{{ 'alarm.acknowledge' |
93 translate }} 93 translate }}
94 </md-button> 94 </md-button>
95 - <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||  
96 - vm.alarm.status==vm.types.alarmStatus.activeUnack" 95 + <md-button ng-if="vm.allowClear && (vm.alarm.status==vm.types.alarmStatus.activeAck ||
  96 + vm.alarm.status==vm.types.alarmStatus.activeUnack)"
97 class="md-raised md-primary" 97 class="md-raised md-primary"
98 ng-disabled="loading" 98 ng-disabled="loading"
99 ng-click="vm.clear()">{{ 'alarm.clear' | 99 ng-click="vm.clear()">{{ 'alarm.clear' |
@@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi @@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi
40 controller: 'AlarmDetailsDialogController', 40 controller: 'AlarmDetailsDialogController',
41 controllerAs: 'vm', 41 controllerAs: 'vm',
42 templateUrl: alarmDetailsDialogTemplate, 42 templateUrl: alarmDetailsDialogTemplate,
43 - locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback}, 43 + locals: {
  44 + alarmId: scope.alarm.id.id,
  45 + allowAcknowledgment: true,
  46 + allowClear: true,
  47 + showingCallback: onShowingCallback
  48 + },
44 parent: angular.element($document[0].body), 49 parent: angular.element($document[0].body),
45 targetEvent: $event, 50 targetEvent: $event,
46 fullscreen: true, 51 fullscreen: true,
@@ -252,12 +252,12 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { @@ -252,12 +252,12 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
252 $timeout(function() { 252 $timeout(function() {
253 alarmSourceListener.alarmsUpdated([simulatedAlarm], false); 253 alarmSourceListener.alarmsUpdated([simulatedAlarm], false);
254 }); 254 });
255 - } else {  
256 - var pollingInterval = 5000; //TODO: 255 + } else if (alarmSource.entityType && alarmSource.entityId) {
  256 + var pollingInterval = alarmSourceListener.alarmsPollingInterval;
257 alarmSourceListener.alarmsQuery = { 257 alarmSourceListener.alarmsQuery = {
258 entityType: alarmSource.entityType, 258 entityType: alarmSource.entityType,
259 entityId: alarmSource.entityId, 259 entityId: alarmSource.entityId,
260 - alarmSearchStatus: null, //TODO: 260 + alarmSearchStatus: alarmSourceListener.alarmSearchStatus,
261 alarmStatus: null 261 alarmStatus: null
262 } 262 }
263 var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); 263 var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'});
@@ -70,6 +70,12 @@ export default class Subscription { @@ -70,6 +70,12 @@ export default class Subscription {
70 this.callbacks.dataLoading = this.callbacks.dataLoading || function(){}; 70 this.callbacks.dataLoading = this.callbacks.dataLoading || function(){};
71 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){}; 71 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){};
72 this.alarmSource = options.alarmSource; 72 this.alarmSource = options.alarmSource;
  73 +
  74 + this.alarmSearchStatus = angular.isDefined(options.alarmSearchStatus) ?
  75 + options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any;
  76 + this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ?
  77 + options.alarmsPollingInterval : 5000;
  78 +
73 this.alarmSourceListener = null; 79 this.alarmSourceListener = null;
74 this.alarms = []; 80 this.alarms = [];
75 81
@@ -193,8 +199,7 @@ export default class Subscription { @@ -193,8 +199,7 @@ export default class Subscription {
193 registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { 199 registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
194 if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { 200 if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
195 subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); 201 subscription.timeWindowConfig = angular.copy(newDashboardTimewindow);
196 - subscription.unsubscribe();  
197 - subscription.subscribe(); 202 + subscription.update();
198 } 203 }
199 }); 204 });
200 this.registrations.push(registration); 205 this.registrations.push(registration);
@@ -281,8 +286,7 @@ export default class Subscription { @@ -281,8 +286,7 @@ export default class Subscription {
281 registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { 286 registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
282 if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { 287 if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
283 subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); 288 subscription.timeWindowConfig = angular.copy(newDashboardTimewindow);
284 - subscription.unsubscribe();  
285 - subscription.subscribe(); 289 + subscription.update();
286 } 290 }
287 }); 291 });
288 this.registrations.push(registration); 292 this.registrations.push(registration);
@@ -298,8 +302,7 @@ export default class Subscription { @@ -298,8 +302,7 @@ export default class Subscription {
298 return subscription.timeWindowConfig; 302 return subscription.timeWindowConfig;
299 }, function (newTimewindow, prevTimewindow) { 303 }, function (newTimewindow, prevTimewindow) {
300 if (!angular.equals(newTimewindow, prevTimewindow)) { 304 if (!angular.equals(newTimewindow, prevTimewindow)) {
301 - subscription.unsubscribe();  
302 - subscription.subscribe(); 305 + subscription.update();
303 } 306 }
304 }, true); 307 }, true);
305 this.registrations.push(this.timeWindowWatchRegistration); 308 this.registrations.push(this.timeWindowWatchRegistration);
@@ -502,8 +505,7 @@ export default class Subscription { @@ -502,8 +505,7 @@ export default class Subscription {
502 this.timeWindowConfig = angular.copy(this.originalTimewindow); 505 this.timeWindowConfig = angular.copy(this.originalTimewindow);
503 this.originalTimewindow = null; 506 this.originalTimewindow = null;
504 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 507 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
505 - this.unsubscribe();  
506 - this.subscribe(); 508 + this.update();
507 this.startWatchingTimewindow(); 509 this.startWatchingTimewindow();
508 } 510 }
509 } 511 }
@@ -519,8 +521,7 @@ export default class Subscription { @@ -519,8 +521,7 @@ export default class Subscription {
519 } 521 }
520 this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs); 522 this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs);
521 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 523 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
522 - this.unsubscribe();  
523 - this.subscribe(); 524 + this.update();
524 this.startWatchingTimewindow(); 525 this.startWatchingTimewindow();
525 } 526 }
526 } 527 }
@@ -618,6 +619,11 @@ export default class Subscription { @@ -618,6 +619,11 @@ export default class Subscription {
618 this.callbacks.legendDataUpdated(this, apply !== false); 619 this.callbacks.legendDataUpdated(this, apply !== false);
619 } 620 }
620 621
  622 + update() {
  623 + this.unsubscribe();
  624 + this.subscribe();
  625 + }
  626 +
621 subscribe() { 627 subscribe() {
622 if (this.type === this.ctx.types.widgetType.rpc.value) { 628 if (this.type === this.ctx.types.widgetType.rpc.value) {
623 return; 629 return;
@@ -688,6 +694,8 @@ export default class Subscription { @@ -688,6 +694,8 @@ export default class Subscription {
688 this.alarmSourceListener = { 694 this.alarmSourceListener = {
689 subscriptionTimewindow: this.subscriptionTimewindow, 695 subscriptionTimewindow: this.subscriptionTimewindow,
690 alarmSource: this.alarmSource, 696 alarmSource: this.alarmSource,
  697 + alarmSearchStatus: this.alarmSearchStatus,
  698 + alarmsPollingInterval: this.alarmsPollingInterval,
691 alarmsUpdated: function(alarms, apply) { 699 alarmsUpdated: function(alarms, apply) {
692 subscription.alarmsUpdated(alarms, apply); 700 subscription.alarmsUpdated(alarms, apply);
693 } 701 }
@@ -394,7 +394,8 @@ export default angular.module('thingsboard.types', []) @@ -394,7 +394,8 @@ export default angular.module('thingsboard.types', [])
394 cards: "cards" 394 cards: "cards"
395 }, 395 },
396 translate: { 396 translate: {
397 - dashboardStatePrefix: "dashboardState.state." 397 + dashboardStatePrefix: "dashboardState.state.",
  398 + keyLabelPrefix: "key.label."
398 } 399 }
399 } 400 }
400 ).name; 401 ).name;
@@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) { @@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
63 element.html(template); 63 element.html(template);
64 64
65 scope.types = types; 65 scope.types = types;
  66 +
  67 + scope.alarmFields = [];
  68 + for (var alarmField in types.alarmFields) {
  69 + scope.alarmFields.push(alarmField);
  70 + }
  71 +
66 scope.selectedKey = null; 72 scope.selectedKey = null;
67 scope.keySearchText = null; 73 scope.keySearchText = null;
68 scope.usePostProcessing = false; 74 scope.usePostProcessing = false;
@@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) { @@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
112 }, true); 118 }, true);
113 119
114 scope.keysSearch = function (searchText) { 120 scope.keysSearch = function (searchText) {
115 - if (scope.entityAlias) {  
116 - var deferred = $q.defer();  
117 - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: scope.model.type})  
118 - .then(function (keys) {  
119 - keys.push(searchText);  
120 - deferred.resolve(keys);  
121 - }, function (e) {  
122 - deferred.reject(e);  
123 - });  
124 - return deferred.promise; 121 + if (scope.model.type === types.dataKeyType.alarm) {
  122 + var dataKeys = searchText ? scope.alarmFields.filter(
  123 + scope.createFilterForDataKey(searchText)) : scope.alarmFields;
  124 + dataKeys.push(searchText);
  125 + return dataKeys;
125 } else { 126 } else {
126 - return $q.when([]); 127 + if (scope.entityAlias) {
  128 + var deferred = $q.defer();
  129 + scope.fetchEntityKeys({
  130 + entityAliasId: scope.entityAlias.id,
  131 + query: searchText,
  132 + type: scope.model.type
  133 + })
  134 + .then(function (keys) {
  135 + keys.push(searchText);
  136 + deferred.resolve(keys);
  137 + }, function (e) {
  138 + deferred.reject(e);
  139 + });
  140 + return deferred.promise;
  141 + } else {
  142 + return $q.when([]);
  143 + }
127 } 144 }
128 }; 145 };
129 146
  147 + scope.createFilterForDataKey = function (query) {
  148 + var lowercaseQuery = angular.lowercase(query);
  149 + return function filterFn(dataKey) {
  150 + return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0);
  151 + };
  152 + };
  153 +
130 $compile(element.contents())(scope); 154 $compile(element.contents())(scope);
131 } 155 }
132 156
@@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
16 16
17 --> 17 -->
18 <md-content class="md-padding" layout="column"> 18 <md-content class="md-padding" layout="column">
19 - <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute" 19 + <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries ||
  20 + model.type === types.dataKeyType.attribute ||
  21 + model.type === types.dataKeyType.alarm"
20 style="padding-bottom: 8px;" 22 style="padding-bottom: 8px;"
21 ng-required="true" 23 ng-required="true"
22 md-no-cache="true" 24 md-no-cache="true"
@@ -27,8 +29,8 @@ @@ -27,8 +29,8 @@
27 md-items="item in keysSearch(keySearchText)" 29 md-items="item in keysSearch(keySearchText)"
28 md-item-text="item" 30 md-item-text="item"
29 md-min-length="0" 31 md-min-length="0"
30 - placeholder="Key name"  
31 - md-floating-label="Key"> 32 + placeholder="{{ 'entity.key-name' | translate }}"
  33 + md-floating-label="{{ 'entity.key' | translate }}">
32 <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span> 34 <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span>
33 </md-autocomplete> 35 </md-autocomplete>
34 <div layout="row" layout-align="start center"> 36 <div layout="row" layout-align="start center">
@@ -48,7 +50,7 @@ @@ -48,7 +50,7 @@
48 md-color-history="false"> 50 md-color-history="false">
49 </div> 51 </div>
50 </div> 52 </div>
51 - <div layout="row" layout-align="start center"> 53 + <div layout="row" layout-align="start center" ng-if="model.type !== types.dataKeyType.alarm">
52 <md-input-container flex> 54 <md-input-container flex>
53 <label translate>datakey.units</label> 55 <label translate>datakey.units</label>
54 <input name="units" ng-model="model.units"> 56 <input name="units" ng-model="model.units">
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 @import '../../scss/constants'; 16 @import '../../scss/constants';
17 17
18 -.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete { 18 +.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
19 .tb-not-found { 19 .tb-not-found {
20 display: block; 20 display: block;
21 line-height: 1.5; 21 line-height: 1.5;
@@ -133,7 +133,7 @@ @@ -133,7 +133,7 @@
133 ng-required="true" 133 ng-required="true"
134 ng-model="alarmDataKeys" md-autocomplete-snap 134 ng-model="alarmDataKeys" md-autocomplete-snap
135 md-transform-chip="transformAlarmDataKeyChip($chip)" 135 md-transform-chip="transformAlarmDataKeyChip($chip)"
136 - md-require-match="true"> 136 + md-require-match="false">
137 <md-autocomplete 137 <md-autocomplete
138 md-no-cache="true" 138 md-no-cache="true"
139 id="alarm_datakey" 139 id="alarm_datakey"
@@ -152,6 +152,9 @@ @@ -152,6 +152,9 @@
152 </div> 152 </div>
153 <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> 153 <div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
154 <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span> 154 <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
  155 + <span>
  156 + <a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
  157 + </span>
155 </div> 158 </div>
156 </div> 159 </div>
157 </md-not-found> 160 </md-not-found>
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 } 26 }
27 } 27 }
28 28
29 - .tb-func-datakey-autocomplete { 29 + .tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
30 .tb-not-found { 30 .tb-not-found {
31 display: block; 31 display: block;
32 line-height: 1.5; 32 line-height: 1.5;
@@ -48,9 +48,9 @@ @@ -48,9 +48,9 @@
48 <span translate>device.no-keys-found</span> 48 <span translate>device.no-keys-found</span>
49 </div> 49 </div>
50 <div ng-if="textIsNotEmpty(dataKeySearchText)"> 50 <div ng-if="textIsNotEmpty(dataKeySearchText)">
51 - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span> 51 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
52 <span> 52 <span>
53 - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> 53 + <a translate ng-click="createKey($event, '#function_datakey_chips')">entity.create-new-key</a>
54 </span> 54 </span>
55 </div> 55 </div>
56 </div> 56 </div>
@@ -81,7 +81,7 @@ @@ -81,7 +81,7 @@
81 ng-required="true" 81 ng-required="true"
82 ng-model="alarmDataKeys" md-autocomplete-snap 82 ng-model="alarmDataKeys" md-autocomplete-snap
83 md-transform-chip="transformAlarmDataKeyChip($chip)" 83 md-transform-chip="transformAlarmDataKeyChip($chip)"
84 - md-require-match="true"> 84 + md-require-match="false">
85 <md-autocomplete 85 <md-autocomplete
86 md-no-cache="true" 86 md-no-cache="true"
87 id="alarm_datakey" 87 id="alarm_datakey"
@@ -100,6 +100,9 @@ @@ -100,6 +100,9 @@
100 </div> 100 </div>
101 <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> 101 <div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
102 <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span> 102 <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
  103 + <span>
  104 + <a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
  105 + </span>
103 </div> 106 </div>
104 </div> 107 </div>
105 </md-not-found> 108 </md-not-found>
@@ -144,6 +144,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout @@ -144,6 +144,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
144 scope.targetDeviceAlias.value = null; 144 scope.targetDeviceAlias.value = null;
145 } 145 }
146 } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { 146 } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) {
  147 + scope.alarmSearchStatus = angular.isDefined(config.alarmSearchStatus) ?
  148 + config.alarmSearchStatus : types.alarmSearchStatus.any;
  149 + scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ?
  150 + config.alarmsPollingInterval : 5;
147 if (config.alarmSource) { 151 if (config.alarmSource) {
148 scope.alarmSource.value = config.alarmSource; 152 scope.alarmSource.value = config.alarmSource;
149 } else { 153 } else {
@@ -205,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout @@ -205,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
205 }; 209 };
206 210
207 scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' + 211 scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' +
208 - 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () { 212 + 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' +
  213 + 'alarmSearchStatus + alarmsPollingInterval + showLegend', function () {
209 if (ngModelCtrl.$viewValue) { 214 if (ngModelCtrl.$viewValue) {
210 var value = ngModelCtrl.$viewValue; 215 var value = ngModelCtrl.$viewValue;
211 if (value.config) { 216 if (value.config) {
@@ -225,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout @@ -225,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
225 config.units = scope.units; 230 config.units = scope.units;
226 config.decimals = scope.decimals; 231 config.decimals = scope.decimals;
227 config.useDashboardTimewindow = scope.useDashboardTimewindow; 232 config.useDashboardTimewindow = scope.useDashboardTimewindow;
  233 + config.alarmSearchStatus = scope.alarmSearchStatus;
  234 + config.alarmsPollingInterval = scope.alarmsPollingInterval;
228 config.showLegend = scope.showLegend; 235 config.showLegend = scope.showLegend;
229 } 236 }
230 if (value.layout) { 237 if (value.layout) {
@@ -31,6 +31,30 @@ @@ -31,6 +31,30 @@
31 flex ng-model="timewindow"></tb-timewindow> 31 flex ng-model="timewindow"></tb-timewindow>
32 </section> 32 </section>
33 </div> 33 </div>
  34 + <div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center"
  35 + layout-gt-sm='row' layout-align-gt-sm="start center">
  36 + <md-input-container class="md-block" flex>
  37 + <label translate>alarm.alarm-status</label>
  38 + <md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;">
  39 + <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
  40 + {{ ('alarm.search-status.' + searchStatus) | translate }}
  41 + </md-option>
  42 + </md-select>
  43 + </md-input-container>
  44 + <md-input-container flex class="md-block">
  45 + <label translate>alarm.polling-interval</label>
  46 + <input ng-required="widgetType === types.widgetType.alarm.value"
  47 + type="number"
  48 + step="1"
  49 + min="1"
  50 + name="alarmsPollingInterval"
  51 + ng-model="alarmsPollingInterval"/>
  52 + <div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false">
  53 + <div ng-message="required" translate>alarm.polling-interval-required</div>
  54 + <div ng-message="min" translate>alarm.min-polling-interval-message</div>
  55 + </div>
  56 + </md-input-container>
  57 + </div>
34 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" 58 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
35 ng-show="widgetType !== types.widgetType.rpc.value 59 ng-show="widgetType !== types.widgetType.rpc.value
36 && widgetType !== types.widgetType.alarm.value 60 && widgetType !== types.widgetType.alarm.value
@@ -289,6 +289,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -289,6 +289,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
289 } 289 }
290 if (widget.type == types.widgetType.alarm.value) { 290 if (widget.type == types.widgetType.alarm.value) {
291 options.alarmSource = angular.copy(widget.config.alarmSource); 291 options.alarmSource = angular.copy(widget.config.alarmSource);
  292 + options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ?
  293 + widget.config.alarmSearchStatus : types.alarmSearchStatus.any;
  294 + options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ?
  295 + widget.config.alarmsPollingInterval * 1000 : 5000;
292 } else { 296 } else {
293 options.datasources = angular.copy(widget.config.datasources) 297 options.datasources = angular.copy(widget.config.datasources)
294 } 298 }
@@ -243,11 +243,9 @@ export default function EntityStateController($scope, $location, $state, $stateP @@ -243,11 +243,9 @@ export default function EntityStateController($scope, $location, $state, $stateP
243 } 243 }
244 244
245 function gotoState(stateId, update, openRightLayout) { 245 function gotoState(stateId, update, openRightLayout) {
246 - if (vm.dashboardCtrl.dashboardCtx.state != stateId) {  
247 - vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);  
248 - if (update) {  
249 - updateLocation();  
250 - } 246 + vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);
  247 + if (update) {
  248 + updateLocation();
251 } 249 }
252 } 250 }
253 251
@@ -148,7 +148,14 @@ export default angular.module('thingsboard.locale', []) @@ -148,7 +148,14 @@ export default angular.module('thingsboard.locale', [])
148 "clear": "Clear", 148 "clear": "Clear",
149 "search": "Search alarms", 149 "search": "Search alarms",
150 "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected", 150 "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected",
151 - "no-data": "No data to display" 151 + "no-data": "No data to display",
  152 + "polling-interval": "Alarms polling interval (sec)",
  153 + "polling-interval-required": "Alarms polling interval is required.",
  154 + "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
  155 + "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }",
  156 + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?",
  157 + "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }",
  158 + "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?"
152 }, 159 },
153 "alias": { 160 "alias": {
154 "add": "Add alias", 161 "add": "Add alias",
@@ -643,6 +650,8 @@ export default angular.module('thingsboard.locale', []) @@ -643,6 +650,8 @@ export default angular.module('thingsboard.locale', [])
643 "no-aliases-found": "No aliases found.", 650 "no-aliases-found": "No aliases found.",
644 "no-alias-matching": "'{{alias}}' not found.", 651 "no-alias-matching": "'{{alias}}' not found.",
645 "create-new-alias": "Create a new one!", 652 "create-new-alias": "Create a new one!",
  653 + "key": "Key",
  654 + "key-name": "Key name",
646 "no-keys-found": "No keys found.", 655 "no-keys-found": "No keys found.",
647 "no-key-matching": "'{{key}}' not found.", 656 "no-key-matching": "'{{key}}' not found.",
648 "create-new-key": "Create a new one!", 657 "create-new-key": "Create a new one!",
@@ -18,7 +18,8 @@ @@ -18,7 +18,8 @@
18 export default function ThingsboardMissingTranslateHandler($log, types) { 18 export default function ThingsboardMissingTranslateHandler($log, types) {
19 19
20 return function (translationId) { 20 return function (translationId) {
21 - if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) { 21 + if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix) &&
  22 + !translationId.startsWith(types.translate.keyLabelPrefix)) {
22 $log.warn('Translation for ' + translationId + ' doesn\'t exist'); 23 $log.warn('Translation for ' + translationId + ' doesn\'t exist');
23 } 24 }
24 }; 25 };
@@ -19,6 +19,7 @@ import './alarms-table-widget.scss'; @@ -19,6 +19,7 @@ import './alarms-table-widget.scss';
19 /* eslint-disable import/no-unresolved, import/default */ 19 /* eslint-disable import/no-unresolved, import/default */
20 20
21 import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; 21 import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
  22 +import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html';
22 23
23 /* eslint-enable import/no-unresolved, import/default */ 24 /* eslint-enable import/no-unresolved, import/default */
24 25
@@ -46,11 +47,12 @@ function AlarmsTableWidget() { @@ -46,11 +47,12 @@ function AlarmsTableWidget() {
46 } 47 }
47 48
48 /*@ngInject*/ 49 /*@ngInject*/
49 -function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUtil, $translate, utils, types) { 50 +function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, alarmService, utils, types) {
50 var vm = this; 51 var vm = this;
51 52
52 vm.stylesInfo = {}; 53 vm.stylesInfo = {};
53 vm.contentsInfo = {}; 54 vm.contentsInfo = {};
  55 + vm.columnWidth = {};
54 56
55 vm.showData = true; 57 vm.showData = true;
56 vm.hasData = false; 58 vm.hasData = false;
@@ -64,19 +66,32 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -64,19 +66,32 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
64 66
65 vm.currentAlarm = null; 67 vm.currentAlarm = null;
66 68
  69 + vm.alarmsTitle = $translate.instant('alarm.alarms');
  70 + vm.enableSelection = true;
  71 + vm.enableSearch = true;
  72 + vm.displayDetails = true;
  73 + vm.allowAcknowledgment = true;
  74 + vm.allowClear = true;
  75 + vm.displayPagination = true;
  76 + vm.defaultPageSize = 10;
  77 + vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value;
  78 +
67 vm.query = { 79 vm.query = {
68 - order: '-'+types.alarmFields.createdTime.value,  
69 - limit: 10, 80 + order: vm.defaultSortOrder,
  81 + limit: vm.defaultPageSize,
70 page: 1, 82 page: 1,
71 search: null 83 search: null
72 }; 84 };
73 85
74 - vm.alarmsTitle = $translate.instant('alarm.alarms');  
75 -  
76 vm.enterFilterMode = enterFilterMode; 86 vm.enterFilterMode = enterFilterMode;
77 vm.exitFilterMode = exitFilterMode; 87 vm.exitFilterMode = exitFilterMode;
78 vm.onReorder = onReorder; 88 vm.onReorder = onReorder;
79 vm.onPaginate = onPaginate; 89 vm.onPaginate = onPaginate;
  90 + vm.onRowClick = onRowClick;
  91 + vm.isCurrent = isCurrent;
  92 + vm.openAlarmDetails = openAlarmDetails;
  93 + vm.ackAlarms = ackAlarms;
  94 + vm.clearAlarms = clearAlarms;
80 95
81 vm.cellStyle = cellStyle; 96 vm.cellStyle = cellStyle;
82 vm.cellContent = cellContent; 97 vm.cellContent = cellContent;
@@ -119,7 +134,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -119,7 +134,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
119 $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) { 134 $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) {
120 vm.isGtMd = isGtMd; 135 vm.isGtMd = isGtMd;
121 if (vm.isGtMd) { 136 if (vm.isGtMd) {
122 - vm.limitOptions = [5, 10, 15]; 137 + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
123 } else { 138 } else {
124 vm.limitOptions = null; 139 vm.limitOptions = null;
125 } 140 }
@@ -130,7 +145,33 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -130,7 +145,33 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
130 if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { 145 if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
131 vm.alarmsTitle = vm.settings.alarmsTitle; 146 vm.alarmsTitle = vm.settings.alarmsTitle;
132 } 147 }
133 - //TODO: 148 + vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
  149 + vm.enableSearch = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
  150 + vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
  151 + vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
  152 + vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
  153 + if (!vm.allowAcknowledgment && !vm.allowClear) {
  154 + vm.enableSelection = false;
  155 + }
  156 +
  157 + vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
  158 +
  159 + var pageSize = vm.settings.defaultPageSize;
  160 + if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 0) {
  161 + vm.defaultPageSize = pageSize;
  162 + }
  163 +
  164 + if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) {
  165 + vm.defaultSortOrder = vm.settings.defaultSortOrder;
  166 + }
  167 +
  168 + vm.query.order = vm.defaultSortOrder;
  169 + vm.query.limit = vm.defaultPageSize;
  170 + if (vm.isGtMd) {
  171 + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
  172 + } else {
  173 + vm.limitOptions = null;
  174 + }
134 175
135 var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; 176 var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
136 var defaultColor = tinycolor(origColor); 177 var defaultColor = tinycolor(origColor);
@@ -207,6 +248,115 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -207,6 +248,115 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
207 updateAlarms(); 248 updateAlarms();
208 } 249 }
209 250
  251 + function onRowClick($event, alarm) {
  252 + if (vm.currentAlarm != alarm) {
  253 + vm.currentAlarm = alarm;
  254 + }
  255 + }
  256 +
  257 + function isCurrent(alarm) {
  258 + return (vm.currentAlarm && alarm && vm.currentAlarm.id && alarm.id) &&
  259 + (vm.currentAlarm.id.id === alarm.id.id);
  260 + }
  261 +
  262 + function openAlarmDetails($event, alarm) {
  263 + if (alarm && alarm.id) {
  264 + var onShowingCallback = {
  265 + onShowing: function(){}
  266 + }
  267 + $mdDialog.show({
  268 + controller: 'AlarmDetailsDialogController',
  269 + controllerAs: 'vm',
  270 + templateUrl: alarmDetailsDialogTemplate,
  271 + locals: {
  272 + alarmId: alarm.id.id,
  273 + allowAcknowledgment: vm.allowAcknowledgment,
  274 + allowClear: vm.allowClear,
  275 + showingCallback: onShowingCallback
  276 + },
  277 + parent: angular.element($document[0].body),
  278 + targetEvent: $event,
  279 + fullscreen: true,
  280 + skipHide: true,
  281 + onShowing: function(scope, element) {
  282 + onShowingCallback.onShowing(scope, element);
  283 + }
  284 + }).then(function (alarm) {
  285 + if (alarm) {
  286 + vm.subscription.update();
  287 + }
  288 + });
  289 +
  290 + }
  291 + }
  292 +
  293 + function ackAlarms($event) {
  294 + if ($event) {
  295 + $event.stopPropagation();
  296 + }
  297 + if (vm.selectedAlarms && vm.selectedAlarms.length > 0) {
  298 + var title = $translate.instant('alarm.aknowledge-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat');
  299 + var content = $translate.instant('alarm.aknowledge-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat');
  300 + var confirm = $mdDialog.confirm()
  301 + .targetEvent($event)
  302 + .title(title)
  303 + .htmlContent(content)
  304 + .ariaLabel(title)
  305 + .cancel($translate.instant('action.no'))
  306 + .ok($translate.instant('action.yes'));
  307 + $mdDialog.show(confirm).then(function () {
  308 + var tasks = [];
  309 + for (var i=0;i<vm.selectedAlarms.length;i++) {
  310 + var alarm = vm.selectedAlarms[i];
  311 + if (alarm.id) {
  312 + tasks.push(alarmService.ackAlarm(alarm.id.id));
  313 + }
  314 + }
  315 + if (tasks.length) {
  316 + $q.all(tasks).then(function () {
  317 + vm.selectedAlarms = [];
  318 + vm.subscription.update();
  319 + });
  320 + }
  321 +
  322 + });
  323 + }
  324 + }
  325 +
  326 + function clearAlarms($event) {
  327 + if ($event) {
  328 + $event.stopPropagation();
  329 + }
  330 + if (vm.selectedAlarms && vm.selectedAlarms.length > 0) {
  331 + var title = $translate.instant('alarm.clear-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat');
  332 + var content = $translate.instant('alarm.clear-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat');
  333 + var confirm = $mdDialog.confirm()
  334 + .targetEvent($event)
  335 + .title(title)
  336 + .htmlContent(content)
  337 + .ariaLabel(title)
  338 + .cancel($translate.instant('action.no'))
  339 + .ok($translate.instant('action.yes'));
  340 + $mdDialog.show(confirm).then(function () {
  341 + var tasks = [];
  342 + for (var i=0;i<vm.selectedAlarms.length;i++) {
  343 + var alarm = vm.selectedAlarms[i];
  344 + if (alarm.id) {
  345 + tasks.push(alarmService.clearAlarm(alarm.id.id));
  346 + }
  347 + }
  348 + if (tasks.length) {
  349 + $q.all(tasks).then(function () {
  350 + vm.selectedAlarms = [];
  351 + vm.subscription.update();
  352 + });
  353 + }
  354 +
  355 + });
  356 + }
  357 + }
  358 +
  359 +
210 function updateAlarms(preserveSelections) { 360 function updateAlarms(preserveSelections) {
211 if (!preserveSelections) { 361 if (!preserveSelections) {
212 vm.selectedAlarms = []; 362 vm.selectedAlarms = [];
@@ -216,8 +366,14 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -216,8 +366,14 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
216 result = $filter('filter')(result, {$: vm.query.search}); 366 result = $filter('filter')(result, {$: vm.query.search});
217 } 367 }
218 vm.alarmsCount = result.length; 368 vm.alarmsCount = result.length;
219 - var startIndex = vm.query.limit * (vm.query.page - 1);  
220 - vm.alarms = result.slice(startIndex, startIndex + vm.query.limit); 369 +
  370 + if (vm.displayPagination) {
  371 + var startIndex = vm.query.limit * (vm.query.page - 1);
  372 + vm.alarms = result.slice(startIndex, startIndex + vm.query.limit);
  373 + } else {
  374 + vm.alarms = result;
  375 + }
  376 +
221 if (preserveSelections) { 377 if (preserveSelections) {
222 var newSelectedAlarms = []; 378 var newSelectedAlarms = [];
223 if (vm.selectedAlarms && vm.selectedAlarms.length) { 379 if (vm.selectedAlarms && vm.selectedAlarms.length) {
@@ -251,6 +407,10 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -251,6 +407,10 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
251 style = defaultStyle(key, value); 407 style = defaultStyle(key, value);
252 } 408 }
253 } 409 }
  410 + if (!style.width) {
  411 + var columnWidth = vm.columnWidth[key.label];
  412 + style.width = columnWidth;
  413 + }
254 return style; 414 return style;
255 } 415 }
256 416
@@ -295,7 +455,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -295,7 +455,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
295 return value; 455 return value;
296 } 456 }
297 } else { 457 } else {
298 - return ''; 458 + return value;
299 } 459 }
300 } else { 460 } else {
301 return ''; 461 return '';
@@ -313,6 +473,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -313,6 +473,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
313 } else { 473 } else {
314 return {}; 474 return {};
315 } 475 }
  476 + } else {
  477 + return {};
316 } 478 }
317 } else { 479 } else {
318 return {}; 480 return {};
@@ -340,9 +502,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -340,9 +502,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
340 502
341 vm.stylesInfo = {}; 503 vm.stylesInfo = {};
342 vm.contentsInfo = {}; 504 vm.contentsInfo = {};
  505 + vm.columnWidth = {};
343 506
344 for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { 507 for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
345 var dataKey = vm.alarmSource.dataKeys[d]; 508 var dataKey = vm.alarmSource.dataKeys[d];
  509 +
  510 + var translationId = types.translate.keyLabelPrefix + dataKey.label;
  511 + var translation = $translate.instant(translationId);
  512 + if (translation != translationId) {
  513 + dataKey.title = translation;
  514 + } else {
  515 + dataKey.title = dataKey.label;
  516 + }
  517 +
346 var keySettings = dataKey.settings; 518 var keySettings = dataKey.settings;
347 519
348 var cellStyleFunction = null; 520 var cellStyleFunction = null;
@@ -384,6 +556,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti @@ -384,6 +556,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
384 useCellContentFunction: useCellContentFunction, 556 useCellContentFunction: useCellContentFunction,
385 cellContentFunction: cellContentFunction 557 cellContentFunction: cellContentFunction
386 }; 558 };
  559 +
  560 + var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
  561 + vm.columnWidth[dataKey.label] = columnWidth;
387 } 562 }
388 } 563 }
389 564
@@ -17,14 +17,14 @@ @@ -17,14 +17,14 @@
17 .tb-alarms-table { 17 .tb-alarms-table {
18 margin-top: 15px; 18 margin-top: 15px;
19 &.tb-data-table { 19 &.tb-data-table {
20 - table.md-table { 20 + table.md-table, table.md-table.md-row-select {
21 tbody { 21 tbody {
22 tr { 22 tr {
23 td { 23 td {
24 - &.ag-action-cell {  
25 - min-width: 40px;  
26 - max-width: 40px;  
27 - width: 40px; 24 + &.tb-action-cell {
  25 + min-width: 36px;
  26 + max-width: 36px;
  27 + width: 36px;
28 } 28 }
29 } 29 }
30 } 30 }
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <div class="md-toolbar-tools"> 22 <div class="md-toolbar-tools">
23 <span>{{ vm.alarmsTitle }}</span> 23 <span>{{ vm.alarmsTitle }}</span>
24 <span flex></span> 24 <span flex></span>
25 - <md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> 25 + <md-button ng-if="vm.enableSearch" class="md-icon-button" ng-click="vm.enterFilterMode()">
26 <md-icon>search</md-icon> 26 <md-icon>search</md-icon>
27 <md-tooltip md-direction="top"> 27 <md-tooltip md-direction="top">
28 {{ 'action.search' | translate }} 28 {{ 'action.search' | translate }}
@@ -57,13 +57,13 @@ @@ -57,13 +57,13 @@
57 translate-values="{count: vm.selectedAlarms.length}" 57 translate-values="{count: vm.selectedAlarms.length}"
58 translate-interpolation="messageformat"></span> 58 translate-interpolation="messageformat"></span>
59 <span flex></span> 59 <span flex></span>
60 - <md-button class="md-icon-button" ng-click="vm.ackAlarms($event)"> 60 + <md-button ng-if="vm.allowAcknowledgment" class="md-icon-button" ng-click="vm.ackAlarms($event)">
61 <md-icon>done</md-icon> 61 <md-icon>done</md-icon>
62 <md-tooltip md-direction="top"> 62 <md-tooltip md-direction="top">
63 {{ 'alarm.acknowledge' | translate }} 63 {{ 'alarm.acknowledge' | translate }}
64 </md-tooltip> 64 </md-tooltip>
65 </md-button> 65 </md-button>
66 - <md-button class="md-icon-button" ng-click="vm.clearAlarms($event)"> 66 + <md-button ng-if="vm.allowClear" class="md-icon-button" ng-click="vm.clearAlarms($event)">
67 <md-icon>clear</md-icon> 67 <md-icon>clear</md-icon>
68 <md-tooltip md-direction="top"> 68 <md-tooltip md-direction="top">
69 {{ 'alarm.clear' | translate }} 69 {{ 'alarm.clear' | translate }}
@@ -72,22 +72,22 @@ @@ -72,22 +72,22 @@
72 </div> 72 </div>
73 </md-toolbar> 73 </md-toolbar>
74 <md-table-container flex> 74 <md-table-container flex>
75 - <table md-table md-row-select multiple="" ng-model="vm.selectedAlarms"> 75 + <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms">
76 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> 76 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
77 <tr md-row> 77 <tr md-row>
78 - <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.label }}</span></th>  
79 - <th md-column><span>&nbsp</span></th> 78 + <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
  79 + <th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th>
80 </tr> 80 </tr>
81 </thead> 81 </thead>
82 <tbody md-body> 82 <tbody md-body>
83 <tr ng-show="vm.alarms.length" md-row md-select="alarm" 83 <tr ng-show="vm.alarms.length" md-row md-select="alarm"
84 md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms" 84 md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
85 ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}"> 85 ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
86 - <td md-cell ng-repeat="key in vm.alarmSource.dataKeys" 86 + <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
87 ng-style="vm.cellStyle(alarm, key)" 87 ng-style="vm.cellStyle(alarm, key)"
88 ng-bind-html="vm.cellContent(alarm, key)"> 88 ng-bind-html="vm.cellContent(alarm, key)">
89 </td> 89 </td>
90 - <td md-cell class="tb-action-cell"> 90 + <td md-cell ng-if="vm.displayDetails" class="tb-action-cell">
91 <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}" 91 <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
92 ng-click="vm.openAlarmDetails($event, alarm)"> 92 ng-click="vm.openAlarmDetails($event, alarm)">
93 <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon> 93 <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
@@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
104 layout-align="center center" 104 layout-align="center center"
105 class="no-data-found" translate>alarm.no-alarms-prompt</span> 105 class="no-data-found" translate>alarm.no-alarms-prompt</span>
106 </md-table-container> 106 </md-table-container>
107 - <md-table-pagination md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions" 107 + <md-table-pagination ng-if="vm.displayPagination" md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
108 md-page="vm.query.page" md-total="{{vm.alarmsCount}}" 108 md-page="vm.query.page" md-total="{{vm.alarmsCount}}"
109 md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd"> 109 md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd">
110 </md-table-pagination> 110 </md-table-pagination>
@@ -281,7 +281,55 @@ pre.tb-highlight { @@ -281,7 +281,55 @@ pre.tb-highlight {
281 display: flex; 281 display: flex;
282 } 282 }
283 table.md-table { 283 table.md-table {
  284 + &.md-row-select td.md-cell,
  285 + &.md-row-select th.md-column {
  286 + &:first-child {
  287 + width: 20px;
  288 + padding: 0 0 0 12px;
  289 + }
  290 +
  291 + &:nth-child(2) {
  292 + padding: 0 12px;
  293 + }
  294 +
  295 + &:nth-child(n+3):nth-last-child(n+2) {
  296 + padding: 0 28px 0 0;
  297 + }
  298 + }
  299 +
  300 + &:not(.md-row-select) td.md-cell,
  301 + &:not(.md-row-select) th.md-column {
  302 + &:first-child {
  303 + padding: 0 12px;
  304 + }
  305 +
  306 + &:nth-child(n+2):nth-last-child(n+2) {
  307 + padding: 0 28px 0 0;
  308 + }
  309 + }
  310 +
  311 + td.md-cell,
  312 + th.md-column {
  313 +
  314 + &:last-child {
  315 + padding: 0 12px 0 0;
  316 + }
  317 +
  318 + }
  319 + }
  320 +
  321 + table.md-table, table.md-table.md-row-select {
284 tbody { 322 tbody {
  323 + &.md-body {
  324 + tr {
  325 + &.md-row:not([disabled]) {
  326 + outline: none;
  327 + &.tb-current, &.tb-current:hover{
  328 + background-color: #dddddd !important;
  329 + }
  330 + }
  331 + }
  332 + }
285 tr { 333 tr {
286 td { 334 td {
287 &.tb-action-cell { 335 &.tb-action-cell {