Commit 531f42450f94c1e70d34e4c30fca51248fdbc6df
1 parent
0726fbb5
TB-63: Alarms widget initial implementation.
Showing
30 changed files
with
1370 additions
and
169 deletions
@@ -18,7 +18,29 @@ export default angular.module('thingsboard.api.alarm', []) | @@ -18,7 +18,29 @@ export default angular.module('thingsboard.api.alarm', []) | ||
18 | .name; | 18 | .name; |
19 | 19 | ||
20 | /*@ngInject*/ | 20 | /*@ngInject*/ |
21 | -function AlarmService($http, $q, $interval, $filter) { | 21 | +function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { |
22 | + | ||
23 | + var alarmSourceListeners = {}; | ||
24 | + | ||
25 | + var simulatedAlarm = { | ||
26 | + createdTime: (new Date).getTime(), | ||
27 | + startTs: (new Date).getTime(), | ||
28 | + endTs: 0, | ||
29 | + ackTs: 0, | ||
30 | + clearTs: 0, | ||
31 | + originatorName: 'Simulated', | ||
32 | + originator: { | ||
33 | + entityType: "DEVICE", | ||
34 | + id: "1" | ||
35 | + }, | ||
36 | + type: 'TEMPERATURE', | ||
37 | + severity: "MAJOR", | ||
38 | + status: types.alarmStatus.activeUnack, | ||
39 | + details: { | ||
40 | + message: "Temperature is high!" | ||
41 | + } | ||
42 | + }; | ||
43 | + | ||
22 | var service = { | 44 | var service = { |
23 | getAlarm: getAlarm, | 45 | getAlarm: getAlarm, |
24 | getAlarmInfo: getAlarmInfo, | 46 | getAlarmInfo: getAlarmInfo, |
@@ -27,7 +49,9 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -27,7 +49,9 @@ function AlarmService($http, $q, $interval, $filter) { | ||
27 | clearAlarm: clearAlarm, | 49 | clearAlarm: clearAlarm, |
28 | getAlarms: getAlarms, | 50 | getAlarms: getAlarms, |
29 | pollAlarms: pollAlarms, | 51 | pollAlarms: pollAlarms, |
30 | - cancelPollAlarms: cancelPollAlarms | 52 | + cancelPollAlarms: cancelPollAlarms, |
53 | + subscribeForAlarms: subscribeForAlarms, | ||
54 | + unsubscribeFromAlarms: unsubscribeFromAlarms | ||
31 | } | 55 | } |
32 | 56 | ||
33 | return service; | 57 | return service; |
@@ -171,12 +195,21 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -171,12 +195,21 @@ function AlarmService($http, $q, $interval, $filter) { | ||
171 | pageLink = { | 195 | pageLink = { |
172 | limit: alarmsQuery.limit | 196 | limit: alarmsQuery.limit |
173 | }; | 197 | }; |
174 | - } else { | 198 | + } else if (alarmsQuery.interval) { |
175 | pageLink = { | 199 | pageLink = { |
176 | limit: 100, | 200 | limit: 100, |
177 | startTime: time - alarmsQuery.interval | 201 | startTime: time - alarmsQuery.interval |
178 | }; | 202 | }; |
203 | + } else if (alarmsQuery.startTime) { | ||
204 | + pageLink = { | ||
205 | + limit: 100, | ||
206 | + startTime: alarmsQuery.startTime | ||
207 | + } | ||
208 | + if (alarmsQuery.endTime) { | ||
209 | + pageLink.endTime = alarmsQuery.endTime; | ||
210 | + } | ||
179 | } | 211 | } |
212 | + | ||
180 | fetchAlarms(alarmsQuery, pageLink, deferred); | 213 | fetchAlarms(alarmsQuery, pageLink, deferred); |
181 | return deferred.promise; | 214 | return deferred.promise; |
182 | } | 215 | } |
@@ -211,4 +244,59 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -211,4 +244,59 @@ function AlarmService($http, $q, $interval, $filter) { | ||
211 | } | 244 | } |
212 | } | 245 | } |
213 | 246 | ||
247 | + function subscribeForAlarms(alarmSourceListener) { | ||
248 | + alarmSourceListener.id = utils.guid(); | ||
249 | + alarmSourceListeners[alarmSourceListener.id] = alarmSourceListener; | ||
250 | + var alarmSource = alarmSourceListener.alarmSource; | ||
251 | + if (alarmSource.type == types.datasourceType.function) { | ||
252 | + $timeout(function() { | ||
253 | + alarmSourceListener.alarmsUpdated([simulatedAlarm], false); | ||
254 | + }); | ||
255 | + } else { | ||
256 | + var pollingInterval = 5000; //TODO: | ||
257 | + alarmSourceListener.alarmsQuery = { | ||
258 | + entityType: alarmSource.entityType, | ||
259 | + entityId: alarmSource.entityId, | ||
260 | + alarmSearchStatus: null, //TODO: | ||
261 | + alarmStatus: null | ||
262 | + } | ||
263 | + var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); | ||
264 | + if (originatorKeys && originatorKeys.length) { | ||
265 | + alarmSourceListener.alarmsQuery.fetchOriginator = true; | ||
266 | + } | ||
267 | + var subscriptionTimewindow = alarmSourceListener.subscriptionTimewindow; | ||
268 | + if (subscriptionTimewindow.realtimeWindowMs) { | ||
269 | + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.startTs; | ||
270 | + } else { | ||
271 | + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.fixedWindow.startTimeMs; | ||
272 | + alarmSourceListener.alarmsQuery.endTime = subscriptionTimewindow.fixedWindow.endTimeMs; | ||
273 | + } | ||
274 | + alarmSourceListener.alarmsQuery.onAlarms = function(alarms) { | ||
275 | + if (subscriptionTimewindow.realtimeWindowMs) { | ||
276 | + var now = Date.now(); | ||
277 | + if (alarmSourceListener.lastUpdateTs) { | ||
278 | + var interval = now - alarmSourceListener.lastUpdateTs; | ||
279 | + alarmSourceListener.alarmsQuery.startTime += interval; | ||
280 | + } else { | ||
281 | + alarmSourceListener.lastUpdateTs = now; | ||
282 | + } | ||
283 | + } | ||
284 | + alarmSourceListener.alarmsUpdated(alarms, false); | ||
285 | + } | ||
286 | + onPollAlarms(alarmSourceListener.alarmsQuery); | ||
287 | + alarmSourceListener.pollPromise = $interval(onPollAlarms, pollingInterval, | ||
288 | + 0, false, alarmSourceListener.alarmsQuery); | ||
289 | + } | ||
290 | + | ||
291 | + } | ||
292 | + | ||
293 | + function unsubscribeFromAlarms(alarmSourceListener) { | ||
294 | + if (alarmSourceListener && alarmSourceListener.id) { | ||
295 | + if (alarmSourceListener.pollPromise) { | ||
296 | + $interval.cancel(alarmSourceListener.pollPromise); | ||
297 | + alarmSourceListener.pollPromise = null; | ||
298 | + } | ||
299 | + delete alarmSourceListeners[alarmSourceListener.id]; | ||
300 | + } | ||
301 | + } | ||
214 | } | 302 | } |
@@ -14,8 +14,6 @@ | @@ -14,8 +14,6 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | -const varsRegex = /\$\{([^\}]*)\}/g; | ||
18 | - | ||
19 | export default class AliasController { | 17 | export default class AliasController { |
20 | 18 | ||
21 | constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { | 19 | constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { |
@@ -113,14 +111,14 @@ export default class AliasController { | @@ -113,14 +111,14 @@ export default class AliasController { | ||
113 | } | 111 | } |
114 | } | 112 | } |
115 | 113 | ||
116 | - resolveDatasource(datasource) { | 114 | + resolveDatasource(datasource, isSingle) { |
117 | var deferred = this.$q.defer(); | 115 | var deferred = this.$q.defer(); |
118 | if (datasource.type === this.types.datasourceType.entity) { | 116 | if (datasource.type === this.types.datasourceType.entity) { |
119 | if (datasource.entityAliasId) { | 117 | if (datasource.entityAliasId) { |
120 | this.getAliasInfo(datasource.entityAliasId).then( | 118 | this.getAliasInfo(datasource.entityAliasId).then( |
121 | function success(aliasInfo) { | 119 | function success(aliasInfo) { |
122 | datasource.aliasName = aliasInfo.alias; | 120 | datasource.aliasName = aliasInfo.alias; |
123 | - if (aliasInfo.resolveMultiple) { | 121 | + if (aliasInfo.resolveMultiple && !isSingle) { |
124 | var newDatasource; | 122 | var newDatasource; |
125 | var resolvedEntities = aliasInfo.resolvedEntities; | 123 | var resolvedEntities = aliasInfo.resolvedEntities; |
126 | if (resolvedEntities && resolvedEntities.length) { | 124 | if (resolvedEntities && resolvedEntities.length) { |
@@ -178,30 +176,44 @@ export default class AliasController { | @@ -178,30 +176,44 @@ export default class AliasController { | ||
178 | return deferred.promise; | 176 | return deferred.promise; |
179 | } | 177 | } |
180 | 178 | ||
179 | + resolveAlarmSource(alarmSource) { | ||
180 | + var deferred = this.$q.defer(); | ||
181 | + var aliasCtrl = this; | ||
182 | + this.resolveDatasource(alarmSource, true).then( | ||
183 | + function success(datasources) { | ||
184 | + var datasource = datasources[0]; | ||
185 | + if (datasource.type === aliasCtrl.types.datasourceType.function) { | ||
186 | + var name; | ||
187 | + if (datasource.name && datasource.name.length) { | ||
188 | + name = datasource.name; | ||
189 | + } else { | ||
190 | + name = aliasCtrl.types.datasourceType.function; | ||
191 | + } | ||
192 | + datasource.name = name; | ||
193 | + datasource.aliasName = name; | ||
194 | + datasource.entityName = name; | ||
195 | + } else if (datasource.unresolvedStateEntity) { | ||
196 | + datasource.name = "Unresolved"; | ||
197 | + datasource.entityName = "Unresolved"; | ||
198 | + } | ||
199 | + deferred.resolve(datasource); | ||
200 | + }, | ||
201 | + function fail() { | ||
202 | + deferred.reject(); | ||
203 | + } | ||
204 | + ); | ||
205 | + return deferred.promise; | ||
206 | + } | ||
207 | + | ||
181 | resolveDatasources(datasources) { | 208 | resolveDatasources(datasources) { |
182 | 209 | ||
210 | + var aliasCtrl = this; | ||
211 | + | ||
183 | function updateDataKeyLabel(dataKey, datasource) { | 212 | function updateDataKeyLabel(dataKey, datasource) { |
184 | if (!dataKey.pattern) { | 213 | if (!dataKey.pattern) { |
185 | dataKey.pattern = angular.copy(dataKey.label); | 214 | dataKey.pattern = angular.copy(dataKey.label); |
186 | } | 215 | } |
187 | - var pattern = dataKey.pattern; | ||
188 | - var label = dataKey.pattern; | ||
189 | - var match = varsRegex.exec(pattern); | ||
190 | - while (match !== null) { | ||
191 | - var variable = match[0]; | ||
192 | - var variableName = match[1]; | ||
193 | - if (variableName === 'dsName') { | ||
194 | - label = label.split(variable).join(datasource.name); | ||
195 | - } else if (variableName === 'entityName') { | ||
196 | - label = label.split(variable).join(datasource.entityName); | ||
197 | - } else if (variableName === 'deviceName') { | ||
198 | - label = label.split(variable).join(datasource.entityName); | ||
199 | - } else if (variableName === 'aliasName') { | ||
200 | - label = label.split(variable).join(datasource.aliasName); | ||
201 | - } | ||
202 | - match = varsRegex.exec(pattern); | ||
203 | - } | ||
204 | - dataKey.label = label; | 216 | + dataKey.label = aliasCtrl.utils.createLabelFromDatasource(datasource, dataKey.pattern); |
205 | } | 217 | } |
206 | 218 | ||
207 | function updateDatasourceKeyLabels(datasource) { | 219 | function updateDatasourceKeyLabels(datasource) { |
@@ -213,7 +225,7 @@ export default class AliasController { | @@ -213,7 +225,7 @@ export default class AliasController { | ||
213 | var deferred = this.$q.defer(); | 225 | var deferred = this.$q.defer(); |
214 | var newDatasources = angular.copy(datasources); | 226 | var newDatasources = angular.copy(datasources); |
215 | var datasorceResolveTasks = []; | 227 | var datasorceResolveTasks = []; |
216 | - var aliasCtrl = this; | 228 | + |
217 | newDatasources.forEach(function (datasource) { | 229 | newDatasources.forEach(function (datasource) { |
218 | var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); | 230 | var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); |
219 | datasorceResolveTasks.push(resolveDatasourceTask); | 231 | datasorceResolveTasks.push(resolveDatasourceTask); |
@@ -64,6 +64,39 @@ export default class Subscription { | @@ -64,6 +64,39 @@ export default class Subscription { | ||
64 | deferred.resolve(subscription); | 64 | deferred.resolve(subscription); |
65 | } | 65 | } |
66 | ); | 66 | ); |
67 | + } else if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
68 | + this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; | ||
69 | + this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; | ||
70 | + this.callbacks.dataLoading = this.callbacks.dataLoading || function(){}; | ||
71 | + this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){}; | ||
72 | + this.alarmSource = options.alarmSource; | ||
73 | + this.alarmSourceListener = null; | ||
74 | + this.alarms = []; | ||
75 | + | ||
76 | + this.originalTimewindow = null; | ||
77 | + this.timeWindow = { | ||
78 | + stDiff: this.ctx.stDiff | ||
79 | + } | ||
80 | + this.useDashboardTimewindow = options.useDashboardTimewindow; | ||
81 | + | ||
82 | + if (this.useDashboardTimewindow) { | ||
83 | + this.timeWindowConfig = angular.copy(options.dashboardTimewindow); | ||
84 | + } else { | ||
85 | + this.timeWindowConfig = angular.copy(options.timeWindowConfig); | ||
86 | + } | ||
87 | + | ||
88 | + this.subscriptionTimewindow = null; | ||
89 | + | ||
90 | + this.loadingData = false; | ||
91 | + this.displayLegend = false; | ||
92 | + this.initAlarmSubscription().then( | ||
93 | + function success() { | ||
94 | + deferred.resolve(subscription); | ||
95 | + }, | ||
96 | + function fail() { | ||
97 | + deferred.reject(); | ||
98 | + } | ||
99 | + ); | ||
67 | } else { | 100 | } else { |
68 | this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; | 101 | this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; |
69 | this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; | 102 | this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; |
@@ -132,6 +165,44 @@ export default class Subscription { | @@ -132,6 +165,44 @@ export default class Subscription { | ||
132 | return deferred.promise; | 165 | return deferred.promise; |
133 | } | 166 | } |
134 | 167 | ||
168 | + initAlarmSubscription() { | ||
169 | + var deferred = this.ctx.$q.defer(); | ||
170 | + if (!this.ctx.aliasController) { | ||
171 | + this.configureAlarmsData(); | ||
172 | + deferred.resolve(); | ||
173 | + } else { | ||
174 | + var subscription = this; | ||
175 | + this.ctx.aliasController.resolveAlarmSource(this.alarmSource).then( | ||
176 | + function success(alarmSource) { | ||
177 | + subscription.alarmSource = alarmSource; | ||
178 | + subscription.configureAlarmsData(); | ||
179 | + deferred.resolve(); | ||
180 | + }, | ||
181 | + function fail() { | ||
182 | + deferred.reject(); | ||
183 | + } | ||
184 | + ); | ||
185 | + } | ||
186 | + return deferred.promise; | ||
187 | + } | ||
188 | + | ||
189 | + configureAlarmsData() { | ||
190 | + var subscription = this; | ||
191 | + var registration; | ||
192 | + if (this.useDashboardTimewindow) { | ||
193 | + registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { | ||
194 | + if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { | ||
195 | + subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); | ||
196 | + subscription.unsubscribe(); | ||
197 | + subscription.subscribe(); | ||
198 | + } | ||
199 | + }); | ||
200 | + this.registrations.push(registration); | ||
201 | + } else { | ||
202 | + this.startWatchingTimewindow(); | ||
203 | + } | ||
204 | + } | ||
205 | + | ||
135 | initDataSubscription() { | 206 | initDataSubscription() { |
136 | var deferred = this.ctx.$q.defer(); | 207 | var deferred = this.ctx.$q.defer(); |
137 | if (!this.ctx.aliasController) { | 208 | if (!this.ctx.aliasController) { |
@@ -393,6 +464,8 @@ export default class Subscription { | @@ -393,6 +464,8 @@ export default class Subscription { | ||
393 | onAliasesChanged(aliasIds) { | 464 | onAliasesChanged(aliasIds) { |
394 | if (this.type === this.ctx.types.widgetType.rpc.value) { | 465 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
395 | return this.checkRpcTarget(aliasIds); | 466 | return this.checkRpcTarget(aliasIds); |
467 | + } else if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
468 | + return this.checkAlarmSource(aliasIds); | ||
396 | } else { | 469 | } else { |
397 | return this.checkSubscriptions(aliasIds); | 470 | return this.checkSubscriptions(aliasIds); |
398 | } | 471 | } |
@@ -516,6 +589,15 @@ export default class Subscription { | @@ -516,6 +589,15 @@ export default class Subscription { | ||
516 | } | 589 | } |
517 | } | 590 | } |
518 | 591 | ||
592 | + alarmsUpdated(alarms, apply) { | ||
593 | + this.notifyDataLoaded(); | ||
594 | + this.alarms = alarms; | ||
595 | + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { | ||
596 | + this.updateTimewindow(); | ||
597 | + } | ||
598 | + this.onDataUpdated(apply); | ||
599 | + } | ||
600 | + | ||
519 | updateLegend(dataIndex, data, apply) { | 601 | updateLegend(dataIndex, data, apply) { |
520 | var dataKey = this.legendData.keys[dataIndex].dataKey; | 602 | var dataKey = this.legendData.keys[dataIndex].dataKey; |
521 | var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; | 603 | var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; |
@@ -540,62 +622,104 @@ export default class Subscription { | @@ -540,62 +622,104 @@ export default class Subscription { | ||
540 | if (this.type === this.ctx.types.widgetType.rpc.value) { | 622 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
541 | return; | 623 | return; |
542 | } | 624 | } |
625 | + if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
626 | + this.alarmsSubscribe(); | ||
627 | + } else { | ||
628 | + this.notifyDataLoading(); | ||
629 | + if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { | ||
630 | + this.updateRealtimeSubscription(); | ||
631 | + if (this.subscriptionTimewindow.fixedWindow) { | ||
632 | + this.onDataUpdated(); | ||
633 | + } | ||
634 | + } | ||
635 | + var index = 0; | ||
636 | + for (var i = 0; i < this.datasources.length; i++) { | ||
637 | + var datasource = this.datasources[i]; | ||
638 | + if (angular.isFunction(datasource)) | ||
639 | + continue; | ||
640 | + | ||
641 | + var subscription = this; | ||
642 | + | ||
643 | + var listener = { | ||
644 | + subscriptionType: this.type, | ||
645 | + subscriptionTimewindow: this.subscriptionTimewindow, | ||
646 | + datasource: datasource, | ||
647 | + entityType: datasource.entityType, | ||
648 | + entityId: datasource.entityId, | ||
649 | + dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
650 | + subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
651 | + }, | ||
652 | + updateRealtimeSubscription: function () { | ||
653 | + this.subscriptionTimewindow = subscription.updateRealtimeSubscription(); | ||
654 | + return this.subscriptionTimewindow; | ||
655 | + }, | ||
656 | + setRealtimeSubscription: function (subscriptionTimewindow) { | ||
657 | + subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
658 | + }, | ||
659 | + datasourceIndex: index | ||
660 | + }; | ||
661 | + | ||
662 | + for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
663 | + this.data[index + a].data = []; | ||
664 | + } | ||
665 | + | ||
666 | + index += datasource.dataKeys.length; | ||
667 | + | ||
668 | + this.datasourceListeners.push(listener); | ||
669 | + this.ctx.datasourceService.subscribeToDatasource(listener); | ||
670 | + if (datasource.unresolvedStateEntity) { | ||
671 | + this.notifyDataLoaded(); | ||
672 | + this.onDataUpdated(); | ||
673 | + } | ||
674 | + | ||
675 | + } | ||
676 | + } | ||
677 | + } | ||
678 | + | ||
679 | + alarmsSubscribe() { | ||
543 | this.notifyDataLoading(); | 680 | this.notifyDataLoading(); |
544 | - if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { | 681 | + if (this.timeWindowConfig) { |
545 | this.updateRealtimeSubscription(); | 682 | this.updateRealtimeSubscription(); |
546 | if (this.subscriptionTimewindow.fixedWindow) { | 683 | if (this.subscriptionTimewindow.fixedWindow) { |
547 | this.onDataUpdated(); | 684 | this.onDataUpdated(); |
548 | } | 685 | } |
549 | } | 686 | } |
550 | - var index = 0; | ||
551 | - for (var i = 0; i < this.datasources.length; i++) { | ||
552 | - var datasource = this.datasources[i]; | ||
553 | - if (angular.isFunction(datasource)) | ||
554 | - continue; | ||
555 | - | ||
556 | - var subscription = this; | ||
557 | - | ||
558 | - var listener = { | ||
559 | - subscriptionType: this.type, | ||
560 | - subscriptionTimewindow: this.subscriptionTimewindow, | ||
561 | - datasource: datasource, | ||
562 | - entityType: datasource.entityType, | ||
563 | - entityId: datasource.entityId, | ||
564 | - dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
565 | - subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
566 | - }, | ||
567 | - updateRealtimeSubscription: function () { | ||
568 | - this.subscriptionTimewindow = subscription.updateRealtimeSubscription(); | ||
569 | - return this.subscriptionTimewindow; | ||
570 | - }, | ||
571 | - setRealtimeSubscription: function (subscriptionTimewindow) { | ||
572 | - subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
573 | - }, | ||
574 | - datasourceIndex: index | ||
575 | - }; | ||
576 | - | ||
577 | - for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
578 | - this.data[index + a].data = []; | 687 | + var subscription = this; |
688 | + this.alarmSourceListener = { | ||
689 | + subscriptionTimewindow: this.subscriptionTimewindow, | ||
690 | + alarmSource: this.alarmSource, | ||
691 | + alarmsUpdated: function(alarms, apply) { | ||
692 | + subscription.alarmsUpdated(alarms, apply); | ||
579 | } | 693 | } |
694 | + } | ||
695 | + this.alarms = []; | ||
580 | 696 | ||
581 | - index += datasource.dataKeys.length; | 697 | + this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener); |
582 | 698 | ||
583 | - this.datasourceListeners.push(listener); | ||
584 | - this.ctx.datasourceService.subscribeToDatasource(listener); | ||
585 | - if (datasource.unresolvedStateEntity) { | ||
586 | - this.notifyDataLoaded(); | ||
587 | - this.onDataUpdated(); | ||
588 | - } | 699 | + if (this.alarmSource.unresolvedStateEntity) { |
700 | + this.notifyDataLoaded(); | ||
701 | + this.onDataUpdated(); | ||
589 | } | 702 | } |
590 | } | 703 | } |
591 | 704 | ||
592 | unsubscribe() { | 705 | unsubscribe() { |
593 | if (this.type !== this.ctx.types.widgetType.rpc.value) { | 706 | if (this.type !== this.ctx.types.widgetType.rpc.value) { |
594 | - for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
595 | - var listener = this.datasourceListeners[i]; | ||
596 | - this.ctx.datasourceService.unsubscribeFromDatasource(listener); | 707 | + if (this.type == this.ctx.types.widgetType.alarm.value) { |
708 | + this.alarmsUnsubscribe(); | ||
709 | + } else { | ||
710 | + for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
711 | + var listener = this.datasourceListeners[i]; | ||
712 | + this.ctx.datasourceService.unsubscribeFromDatasource(listener); | ||
713 | + } | ||
714 | + this.datasourceListeners = []; | ||
597 | } | 715 | } |
598 | - this.datasourceListeners = []; | 716 | + } |
717 | + } | ||
718 | + | ||
719 | + alarmsUnsubscribe() { | ||
720 | + if (this.alarmSourceListener) { | ||
721 | + this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener); | ||
722 | + this.alarmSourceListener = null; | ||
599 | } | 723 | } |
600 | } | 724 | } |
601 | 725 | ||
@@ -607,6 +731,14 @@ export default class Subscription { | @@ -607,6 +731,14 @@ export default class Subscription { | ||
607 | } | 731 | } |
608 | } | 732 | } |
609 | 733 | ||
734 | + checkAlarmSource(aliasIds) { | ||
735 | + if (this.alarmSource && this.alarmSource.entityAliasId) { | ||
736 | + return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1; | ||
737 | + } else { | ||
738 | + return false; | ||
739 | + } | ||
740 | + } | ||
741 | + | ||
610 | checkSubscriptions(aliasIds) { | 742 | checkSubscriptions(aliasIds) { |
611 | var subscriptionsChanged = false; | 743 | var subscriptionsChanged = false; |
612 | for (var i = 0; i < this.datasourceListeners.length; i++) { | 744 | for (var i = 0; i < this.datasourceListeners.length; i++) { |
@@ -19,6 +19,7 @@ import tinycolor from 'tinycolor2'; | @@ -19,6 +19,7 @@ import tinycolor from 'tinycolor2'; | ||
19 | 19 | ||
20 | import thingsboardLedLight from '../components/led-light.directive'; | 20 | import thingsboardLedLight from '../components/led-light.directive'; |
21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; | 21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; |
22 | +import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; | ||
22 | 23 | ||
23 | import TbFlot from '../widget/lib/flot-widget'; | 24 | import TbFlot from '../widget/lib/flot-widget'; |
24 | import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; | 25 | import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; |
@@ -33,7 +34,7 @@ import thingsboardTypes from '../common/types.constant'; | @@ -33,7 +34,7 @@ import thingsboardTypes from '../common/types.constant'; | ||
33 | import thingsboardUtils from '../common/utils.service'; | 34 | import thingsboardUtils from '../common/utils.service'; |
34 | 35 | ||
35 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, | 36 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, |
36 | - thingsboardTypes, thingsboardUtils]) | 37 | + thingsboardAlarmsTableWidget, thingsboardTypes, thingsboardUtils]) |
37 | .factory('widgetService', WidgetService) | 38 | .factory('widgetService', WidgetService) |
38 | .name; | 39 | .name; |
39 | 40 |
@@ -59,6 +59,53 @@ export default angular.module('thingsboard.types', []) | @@ -59,6 +59,53 @@ export default angular.module('thingsboard.types', []) | ||
59 | name: "aggregation.none" | 59 | name: "aggregation.none" |
60 | } | 60 | } |
61 | }, | 61 | }, |
62 | + alarmFields: { | ||
63 | + createdTime: { | ||
64 | + value: "createdTime", | ||
65 | + name: "alarm.created-time", | ||
66 | + time: true | ||
67 | + }, | ||
68 | + startTime: { | ||
69 | + value: "startTs", | ||
70 | + name: "alarm.start-time", | ||
71 | + time: true | ||
72 | + }, | ||
73 | + endTime: { | ||
74 | + value: "endTs", | ||
75 | + name: "alarm.end-time", | ||
76 | + time: true | ||
77 | + }, | ||
78 | + ackTime: { | ||
79 | + value: "ackTs", | ||
80 | + name: "alarm.ack-time", | ||
81 | + time: true | ||
82 | + }, | ||
83 | + clearTime: { | ||
84 | + value: "clearTs", | ||
85 | + name: "alarm.clear-time", | ||
86 | + time: true | ||
87 | + }, | ||
88 | + originator: { | ||
89 | + value: "originatorName", | ||
90 | + name: "alarm.originator" | ||
91 | + }, | ||
92 | + originatorType: { | ||
93 | + value: "originator.entityType", | ||
94 | + name: "alarm.originator-type" | ||
95 | + }, | ||
96 | + type: { | ||
97 | + value: "type", | ||
98 | + name: "alarm.type" | ||
99 | + }, | ||
100 | + severity: { | ||
101 | + value: "severity", | ||
102 | + name: "alarm.severity" | ||
103 | + }, | ||
104 | + status: { | ||
105 | + value: "status", | ||
106 | + name: "alarm.status" | ||
107 | + } | ||
108 | + }, | ||
62 | alarmStatus: { | 109 | alarmStatus: { |
63 | activeUnack: "ACTIVE_UNACK", | 110 | activeUnack: "ACTIVE_UNACK", |
64 | activeAck: "ACTIVE_ACK", | 111 | activeAck: "ACTIVE_ACK", |
@@ -75,23 +122,28 @@ export default angular.module('thingsboard.types', []) | @@ -75,23 +122,28 @@ export default angular.module('thingsboard.types', []) | ||
75 | alarmSeverity: { | 122 | alarmSeverity: { |
76 | "CRITICAL": { | 123 | "CRITICAL": { |
77 | name: "alarm.severity-critical", | 124 | name: "alarm.severity-critical", |
78 | - class: "tb-critical" | 125 | + class: "tb-critical", |
126 | + color: "red" | ||
79 | }, | 127 | }, |
80 | "MAJOR": { | 128 | "MAJOR": { |
81 | name: "alarm.severity-major", | 129 | name: "alarm.severity-major", |
82 | - class: "tb-major" | 130 | + class: "tb-major", |
131 | + color: "orange" | ||
83 | }, | 132 | }, |
84 | "MINOR": { | 133 | "MINOR": { |
85 | name: "alarm.severity-minor", | 134 | name: "alarm.severity-minor", |
86 | - class: "tb-minor" | 135 | + class: "tb-minor", |
136 | + color: "#ffca3d" | ||
87 | }, | 137 | }, |
88 | "WARNING": { | 138 | "WARNING": { |
89 | name: "alarm.severity-warning", | 139 | name: "alarm.severity-warning", |
90 | - class: "tb-warning" | 140 | + class: "tb-warning", |
141 | + color: "#abab00" | ||
91 | }, | 142 | }, |
92 | "INDETERMINATE": { | 143 | "INDETERMINATE": { |
93 | name: "alarm.severity-indeterminate", | 144 | name: "alarm.severity-indeterminate", |
94 | - class: "tb-indeterminate" | 145 | + class: "tb-indeterminate", |
146 | + color: "green" | ||
95 | } | 147 | } |
96 | }, | 148 | }, |
97 | aliasFilterType: { | 149 | aliasFilterType: { |
@@ -153,7 +205,8 @@ export default angular.module('thingsboard.types', []) | @@ -153,7 +205,8 @@ export default angular.module('thingsboard.types', []) | ||
153 | dataKeyType: { | 205 | dataKeyType: { |
154 | timeseries: "timeseries", | 206 | timeseries: "timeseries", |
155 | attribute: "attribute", | 207 | attribute: "attribute", |
156 | - function: "function" | 208 | + function: "function", |
209 | + alarm: "alarm" | ||
157 | }, | 210 | }, |
158 | componentType: { | 211 | componentType: { |
159 | filter: "FILTER", | 212 | filter: "FILTER", |
@@ -319,6 +372,14 @@ export default angular.module('thingsboard.types', []) | @@ -319,6 +372,14 @@ export default angular.module('thingsboard.types', []) | ||
319 | alias: "basic_gpio_control" | 372 | alias: "basic_gpio_control" |
320 | } | 373 | } |
321 | }, | 374 | }, |
375 | + alarm: { | ||
376 | + value: "alarm", | ||
377 | + name: "widget.alarm", | ||
378 | + template: { | ||
379 | + bundleAlias: "alarm_widgets", | ||
380 | + alias: "alarms_table" | ||
381 | + } | ||
382 | + }, | ||
322 | static: { | 383 | static: { |
323 | value: "static", | 384 | value: "static", |
324 | name: "widget.static", | 385 | name: "widget.static", |
@@ -21,8 +21,10 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | @@ -21,8 +21,10 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | ||
21 | .factory('utils', Utils) | 21 | .factory('utils', Utils) |
22 | .name; | 22 | .name; |
23 | 23 | ||
24 | +const varsRegex = /\$\{([^\}]*)\}/g; | ||
25 | + | ||
24 | /*@ngInject*/ | 26 | /*@ngInject*/ |
25 | -function Utils($mdColorPalette, $rootScope, $window, types) { | 27 | +function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
26 | 28 | ||
27 | var predefinedFunctions = {}, | 29 | var predefinedFunctions = {}, |
28 | predefinedFunctionsList = [], | 30 | predefinedFunctionsList = [], |
@@ -93,9 +95,32 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -93,9 +95,32 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
93 | dataKeys: [angular.copy(defaultDataKey)] | 95 | dataKeys: [angular.copy(defaultDataKey)] |
94 | }; | 96 | }; |
95 | 97 | ||
98 | + var defaultAlarmFields = [ | ||
99 | + 'createdTime', | ||
100 | + 'originator', | ||
101 | + 'type', | ||
102 | + 'severity', | ||
103 | + 'status' | ||
104 | + ]; | ||
105 | + | ||
106 | + var defaultAlarmDataKeys = []; | ||
107 | + for (var i=0;i<defaultAlarmFields.length;i++) { | ||
108 | + var name = defaultAlarmFields[i]; | ||
109 | + var dataKey = { | ||
110 | + name: name, | ||
111 | + type: types.dataKeyType.alarm, | ||
112 | + label: $translate.instant(types.alarmFields[name].name)+'', | ||
113 | + color: getMaterialColor(i), | ||
114 | + settings: {}, | ||
115 | + _hash: Math.random() | ||
116 | + }; | ||
117 | + defaultAlarmDataKeys.push(dataKey); | ||
118 | + } | ||
119 | + | ||
96 | var service = { | 120 | var service = { |
97 | getDefaultDatasource: getDefaultDatasource, | 121 | getDefaultDatasource: getDefaultDatasource, |
98 | getDefaultDatasourceJson: getDefaultDatasourceJson, | 122 | getDefaultDatasourceJson: getDefaultDatasourceJson, |
123 | + getDefaultAlarmDataKeys: getDefaultAlarmDataKeys, | ||
99 | getMaterialColor: getMaterialColor, | 124 | getMaterialColor: getMaterialColor, |
100 | getPredefinedFunctionBody: getPredefinedFunctionBody, | 125 | getPredefinedFunctionBody: getPredefinedFunctionBody, |
101 | getPredefinedFunctionsList: getPredefinedFunctionsList, | 126 | getPredefinedFunctionsList: getPredefinedFunctionsList, |
@@ -109,7 +134,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -109,7 +134,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
109 | cleanCopy: cleanCopy, | 134 | cleanCopy: cleanCopy, |
110 | isLocalUrl: isLocalUrl, | 135 | isLocalUrl: isLocalUrl, |
111 | validateDatasources: validateDatasources, | 136 | validateDatasources: validateDatasources, |
112 | - createKey: createKey | 137 | + createKey: createKey, |
138 | + createLabelFromDatasource: createLabelFromDatasource | ||
113 | } | 139 | } |
114 | 140 | ||
115 | return service; | 141 | return service; |
@@ -212,6 +238,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -212,6 +238,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
212 | return angular.toJson(getDefaultDatasource(dataKeySchema)); | 238 | return angular.toJson(getDefaultDatasource(dataKeySchema)); |
213 | } | 239 | } |
214 | 240 | ||
241 | + function getDefaultAlarmDataKeys() { | ||
242 | + return angular.copy(defaultAlarmDataKeys); | ||
243 | + } | ||
244 | + | ||
215 | function isDescriptorSchemaNotEmpty(descriptor) { | 245 | function isDescriptorSchemaNotEmpty(descriptor) { |
216 | if (descriptor && descriptor.schema && descriptor.schema.properties) { | 246 | if (descriptor && descriptor.schema && descriptor.schema.properties) { |
217 | for(var prop in descriptor.schema.properties) { | 247 | for(var prop in descriptor.schema.properties) { |
@@ -357,4 +387,24 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -357,4 +387,24 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
357 | return dataKey; | 387 | return dataKey; |
358 | } | 388 | } |
359 | 389 | ||
390 | + function createLabelFromDatasource(datasource, pattern) { | ||
391 | + var label = angular.copy(pattern); | ||
392 | + var match = varsRegex.exec(pattern); | ||
393 | + while (match !== null) { | ||
394 | + var variable = match[0]; | ||
395 | + var variableName = match[1]; | ||
396 | + if (variableName === 'dsName') { | ||
397 | + label = label.split(variable).join(datasource.name); | ||
398 | + } else if (variableName === 'entityName') { | ||
399 | + label = label.split(variable).join(datasource.entityName); | ||
400 | + } else if (variableName === 'deviceName') { | ||
401 | + label = label.split(variable).join(datasource.entityName); | ||
402 | + } else if (variableName === 'aliasName') { | ||
403 | + label = label.split(variable).join(datasource.aliasName); | ||
404 | + } | ||
405 | + match = varsRegex.exec(pattern); | ||
406 | + } | ||
407 | + return label; | ||
408 | + } | ||
409 | + | ||
360 | } | 410 | } |
@@ -181,6 +181,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -181,6 +181,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
181 | vm.dropWidgetShadow = dropWidgetShadow; | 181 | vm.dropWidgetShadow = dropWidgetShadow; |
182 | vm.enableWidgetFullscreen = enableWidgetFullscreen; | 182 | vm.enableWidgetFullscreen = enableWidgetFullscreen; |
183 | vm.hasTimewindow = hasTimewindow; | 183 | vm.hasTimewindow = hasTimewindow; |
184 | + vm.hasAggregation = hasAggregation; | ||
184 | vm.editWidget = editWidget; | 185 | vm.editWidget = editWidget; |
185 | vm.exportWidget = exportWidget; | 186 | vm.exportWidget = exportWidget; |
186 | vm.removeWidget = removeWidget; | 187 | vm.removeWidget = removeWidget; |
@@ -771,7 +772,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -771,7 +772,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
771 | } | 772 | } |
772 | 773 | ||
773 | function hasTimewindow(widget) { | 774 | function hasTimewindow(widget) { |
774 | - if (widget.type === types.widgetType.timeseries.value) { | 775 | + if (widget.type === types.widgetType.timeseries.value || widget.type === types.widgetType.alarm.value) { |
775 | return angular.isDefined(widget.config.useDashboardTimewindow) ? | 776 | return angular.isDefined(widget.config.useDashboardTimewindow) ? |
776 | !widget.config.useDashboardTimewindow : false; | 777 | !widget.config.useDashboardTimewindow : false; |
777 | } else { | 778 | } else { |
@@ -779,6 +780,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -779,6 +780,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
779 | } | 780 | } |
780 | } | 781 | } |
781 | 782 | ||
783 | + function hasAggregation(widget) { | ||
784 | + return widget.type === types.widgetType.timeseries.value; | ||
785 | + } | ||
786 | + | ||
782 | function adoptMaxRows() { | 787 | function adoptMaxRows() { |
783 | if (vm.widgets) { | 788 | if (vm.widgets) { |
784 | var maxRows = vm.gridsterOpts.maxRows; | 789 | var maxRows = vm.gridsterOpts.maxRows; |
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | padding: vm.widgetPadding(widget)}"> | 47 | padding: vm.widgetPadding(widget)}"> |
48 | <div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> | 48 | <div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> |
49 | <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span> | 49 | <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span> |
50 | - <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | 50 | + <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> |
51 | </div> | 51 | </div> |
52 | <div class="tb-widget-actions" layout="row" layout-align="start center" tb-mousedown="$event.stopPropagation()"> | 52 | <div class="tb-widget-actions" layout="row" layout-align="start center" tb-mousedown="$event.stopPropagation()"> |
53 | <md-button id="expand-button" | 53 | <md-button id="expand-button" |
@@ -45,12 +45,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -45,12 +45,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
45 | scope.ngModelCtrl = ngModelCtrl; | 45 | scope.ngModelCtrl = ngModelCtrl; |
46 | scope.types = types; | 46 | scope.types = types; |
47 | 47 | ||
48 | + scope.alarmFields = []; | ||
49 | + for (var alarmField in types.alarmFields) { | ||
50 | + scope.alarmFields.push(alarmField); | ||
51 | + } | ||
52 | + | ||
48 | scope.selectedTimeseriesDataKey = null; | 53 | scope.selectedTimeseriesDataKey = null; |
49 | scope.timeseriesDataKeySearchText = null; | 54 | scope.timeseriesDataKeySearchText = null; |
50 | 55 | ||
51 | scope.selectedAttributeDataKey = null; | 56 | scope.selectedAttributeDataKey = null; |
52 | scope.attributeDataKeySearchText = null; | 57 | scope.attributeDataKeySearchText = null; |
53 | 58 | ||
59 | + scope.selectedAlarmDataKey = null; | ||
60 | + scope.alarmDataKeySearchText = null; | ||
61 | + | ||
54 | scope.updateValidity = function () { | 62 | scope.updateValidity = function () { |
55 | if (ngModelCtrl.$viewValue) { | 63 | if (ngModelCtrl.$viewValue) { |
56 | var value = ngModelCtrl.$viewValue; | 64 | var value = ngModelCtrl.$viewValue; |
@@ -81,24 +89,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -81,24 +89,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
81 | }); | 89 | }); |
82 | 90 | ||
83 | scope.$watch('timeseriesDataKeys', function () { | 91 | scope.$watch('timeseriesDataKeys', function () { |
84 | - if (ngModelCtrl.$viewValue) { | ||
85 | - var dataKeys = []; | ||
86 | - dataKeys = dataKeys.concat(scope.timeseriesDataKeys); | ||
87 | - dataKeys = dataKeys.concat(scope.attributeDataKeys); | ||
88 | - ngModelCtrl.$viewValue.dataKeys = dataKeys; | ||
89 | - scope.updateValidity(); | ||
90 | - } | 92 | + updateDataKeys(); |
91 | }, true); | 93 | }, true); |
92 | 94 | ||
93 | scope.$watch('attributeDataKeys', function () { | 95 | scope.$watch('attributeDataKeys', function () { |
96 | + updateDataKeys(); | ||
97 | + }, true); | ||
98 | + | ||
99 | + scope.$watch('alarmDataKeys', function () { | ||
100 | + updateDataKeys(); | ||
101 | + }, true); | ||
102 | + | ||
103 | + function updateDataKeys() { | ||
94 | if (ngModelCtrl.$viewValue) { | 104 | if (ngModelCtrl.$viewValue) { |
95 | var dataKeys = []; | 105 | var dataKeys = []; |
96 | dataKeys = dataKeys.concat(scope.timeseriesDataKeys); | 106 | dataKeys = dataKeys.concat(scope.timeseriesDataKeys); |
97 | dataKeys = dataKeys.concat(scope.attributeDataKeys); | 107 | dataKeys = dataKeys.concat(scope.attributeDataKeys); |
108 | + dataKeys = dataKeys.concat(scope.alarmDataKeys); | ||
98 | ngModelCtrl.$viewValue.dataKeys = dataKeys; | 109 | ngModelCtrl.$viewValue.dataKeys = dataKeys; |
99 | scope.updateValidity(); | 110 | scope.updateValidity(); |
100 | } | 111 | } |
101 | - }, true); | 112 | + } |
102 | 113 | ||
103 | ngModelCtrl.$render = function () { | 114 | ngModelCtrl.$render = function () { |
104 | if (ngModelCtrl.$viewValue) { | 115 | if (ngModelCtrl.$viewValue) { |
@@ -111,16 +122,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -111,16 +122,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
111 | } | 122 | } |
112 | var timeseriesDataKeys = []; | 123 | var timeseriesDataKeys = []; |
113 | var attributeDataKeys = []; | 124 | var attributeDataKeys = []; |
125 | + var alarmDataKeys = []; | ||
114 | for (var d in ngModelCtrl.$viewValue.dataKeys) { | 126 | for (var d in ngModelCtrl.$viewValue.dataKeys) { |
115 | var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; | 127 | var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; |
116 | if (dataKey.type === types.dataKeyType.timeseries) { | 128 | if (dataKey.type === types.dataKeyType.timeseries) { |
117 | timeseriesDataKeys.push(dataKey); | 129 | timeseriesDataKeys.push(dataKey); |
118 | } else if (dataKey.type === types.dataKeyType.attribute) { | 130 | } else if (dataKey.type === types.dataKeyType.attribute) { |
119 | attributeDataKeys.push(dataKey); | 131 | attributeDataKeys.push(dataKey); |
132 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
133 | + alarmDataKeys.push(dataKey); | ||
120 | } | 134 | } |
121 | } | 135 | } |
122 | scope.timeseriesDataKeys = timeseriesDataKeys; | 136 | scope.timeseriesDataKeys = timeseriesDataKeys; |
123 | scope.attributeDataKeys = attributeDataKeys; | 137 | scope.attributeDataKeys = attributeDataKeys; |
138 | + scope.alarmDataKeys = alarmDataKeys; | ||
124 | } | 139 | } |
125 | }; | 140 | }; |
126 | 141 | ||
@@ -135,6 +150,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -135,6 +150,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
135 | if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { | 150 | if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { |
136 | scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; | 151 | scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; |
137 | } | 152 | } |
153 | + if (!scope.alarmDataKeySearchText || scope.alarmDataKeySearchText === '') { | ||
154 | + scope.alarmDataKeySearchText = scope.alarmDataKeySearchText === '' ? null : ''; | ||
155 | + } | ||
138 | }; | 156 | }; |
139 | 157 | ||
140 | scope.transformTimeseriesDataKeyChip = function (chip) { | 158 | scope.transformTimeseriesDataKeyChip = function (chip) { |
@@ -145,6 +163,10 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -145,6 +163,10 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
145 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); | 163 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); |
146 | }; | 164 | }; |
147 | 165 | ||
166 | + scope.transformAlarmDataKeyChip = function (chip) { | ||
167 | + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); | ||
168 | + }; | ||
169 | + | ||
148 | scope.showColorPicker = function (event, dataKey) { | 170 | scope.showColorPicker = function (event, dataKey) { |
149 | $mdColorPicker.show({ | 171 | $mdColorPicker.show({ |
150 | value: dataKey.color, | 172 | value: dataKey.color, |
@@ -196,6 +218,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -196,6 +218,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
196 | scope.timeseriesDataKeys[index] = dataKey; | 218 | scope.timeseriesDataKeys[index] = dataKey; |
197 | } else if (dataKey.type === types.dataKeyType.attribute) { | 219 | } else if (dataKey.type === types.dataKeyType.attribute) { |
198 | scope.attributeDataKeys[index] = dataKey; | 220 | scope.attributeDataKeys[index] = dataKey; |
221 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
222 | + scope.alarmDataKeys[index] = dataKey; | ||
199 | } | 223 | } |
200 | ngModelCtrl.$setDirty(); | 224 | ngModelCtrl.$setDirty(); |
201 | }, function () { | 225 | }, function () { |
@@ -203,20 +227,33 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -203,20 +227,33 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
203 | }; | 227 | }; |
204 | 228 | ||
205 | scope.dataKeysSearch = function (searchText, type) { | 229 | scope.dataKeysSearch = function (searchText, type) { |
206 | - if (scope.entityAlias) { | ||
207 | - var deferred = $q.defer(); | ||
208 | - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type}) | ||
209 | - .then(function (dataKeys) { | ||
210 | - deferred.resolve(dataKeys); | ||
211 | - }, function (e) { | ||
212 | - deferred.reject(e); | ||
213 | - }); | ||
214 | - return deferred.promise; | 230 | + if (scope.widgetType == types.widgetType.alarm.value) { |
231 | + var dataKeys = searchText ? scope.alarmFields.filter( | ||
232 | + scope.createFilterForDataKey(searchText)) : scope.alarmFields; | ||
233 | + return dataKeys; | ||
215 | } else { | 234 | } else { |
216 | - return $q.when([]); | 235 | + if (scope.entityAlias) { |
236 | + var deferred = $q.defer(); | ||
237 | + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type}) | ||
238 | + .then(function (dataKeys) { | ||
239 | + deferred.resolve(dataKeys); | ||
240 | + }, function (e) { | ||
241 | + deferred.reject(e); | ||
242 | + }); | ||
243 | + return deferred.promise; | ||
244 | + } else { | ||
245 | + return $q.when([]); | ||
246 | + } | ||
217 | } | 247 | } |
218 | }; | 248 | }; |
219 | 249 | ||
250 | + scope.createFilterForDataKey = function (query) { | ||
251 | + var lowercaseQuery = angular.lowercase(query); | ||
252 | + return function filterFn(dataKey) { | ||
253 | + return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0); | ||
254 | + }; | ||
255 | + }; | ||
256 | + | ||
220 | scope.createKey = function (event, chipsId) { | 257 | scope.createKey = function (event, chipsId) { |
221 | var chipsChild = $(chipsId, element)[0].firstElementChild; | 258 | var chipsChild = $(chipsId, element)[0].firstElementChild; |
222 | var el = angular.element(chipsChild); | 259 | var el = angular.element(chipsChild); |
@@ -24,7 +24,7 @@ | @@ -24,7 +24,7 @@ | ||
24 | </tb-entity-alias-select> | 24 | </tb-entity-alias-select> |
25 | <section flex layout='column'> | 25 | <section flex layout='column'> |
26 | <section flex layout='column' layout-align="center" style="padding-left: 4px;"> | 26 | <section flex layout='column' layout-align="center" style="padding-left: 4px;"> |
27 | - <md-chips flex | 27 | + <md-chips flex ng-if="widgetType != types.widgetType.alarm.value" |
28 | id="timeseries_datakey_chips" | 28 | id="timeseries_datakey_chips" |
29 | ng-required="true" | 29 | ng-required="true" |
30 | ng-model="timeseriesDataKeys" md-autocomplete-snap | 30 | ng-model="timeseriesDataKeys" md-autocomplete-snap |
@@ -128,10 +128,60 @@ | @@ -128,10 +128,60 @@ | ||
128 | </div> | 128 | </div> |
129 | </md-chip-template> | 129 | </md-chip-template> |
130 | </md-chips> | 130 | </md-chips> |
131 | + <md-chips flex ng-if="widgetType == types.widgetType.alarm.value" | ||
132 | + id="alarm_datakey_chips" | ||
133 | + ng-required="true" | ||
134 | + ng-model="alarmDataKeys" md-autocomplete-snap | ||
135 | + md-transform-chip="transformAlarmDataKeyChip($chip)" | ||
136 | + md-require-match="true"> | ||
137 | + <md-autocomplete | ||
138 | + md-no-cache="true" | ||
139 | + id="alarm_datakey" | ||
140 | + md-selected-item="selectedAlarmDataKey" | ||
141 | + md-search-text="alarmDataKeySearchText" | ||
142 | + md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)" | ||
143 | + md-item-text="item.name" | ||
144 | + md-min-length="0" | ||
145 | + placeholder="{{'datakey.alarm' | translate }}" | ||
146 | + md-menu-class="tb-alarm-datakey-autocomplete"> | ||
147 | + <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span> | ||
148 | + <md-not-found> | ||
149 | + <div class="tb-not-found"> | ||
150 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)"> | ||
151 | + <span translate>entity.no-keys-found</span> | ||
152 | + </div> | ||
153 | + <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> | ||
154 | + <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span> | ||
155 | + </div> | ||
156 | + </div> | ||
157 | + </md-not-found> | ||
158 | + </md-autocomplete> | ||
159 | + <md-chip-template> | ||
160 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | ||
161 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | ||
162 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | ||
163 | + </div> | ||
164 | + <div layout="row" flex> | ||
165 | + <div class="tb-chip-label"> | ||
166 | + {{$chip.label}} | ||
167 | + </div> | ||
168 | + <div class="tb-chip-separator">: </div> | ||
169 | + <div class="tb-chip-label"> | ||
170 | + <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> | ||
171 | + <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> | ||
172 | + </div> | ||
173 | + </div> | ||
174 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | ||
175 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | ||
176 | + </md-button> | ||
177 | + </div> | ||
178 | + </md-chip-template> | ||
179 | + </md-chips> | ||
131 | </section> | 180 | </section> |
132 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | 181 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
133 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div> | 182 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div> |
134 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> | 183 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> |
184 | + <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> | ||
135 | </div> | 185 | </div> |
136 | </section> | 186 | </section> |
137 | </section> | 187 | </section> |
@@ -43,18 +43,27 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -43,18 +43,27 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
43 | element.html(template); | 43 | element.html(template); |
44 | 44 | ||
45 | scope.ngModelCtrl = ngModelCtrl; | 45 | scope.ngModelCtrl = ngModelCtrl; |
46 | + scope.types = types; | ||
47 | + | ||
46 | scope.functionTypes = utils.getPredefinedFunctionsList(); | 48 | scope.functionTypes = utils.getPredefinedFunctionsList(); |
49 | + scope.alarmFields = []; | ||
50 | + for (var alarmField in types.alarmFields) { | ||
51 | + scope.alarmFields.push(alarmField); | ||
52 | + } | ||
47 | 53 | ||
48 | scope.selectedDataKey = null; | 54 | scope.selectedDataKey = null; |
49 | scope.dataKeySearchText = null; | 55 | scope.dataKeySearchText = null; |
50 | 56 | ||
57 | + scope.selectedAlarmDataKey = null; | ||
58 | + scope.alarmDataKeySearchText = null; | ||
59 | + | ||
51 | scope.updateValidity = function () { | 60 | scope.updateValidity = function () { |
52 | if (ngModelCtrl.$viewValue) { | 61 | if (ngModelCtrl.$viewValue) { |
53 | var value = ngModelCtrl.$viewValue; | 62 | var value = ngModelCtrl.$viewValue; |
54 | var dataValid = angular.isDefined(value) && value != null; | 63 | var dataValid = angular.isDefined(value) && value != null; |
55 | ngModelCtrl.$setValidity('deviceData', dataValid); | 64 | ngModelCtrl.$setValidity('deviceData', dataValid); |
56 | if (dataValid) { | 65 | if (dataValid) { |
57 | - ngModelCtrl.$setValidity('funcTypes', | 66 | + ngModelCtrl.$setValidity('datasourceKeys', |
58 | angular.isDefined(value.dataKeys) && | 67 | angular.isDefined(value.dataKeys) && |
59 | value.dataKeys != null && | 68 | value.dataKeys != null && |
60 | value.dataKeys.length > 0); | 69 | value.dataKeys.length > 0); |
@@ -63,13 +72,22 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -63,13 +72,22 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
63 | }; | 72 | }; |
64 | 73 | ||
65 | scope.$watch('funcDataKeys', function () { | 74 | scope.$watch('funcDataKeys', function () { |
75 | + updateDataKeys(); | ||
76 | + }, true); | ||
77 | + | ||
78 | + scope.$watch('alarmDataKeys', function () { | ||
79 | + updateDataKeys(); | ||
80 | + }, true); | ||
81 | + | ||
82 | + function updateDataKeys() { | ||
66 | if (ngModelCtrl.$viewValue) { | 83 | if (ngModelCtrl.$viewValue) { |
67 | var dataKeys = []; | 84 | var dataKeys = []; |
68 | dataKeys = dataKeys.concat(scope.funcDataKeys); | 85 | dataKeys = dataKeys.concat(scope.funcDataKeys); |
86 | + dataKeys = dataKeys.concat(scope.alarmDataKeys); | ||
69 | ngModelCtrl.$viewValue.dataKeys = dataKeys; | 87 | ngModelCtrl.$viewValue.dataKeys = dataKeys; |
70 | scope.updateValidity(); | 88 | scope.updateValidity(); |
71 | } | 89 | } |
72 | - }, true); | 90 | + } |
73 | 91 | ||
74 | scope.$watch('datasourceName', function () { | 92 | scope.$watch('datasourceName', function () { |
75 | if (ngModelCtrl.$viewValue) { | 93 | if (ngModelCtrl.$viewValue) { |
@@ -81,18 +99,31 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -81,18 +99,31 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
81 | ngModelCtrl.$render = function () { | 99 | ngModelCtrl.$render = function () { |
82 | if (ngModelCtrl.$viewValue) { | 100 | if (ngModelCtrl.$viewValue) { |
83 | var funcDataKeys = []; | 101 | var funcDataKeys = []; |
102 | + var alarmDataKeys = []; | ||
84 | if (ngModelCtrl.$viewValue.dataKeys) { | 103 | if (ngModelCtrl.$viewValue.dataKeys) { |
85 | - funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); | 104 | + for (var d=0;d<ngModelCtrl.$viewValue.dataKeys.length;d++) { |
105 | + var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; | ||
106 | + if (dataKey.type === types.dataKeyType.function) { | ||
107 | + funcDataKeys.push(dataKey); | ||
108 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
109 | + alarmDataKeys.push(dataKey); | ||
110 | + } | ||
111 | + } | ||
86 | } | 112 | } |
87 | scope.funcDataKeys = funcDataKeys; | 113 | scope.funcDataKeys = funcDataKeys; |
114 | + scope.alarmDataKeys = alarmDataKeys; | ||
88 | scope.datasourceName = ngModelCtrl.$viewValue.name; | 115 | scope.datasourceName = ngModelCtrl.$viewValue.name; |
89 | } | 116 | } |
90 | }; | 117 | }; |
91 | 118 | ||
92 | - scope.transformDataKeyChip = function (chip) { | 119 | + scope.transformFuncDataKeyChip = function (chip) { |
93 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.function}); | 120 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.function}); |
94 | }; | 121 | }; |
95 | 122 | ||
123 | + scope.transformAlarmDataKeyChip = function (chip) { | ||
124 | + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); | ||
125 | + }; | ||
126 | + | ||
96 | scope.showColorPicker = function (event, dataKey) { | 127 | scope.showColorPicker = function (event, dataKey) { |
97 | $mdColorPicker.show({ | 128 | $mdColorPicker.show({ |
98 | value: dataKey.color, | 129 | value: dataKey.color, |
@@ -129,7 +160,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -129,7 +160,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
129 | dataKey: angular.copy(dataKey), | 160 | dataKey: angular.copy(dataKey), |
130 | dataKeySettingsSchema: scope.datakeySettingsSchema, | 161 | dataKeySettingsSchema: scope.datakeySettingsSchema, |
131 | entityAlias: null, | 162 | entityAlias: null, |
132 | - entityAliases: null | 163 | + aliasController: null |
133 | }, | 164 | }, |
134 | parent: angular.element($document[0].body), | 165 | parent: angular.element($document[0].body), |
135 | fullscreen: true, | 166 | fullscreen: true, |
@@ -140,7 +171,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -140,7 +171,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
140 | w.triggerHandler('resize'); | 171 | w.triggerHandler('resize'); |
141 | } | 172 | } |
142 | }).then(function (dataKey) { | 173 | }).then(function (dataKey) { |
143 | - scope.funcDataKeys[index] = dataKey; | 174 | + if (dataKey.type === types.dataKeyType.function) { |
175 | + scope.funcDataKeys[index] = dataKey; | ||
176 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
177 | + scope.alarmDataKeys[index] = dataKey; | ||
178 | + } | ||
144 | ngModelCtrl.$setDirty(); | 179 | ngModelCtrl.$setDirty(); |
145 | }, function () { | 180 | }, function () { |
146 | }); | 181 | }); |
@@ -151,8 +186,9 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -151,8 +186,9 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
151 | } | 186 | } |
152 | 187 | ||
153 | scope.dataKeysSearch = function (dataKeySearchText) { | 188 | scope.dataKeysSearch = function (dataKeySearchText) { |
154 | - var dataKeys = dataKeySearchText ? scope.functionTypes.filter( | ||
155 | - scope.createFilterForDataKey(dataKeySearchText)) : scope.functionTypes; | 189 | + var targetKeys = scope.widgetType == types.widgetType.alarm.value ? scope.alarmFields : scope.functionTypes; |
190 | + var dataKeys = dataKeySearchText ? targetKeys.filter( | ||
191 | + scope.createFilterForDataKey(dataKeySearchText)) : targetKeys; | ||
156 | return dataKeys; | 192 | return dataKeys; |
157 | }; | 193 | }; |
158 | 194 | ||
@@ -180,6 +216,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -180,6 +216,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
180 | restrict: "E", | 216 | restrict: "E", |
181 | require: "^ngModel", | 217 | require: "^ngModel", |
182 | scope: { | 218 | scope: { |
219 | + widgetType: '=', | ||
183 | generateDataKey: '&', | 220 | generateDataKey: '&', |
184 | datakeySettingsSchema: '=' | 221 | datakeySettingsSchema: '=' |
185 | }, | 222 | }, |
@@ -17,18 +17,19 @@ | @@ -17,18 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <section class="tb-datasource-func" flex layout='column' | 18 | <section class="tb-datasource-func" flex layout='column' |
19 | layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> | 19 | layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> |
20 | - <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;"> | 20 | + <md-input-container ng-if="widgetType != types.widgetType.alarm.value" |
21 | + class="tb-datasource-name" md-no-float style="min-width: 200px;"> | ||
21 | <input name="datasourceName" | 22 | <input name="datasourceName" |
22 | placeholder="{{ 'datasource.name' | translate }}" | 23 | placeholder="{{ 'datasource.name' | translate }}" |
23 | ng-model="datasourceName" | 24 | ng-model="datasourceName" |
24 | aria-label="{{ 'datasource.name' | translate }}"> | 25 | aria-label="{{ 'datasource.name' | translate }}"> |
25 | </md-input-container> | 26 | </md-input-container> |
26 | <section flex layout='column' style="padding-left: 4px;"> | 27 | <section flex layout='column' style="padding-left: 4px;"> |
27 | - <md-chips flex | 28 | + <md-chips flex ng-if="widgetType != types.widgetType.alarm.value" |
28 | id="function_datakey_chips" | 29 | id="function_datakey_chips" |
29 | ng-required="true" | 30 | ng-required="true" |
30 | ng-model="funcDataKeys" md-autocomplete-snap | 31 | ng-model="funcDataKeys" md-autocomplete-snap |
31 | - md-transform-chip="transformDataKeyChip($chip)" | 32 | + md-transform-chip="transformFuncDataKeyChip($chip)" |
32 | md-require-match="false"> | 33 | md-require-match="false"> |
33 | <md-autocomplete | 34 | <md-autocomplete |
34 | md-no-cache="false" | 35 | md-no-cache="false" |
@@ -75,8 +76,58 @@ | @@ -75,8 +76,58 @@ | ||
75 | </div> | 76 | </div> |
76 | </md-chip-template> | 77 | </md-chip-template> |
77 | </md-chips> | 78 | </md-chips> |
79 | + <md-chips flex ng-if="widgetType == types.widgetType.alarm.value" | ||
80 | + id="alarm_datakey_chips" | ||
81 | + ng-required="true" | ||
82 | + ng-model="alarmDataKeys" md-autocomplete-snap | ||
83 | + md-transform-chip="transformAlarmDataKeyChip($chip)" | ||
84 | + md-require-match="true"> | ||
85 | + <md-autocomplete | ||
86 | + md-no-cache="true" | ||
87 | + id="alarm_datakey" | ||
88 | + md-selected-item="selectedAlarmDataKey" | ||
89 | + md-search-text="alarmDataKeySearchText" | ||
90 | + md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)" | ||
91 | + md-item-text="item.name" | ||
92 | + md-min-length="0" | ||
93 | + placeholder="{{'datakey.alarm' | translate }}" | ||
94 | + md-menu-class="tb-alarm-datakey-autocomplete"> | ||
95 | + <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span> | ||
96 | + <md-not-found> | ||
97 | + <div class="tb-not-found"> | ||
98 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)"> | ||
99 | + <span translate>entity.no-keys-found</span> | ||
100 | + </div> | ||
101 | + <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> | ||
102 | + <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span> | ||
103 | + </div> | ||
104 | + </div> | ||
105 | + </md-not-found> | ||
106 | + </md-autocomplete> | ||
107 | + <md-chip-template> | ||
108 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | ||
109 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | ||
110 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | ||
111 | + </div> | ||
112 | + <div layout="row" flex> | ||
113 | + <div class="tb-chip-label"> | ||
114 | + {{$chip.label}} | ||
115 | + </div> | ||
116 | + <div class="tb-chip-separator">: </div> | ||
117 | + <div class="tb-chip-label"> | ||
118 | + <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> | ||
119 | + <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> | ||
120 | + </div> | ||
121 | + </div> | ||
122 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | ||
123 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | ||
124 | + </md-button> | ||
125 | + </div> | ||
126 | + </md-chip-template> | ||
127 | + </md-chips> | ||
78 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | 128 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
79 | - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div> | 129 | + <div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div> |
130 | + <div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> | ||
80 | </div> | 131 | </div> |
81 | </section> | 132 | </section> |
82 | </section> | 133 | </section> |
@@ -30,7 +30,7 @@ export default angular.module('thingsboard.directives.datasource', [thingsboardT | @@ -30,7 +30,7 @@ export default angular.module('thingsboard.directives.datasource', [thingsboardT | ||
30 | .name; | 30 | .name; |
31 | 31 | ||
32 | /*@ngInject*/ | 32 | /*@ngInject*/ |
33 | -function Datasource($compile, $templateCache, types) { | 33 | +function Datasource($compile, $templateCache, utils, types) { |
34 | 34 | ||
35 | var linker = function (scope, element, attrs, ngModelCtrl) { | 35 | var linker = function (scope, element, attrs, ngModelCtrl) { |
36 | 36 | ||
@@ -53,8 +53,12 @@ function Datasource($compile, $templateCache, types) { | @@ -53,8 +53,12 @@ function Datasource($compile, $templateCache, types) { | ||
53 | } | 53 | } |
54 | 54 | ||
55 | scope.$watch('model.type', function (newType, prevType) { | 55 | scope.$watch('model.type', function (newType, prevType) { |
56 | - if (newType != prevType) { | ||
57 | - scope.model.dataKeys = []; | 56 | + if (newType && prevType && newType != prevType) { |
57 | + if (scope.widgetType == types.widgetType.alarm.value) { | ||
58 | + scope.model.dataKeys = utils.getDefaultAlarmDataKeys(); | ||
59 | + } else { | ||
60 | + scope.model.dataKeys = []; | ||
61 | + } | ||
58 | } | 62 | } |
59 | }); | 63 | }); |
60 | 64 | ||
@@ -63,9 +67,10 @@ function Datasource($compile, $templateCache, types) { | @@ -63,9 +67,10 @@ function Datasource($compile, $templateCache, types) { | ||
63 | }, true); | 67 | }, true); |
64 | 68 | ||
65 | ngModelCtrl.$render = function () { | 69 | ngModelCtrl.$render = function () { |
66 | - scope.model = {}; | ||
67 | if (ngModelCtrl.$viewValue) { | 70 | if (ngModelCtrl.$viewValue) { |
68 | scope.model = ngModelCtrl.$viewValue; | 71 | scope.model = ngModelCtrl.$viewValue; |
72 | + } else { | ||
73 | + scope.model = {}; | ||
69 | } | 74 | } |
70 | }; | 75 | }; |
71 | 76 |
@@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
29 | ng-model="model" | 29 | ng-model="model" |
30 | datakey-settings-schema="datakeySettingsSchema" | 30 | datakey-settings-schema="datakeySettingsSchema" |
31 | ng-required="model.type === types.datasourceType.function" | 31 | ng-required="model.type === types.datasourceType.function" |
32 | + widget-type="widgetType" | ||
32 | generate-data-key="generateDataKey({chip: chip, type: type})"> | 33 | generate-data-key="generateDataKey({chip: chip, type: type})"> |
33 | </tb-datasource-func> | 34 | </tb-datasource-func> |
34 | <tb-datasource-entity flex | 35 | <tb-datasource-entity flex |
@@ -64,7 +64,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | @@ -64,7 +64,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | ||
64 | 64 | ||
65 | scope.historyOnly = angular.isDefined(attrs.historyOnly); | 65 | scope.historyOnly = angular.isDefined(attrs.historyOnly); |
66 | 66 | ||
67 | - scope.aggregation = angular.isDefined(attrs.aggregation); | 67 | + scope.aggregation = scope.$eval(attrs.aggregation); |
68 | 68 | ||
69 | scope.isToolbar = angular.isDefined(attrs.isToolbar); | 69 | scope.isToolbar = angular.isDefined(attrs.isToolbar); |
70 | 70 |
@@ -43,7 +43,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | @@ -43,7 +43,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | ||
43 | .name; | 43 | .name; |
44 | 44 | ||
45 | /*@ngInject*/ | 45 | /*@ngInject*/ |
46 | -function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, utils) { | 46 | +function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout, types, utils) { |
47 | 47 | ||
48 | var linker = function (scope, element, attrs, ngModelCtrl) { | 48 | var linker = function (scope, element, attrs, ngModelCtrl) { |
49 | 49 | ||
@@ -87,6 +87,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -87,6 +87,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
87 | value: null | 87 | value: null |
88 | } | 88 | } |
89 | 89 | ||
90 | + scope.alarmSource = { | ||
91 | + value: null | ||
92 | + } | ||
93 | + | ||
90 | ngModelCtrl.$render = function () { | 94 | ngModelCtrl.$render = function () { |
91 | if (ngModelCtrl.$viewValue) { | 95 | if (ngModelCtrl.$viewValue) { |
92 | var config = ngModelCtrl.$viewValue.config; | 96 | var config = ngModelCtrl.$viewValue.config; |
@@ -113,7 +117,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -113,7 +117,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
113 | scope.showLegend = angular.isDefined(config.showLegend) ? | 117 | scope.showLegend = angular.isDefined(config.showLegend) ? |
114 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; | 118 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; |
115 | scope.legendConfig = config.legendConfig; | 119 | scope.legendConfig = config.legendConfig; |
116 | - if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value | 120 | + if (scope.widgetType !== types.widgetType.rpc.value && |
121 | + scope.widgetType !== types.widgetType.alarm.value && | ||
122 | + scope.widgetType !== types.widgetType.static.value | ||
117 | && scope.isDataEnabled) { | 123 | && scope.isDataEnabled) { |
118 | if (scope.datasources) { | 124 | if (scope.datasources) { |
119 | scope.datasources.splice(0, scope.datasources.length); | 125 | scope.datasources.splice(0, scope.datasources.length); |
@@ -137,6 +143,12 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -137,6 +143,12 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
137 | } else { | 143 | } else { |
138 | scope.targetDeviceAlias.value = null; | 144 | scope.targetDeviceAlias.value = null; |
139 | } | 145 | } |
146 | + } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
147 | + if (config.alarmSource) { | ||
148 | + scope.alarmSource.value = config.alarmSource; | ||
149 | + } else { | ||
150 | + scope.alarmSource.value = null; | ||
151 | + } | ||
140 | } | 152 | } |
141 | 153 | ||
142 | scope.settings = config.settings; | 154 | scope.settings = config.settings; |
@@ -175,6 +187,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -175,6 +187,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
175 | if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { | 187 | if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { |
176 | valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0; | 188 | valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0; |
177 | ngModelCtrl.$setValidity('targetDeviceAliasIds', valid); | 189 | ngModelCtrl.$setValidity('targetDeviceAliasIds', valid); |
190 | + } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
191 | + valid = config && config.alarmSource; | ||
192 | + ngModelCtrl.$setValidity('alarmSource', valid); | ||
178 | } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { | 193 | } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { |
179 | valid = config && config.datasources && config.datasources.length > 0; | 194 | valid = config && config.datasources && config.datasources.length > 0; |
180 | ngModelCtrl.$setValidity('datasources', valid); | 195 | ngModelCtrl.$setValidity('datasources', valid); |
@@ -253,7 +268,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -253,7 +268,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
253 | }, true); | 268 | }, true); |
254 | 269 | ||
255 | scope.$watch('datasources', function () { | 270 | scope.$watch('datasources', function () { |
256 | - if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType !== types.widgetType.rpc.value | 271 | + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config |
272 | + && scope.widgetType !== types.widgetType.rpc.value | ||
273 | + && scope.widgetType !== types.widgetType.alarm.value | ||
257 | && scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { | 274 | && scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { |
258 | var value = ngModelCtrl.$viewValue; | 275 | var value = ngModelCtrl.$viewValue; |
259 | var config = value.config; | 276 | var config = value.config; |
@@ -286,6 +303,20 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -286,6 +303,20 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
286 | } | 303 | } |
287 | }); | 304 | }); |
288 | 305 | ||
306 | + scope.$watch('alarmSource.value', function () { | ||
307 | + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
308 | + var value = ngModelCtrl.$viewValue; | ||
309 | + var config = value.config; | ||
310 | + if (scope.alarmSource.value) { | ||
311 | + config.alarmSource = scope.alarmSource.value; | ||
312 | + } else { | ||
313 | + config.alarmSource = null; | ||
314 | + } | ||
315 | + ngModelCtrl.$setViewValue(value); | ||
316 | + scope.updateValidity(); | ||
317 | + } | ||
318 | + }); | ||
319 | + | ||
289 | scope.addDatasource = function () { | 320 | scope.addDatasource = function () { |
290 | var newDatasource; | 321 | var newDatasource; |
291 | if (scope.functionsOnly) { | 322 | if (scope.functionsOnly) { |
@@ -320,10 +351,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -320,10 +351,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
320 | return chip; | 351 | return chip; |
321 | } | 352 | } |
322 | 353 | ||
354 | + var label = chip; | ||
355 | + if (type === types.dataKeyType.alarm) { | ||
356 | + var alarmField = types.alarmFields[chip]; | ||
357 | + if (alarmField) { | ||
358 | + label = $translate.instant(alarmField.name)+''; | ||
359 | + } | ||
360 | + } | ||
361 | + label = scope.genNextLabel(label); | ||
362 | + | ||
323 | var result = { | 363 | var result = { |
324 | name: chip, | 364 | name: chip, |
325 | type: type, | 365 | type: type, |
326 | - label: scope.genNextLabel(chip), | 366 | + label: label, |
327 | color: scope.genNextColor(), | 367 | color: scope.genNextColor(), |
328 | settings: {}, | 368 | settings: {}, |
329 | _hash: Math.random() | 369 | _hash: Math.random() |
@@ -351,15 +391,18 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -351,15 +391,18 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
351 | var matches = false; | 391 | var matches = false; |
352 | do { | 392 | do { |
353 | matches = false; | 393 | matches = false; |
354 | - if (value.config.datasources) { | ||
355 | - for (var d=0;d<value.config.datasources.length;d++) { | ||
356 | - var datasource = value.config.datasources[d]; | ||
357 | - for (var k=0;k<datasource.dataKeys.length;k++) { | ||
358 | - var dataKey = datasource.dataKeys[k]; | ||
359 | - if (dataKey.label === label) { | ||
360 | - i++; | ||
361 | - label = name + ' ' + i; | ||
362 | - matches = true; | 394 | + var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources; |
395 | + if (datasources) { | ||
396 | + for (var d=0;d<datasources.length;d++) { | ||
397 | + var datasource = datasources[d]; | ||
398 | + if (datasource && datasource.dataKeys) { | ||
399 | + for (var k = 0; k < datasource.dataKeys.length; k++) { | ||
400 | + var dataKey = datasource.dataKeys[k]; | ||
401 | + if (dataKey.label === label) { | ||
402 | + i++; | ||
403 | + label = name + ' ' + i; | ||
404 | + matches = true; | ||
405 | + } | ||
363 | } | 406 | } |
364 | } | 407 | } |
365 | } | 408 | } |
@@ -371,10 +414,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -371,10 +414,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
371 | scope.genNextColor = function () { | 414 | scope.genNextColor = function () { |
372 | var i = 0; | 415 | var i = 0; |
373 | var value = ngModelCtrl.$viewValue; | 416 | var value = ngModelCtrl.$viewValue; |
374 | - if (value.config.datasources) { | ||
375 | - for (var d=0;d<value.config.datasources.length;d++) { | ||
376 | - var datasource = value.config.datasources[d]; | ||
377 | - i += datasource.dataKeys.length; | 417 | + var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources; |
418 | + if (datasources) { | ||
419 | + for (var d=0;d<datasources.length;d++) { | ||
420 | + var datasource = datasources[d]; | ||
421 | + if (datasource && datasource.dataKeys) { | ||
422 | + i += datasource.dataKeys.length; | ||
423 | + } | ||
378 | } | 424 | } |
379 | } | 425 | } |
380 | return utils.getMaterialColor(i); | 426 | return utils.getMaterialColor(i); |
@@ -20,18 +20,22 @@ | @@ -20,18 +20,22 @@ | ||
20 | <md-tab label="{{ 'widget-config.data' | translate }}" | 20 | <md-tab label="{{ 'widget-config.data' | translate }}" |
21 | ng-show="widgetType !== types.widgetType.static.value"> | 21 | ng-show="widgetType !== types.widgetType.static.value"> |
22 | <md-content class="md-padding" layout="column"> | 22 | <md-content class="md-padding" layout="column"> |
23 | - <div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center" | 23 | + <div ng-show="widgetType === types.widgetType.timeseries.value || widgetType === types.widgetType.alarm.value" layout='column' layout-align="center" |
24 | layout-gt-sm='row' layout-align-gt-sm="start center"> | 24 | layout-gt-sm='row' layout-align-gt-sm="start center"> |
25 | <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}" | 25 | <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}" |
26 | ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }} | 26 | ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }} |
27 | </md-checkbox> | 27 | </md-checkbox> |
28 | <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;"> | 28 | <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;"> |
29 | <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> | 29 | <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> |
30 | - <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow> | 30 | + <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation="{{ widgetType === types.widgetType.timeseries.value }}" |
31 | + flex ng-model="timewindow"></tb-timewindow> | ||
31 | </section> | 32 | </section> |
32 | </div> | 33 | </div> |
33 | <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" | 34 | <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" |
34 | - ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value && isDataEnabled"> | 35 | + ng-show="widgetType !== types.widgetType.rpc.value |
36 | + && widgetType !== types.widgetType.alarm.value | ||
37 | + && widgetType !== types.widgetType.static.value | ||
38 | + && isDataEnabled"> | ||
35 | <v-pane id="datasources-pane" expanded="true"> | 39 | <v-pane id="datasources-pane" expanded="true"> |
36 | <v-pane-header> | 40 | <v-pane-header> |
37 | {{ 'widget-config.datasources' | translate }} | 41 | {{ 'widget-config.datasources' | translate }} |
@@ -112,6 +116,24 @@ | @@ -112,6 +116,24 @@ | ||
112 | </v-pane-content> | 116 | </v-pane-content> |
113 | </v-pane> | 117 | </v-pane> |
114 | </v-accordion> | 118 | </v-accordion> |
119 | + <v-accordion id="alarn-source-accordion" control="alarmSourceAccordion" class="vAccordion--default" | ||
120 | + ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled"> | ||
121 | + <v-pane id="alarm-source-pane" expanded="true"> | ||
122 | + <v-pane-header> | ||
123 | + {{ 'widget-config.alarm-source' | translate }} | ||
124 | + </v-pane-header> | ||
125 | + <v-pane-content style="padding: 0 5px;"> | ||
126 | + <tb-datasource flex | ||
127 | + ng-model="alarmSource.value" | ||
128 | + widget-type="widgetType" | ||
129 | + functions-only="functionsOnly" | ||
130 | + alias-controller="aliasController" | ||
131 | + datakey-settings-schema="datakeySettingsSchema" | ||
132 | + generate-data-key="generateDataKey(chip,type)" | ||
133 | + on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"></tb-datasource> | ||
134 | + </v-pane-content> | ||
135 | + </v-pane> | ||
136 | + </v-accordion> | ||
115 | </md-content> | 137 | </md-content> |
116 | </md-tab> | 138 | </md-tab> |
117 | <md-tab label="{{ 'widget-config.settings' | translate }}"> | 139 | <md-tab label="{{ 'widget-config.settings' | translate }}"> |
@@ -21,7 +21,7 @@ import Subscription from '../api/subscription'; | @@ -21,7 +21,7 @@ import Subscription from '../api/subscription'; | ||
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, | 23 | export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, |
24 | - datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, | 24 | + datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, |
25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { | 25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { |
26 | 26 | ||
27 | var vm = this; | 27 | var vm = this; |
@@ -44,7 +44,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -44,7 +44,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
44 | 44 | ||
45 | var widgetContext = { | 45 | var widgetContext = { |
46 | inited: false, | 46 | inited: false, |
47 | - $scope: $scope, | ||
48 | $container: null, | 47 | $container: null, |
49 | $containerParent: null, | 48 | $containerParent: null, |
50 | width: 0, | 49 | width: 0, |
@@ -113,6 +112,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -113,6 +112,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
113 | timeService: timeService, | 112 | timeService: timeService, |
114 | deviceService: deviceService, | 113 | deviceService: deviceService, |
115 | datasourceService: datasourceService, | 114 | datasourceService: datasourceService, |
115 | + alarmService: alarmService, | ||
116 | utils: utils, | 116 | utils: utils, |
117 | widgetUtils: widgetContext.utils, | 117 | widgetUtils: widgetContext.utils, |
118 | dashboardTimewindowApi: dashboardTimewindowApi, | 118 | dashboardTimewindowApi: dashboardTimewindowApi, |
@@ -285,9 +285,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -285,9 +285,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
285 | var deferred = $q.defer(); | 285 | var deferred = $q.defer(); |
286 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { | 286 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
287 | options = { | 287 | options = { |
288 | - type: widget.type, | ||
289 | - datasources: angular.copy(widget.config.datasources) | ||
290 | - }; | 288 | + type: widget.type |
289 | + } | ||
290 | + if (widget.type == types.widgetType.alarm.value) { | ||
291 | + options.alarmSource = angular.copy(widget.config.alarmSource); | ||
292 | + } else { | ||
293 | + options.datasources = angular.copy(widget.config.datasources) | ||
294 | + } | ||
295 | + | ||
291 | defaultComponentsOptions(options); | 296 | defaultComponentsOptions(options); |
292 | 297 | ||
293 | createSubscription(options).then( | 298 | createSubscription(options).then( |
@@ -320,7 +325,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -320,7 +325,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
320 | $scope.executingRpcRequest = subscription.executingRpcRequest; | 325 | $scope.executingRpcRequest = subscription.executingRpcRequest; |
321 | }, | 326 | }, |
322 | onRpcSuccess: function(subscription) { | 327 | onRpcSuccess: function(subscription) { |
323 | - $scope.executingRpcRequest = subscription.executingRpcRequest; | 328 | + $scope.executingRpcRequest = subscription.executingRpcRequest; |
324 | $scope.rpcErrorText = subscription.rpcErrorText; | 329 | $scope.rpcErrorText = subscription.rpcErrorText; |
325 | $scope.rpcRejection = subscription.rpcRejection; | 330 | $scope.rpcRejection = subscription.rpcRejection; |
326 | }, | 331 | }, |
@@ -436,7 +441,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -436,7 +441,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
436 | widgetContext.$container = $('#container', containerElement); | 441 | widgetContext.$container = $('#container', containerElement); |
437 | widgetContext.$containerParent = $(containerElement); | 442 | widgetContext.$containerParent = $(containerElement); |
438 | 443 | ||
439 | - $compile($element.contents())($scope); | 444 | + if (widgetSizeDetected) { |
445 | + widgetContext.$container.css('height', widgetContext.height + 'px'); | ||
446 | + widgetContext.$container.css('width', widgetContext.width + 'px'); | ||
447 | + } | ||
448 | + | ||
449 | + widgetContext.$scope = $scope.$new(); | ||
450 | + | ||
451 | + $compile($element.contents())(widgetContext.$scope); | ||
440 | 452 | ||
441 | addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | 453 | addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef |
442 | } | 454 | } |
@@ -444,6 +456,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -444,6 +456,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
444 | function destroyWidgetElement() { | 456 | function destroyWidgetElement() { |
445 | removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | 457 | removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef |
446 | $element.html(''); | 458 | $element.html(''); |
459 | + if (widgetContext.$scope) { | ||
460 | + widgetContext.$scope.$destroy(); | ||
461 | + } | ||
447 | widgetContext.$container = null; | 462 | widgetContext.$container = null; |
448 | widgetContext.$containerParent = null; | 463 | widgetContext.$containerParent = null; |
449 | } | 464 | } |
@@ -76,6 +76,10 @@ export default function AddWidgetController($scope, widgetService, entityService | @@ -76,6 +76,10 @@ export default function AddWidgetController($scope, widgetService, entityService | ||
76 | link = 'widgetsConfigRpc'; | 76 | link = 'widgetsConfigRpc'; |
77 | break; | 77 | break; |
78 | } | 78 | } |
79 | + case types.widgetType.alarm.value: { | ||
80 | + link = 'widgetsConfigAlarm'; | ||
81 | + break; | ||
82 | + } | ||
79 | case types.widgetType.static.value: { | 83 | case types.widgetType.static.value: { |
80 | link = 'widgetsConfigStatic'; | 84 | link = 'widgetsConfigStatic'; |
81 | break; | 85 | break; |
@@ -47,6 +47,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -47,6 +47,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
47 | vm.latestWidgetTypes = []; | 47 | vm.latestWidgetTypes = []; |
48 | vm.timeseriesWidgetTypes = []; | 48 | vm.timeseriesWidgetTypes = []; |
49 | vm.rpcWidgetTypes = []; | 49 | vm.rpcWidgetTypes = []; |
50 | + vm.alarmWidgetTypes = []; | ||
50 | vm.staticWidgetTypes = []; | 51 | vm.staticWidgetTypes = []; |
51 | vm.widgetEditMode = $state.$current.data.widgetEditMode; | 52 | vm.widgetEditMode = $state.$current.data.widgetEditMode; |
52 | vm.iframeMode = $rootScope.iframeMode; | 53 | vm.iframeMode = $rootScope.iframeMode; |
@@ -263,6 +264,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -263,6 +264,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
263 | vm.latestWidgetTypes = []; | 264 | vm.latestWidgetTypes = []; |
264 | vm.timeseriesWidgetTypes = []; | 265 | vm.timeseriesWidgetTypes = []; |
265 | vm.rpcWidgetTypes = []; | 266 | vm.rpcWidgetTypes = []; |
267 | + vm.alarmWidgetTypes = []; | ||
266 | vm.staticWidgetTypes = []; | 268 | vm.staticWidgetTypes = []; |
267 | if (vm.widgetsBundle) { | 269 | if (vm.widgetsBundle) { |
268 | var bundleAlias = vm.widgetsBundle.alias; | 270 | var bundleAlias = vm.widgetsBundle.alias; |
@@ -308,6 +310,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -308,6 +310,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
308 | vm.latestWidgetTypes.push(widget); | 310 | vm.latestWidgetTypes.push(widget); |
309 | } else if (widgetTypeInfo.type === types.widgetType.rpc.value) { | 311 | } else if (widgetTypeInfo.type === types.widgetType.rpc.value) { |
310 | vm.rpcWidgetTypes.push(widget); | 312 | vm.rpcWidgetTypes.push(widget); |
313 | + } else if (widgetTypeInfo.type === types.widgetType.alarm.value) { | ||
314 | + vm.alarmWidgetTypes.push(widget); | ||
311 | } else if (widgetTypeInfo.type === types.widgetType.static.value) { | 315 | } else if (widgetTypeInfo.type === types.widgetType.static.value) { |
312 | vm.staticWidgetTypes.push(widget); | 316 | vm.staticWidgetTypes.push(widget); |
313 | } | 317 | } |
@@ -358,21 +362,6 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -358,21 +362,6 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
358 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; | 362 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; |
359 | vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, | 363 | vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, |
360 | types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); | 364 | types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); |
361 | - | ||
362 | - /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) | ||
363 | - .then( | ||
364 | - function(resolution) { | ||
365 | - if (resolution.error && !isTenantAdmin()) { | ||
366 | - vm.configurationError = true; | ||
367 | - showAliasesResolutionError(resolution.error); | ||
368 | - } else { | ||
369 | - vm.dashboardConfiguration = vm.dashboard.configuration; | ||
370 | - vm.dashboardCtx.dashboard = vm.dashboard; | ||
371 | - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo; | ||
372 | - vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; | ||
373 | - } | ||
374 | - } | ||
375 | - );*/ | ||
376 | }, function fail() { | 365 | }, function fail() { |
377 | vm.configurationError = true; | 366 | vm.configurationError = true; |
378 | }); | 367 | }); |
@@ -744,6 +733,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -744,6 +733,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
744 | link = 'widgetsConfigRpc'; | 733 | link = 'widgetsConfigRpc'; |
745 | break; | 734 | break; |
746 | } | 735 | } |
736 | + case types.widgetType.alarm.value: { | ||
737 | + link = 'widgetsConfigAlarm'; | ||
738 | + break; | ||
739 | + } | ||
747 | case types.widgetType.static.value: { | 740 | case types.widgetType.static.value: { |
748 | link = 'widgetsConfigStatic'; | 741 | link = 'widgetsConfigStatic'; |
749 | break; | 742 | break; |
@@ -851,6 +844,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -851,6 +844,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
851 | vm.timeseriesWidgetTypes = []; | 844 | vm.timeseriesWidgetTypes = []; |
852 | vm.latestWidgetTypes = []; | 845 | vm.latestWidgetTypes = []; |
853 | vm.rpcWidgetTypes = []; | 846 | vm.rpcWidgetTypes = []; |
847 | + vm.alarmWidgetTypes = []; | ||
854 | vm.staticWidgetTypes = []; | 848 | vm.staticWidgetTypes = []; |
855 | } | 849 | } |
856 | 850 |
@@ -52,7 +52,7 @@ | @@ -52,7 +52,7 @@ | ||
52 | <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()" | 52 | <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()" |
53 | is-toolbar | 53 | is-toolbar |
54 | direction="left" | 54 | direction="left" |
55 | - tooltip-direction="bottom" aggregation | 55 | + tooltip-direction="bottom" aggregation="true" |
56 | ng-model="vm.dashboardCtx.dashboardTimewindow"> | 56 | ng-model="vm.dashboardCtx.dashboardTimewindow"> |
57 | </tb-timewindow> | 57 | </tb-timewindow> |
58 | <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" | 58 | <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" |
@@ -179,6 +179,7 @@ | @@ -179,6 +179,7 @@ | ||
179 | <tb-edit-widget | 179 | <tb-edit-widget |
180 | dashboard="vm.dashboard" | 180 | dashboard="vm.dashboard" |
181 | alias-controller="vm.dashboardCtx.aliasController" | 181 | alias-controller="vm.dashboardCtx.aliasController" |
182 | + widget-edit-mode="vm.widgetEditMode" | ||
182 | widget="vm.editingWidget" | 183 | widget="vm.editingWidget" |
183 | widget-layout="vm.editingWidgetLayout" | 184 | widget-layout="vm.editingWidgetLayout" |
184 | the-form="vm.widgetForm"> | 185 | the-form="vm.widgetForm"> |
@@ -205,7 +206,8 @@ | @@ -205,7 +206,8 @@ | ||
205 | </header-pane> | 206 | </header-pane> |
206 | <div ng-if="vm.isAddingWidget"> | 207 | <div ng-if="vm.isAddingWidget"> |
207 | <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 || | 208 | <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 || |
208 | - vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0" | 209 | + vm.rpcWidgetTypes.length > 0 || vm.alarmWidgetTypes.length > 0 || |
210 | + vm.staticWidgetTypes.length > 0" | ||
209 | flex | 211 | flex |
210 | class="tb-absolute-fill" md-border-bottom> | 212 | class="tb-absolute-fill" md-border-bottom> |
211 | <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> | 213 | <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> |
@@ -238,6 +240,16 @@ | @@ -238,6 +240,16 @@ | ||
238 | on-widget-clicked="vm.addWidgetFromType(event, widget)"> | 240 | on-widget-clicked="vm.addWidgetFromType(event, widget)"> |
239 | </tb-dashboard> | 241 | </tb-dashboard> |
240 | </md-tab> | 242 | </md-tab> |
243 | + <md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}"> | ||
244 | + <tb-dashboard | ||
245 | + widgets="vm.alarmWidgetTypes" | ||
246 | + is-edit="false" | ||
247 | + is-mobile="true" | ||
248 | + is-edit-action-enabled="false" | ||
249 | + is-remove-action-enabled="false" | ||
250 | + on-widget-clicked="vm.addWidgetFromType(event, widget)"> | ||
251 | + </tb-dashboard> | ||
252 | + </md-tab> | ||
241 | <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}"> | 253 | <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}"> |
242 | <tb-dashboard | 254 | <tb-dashboard |
243 | widgets="vm.staticWidgetTypes" | 255 | widgets="vm.staticWidgetTypes" |
@@ -250,7 +262,8 @@ | @@ -250,7 +262,8 @@ | ||
250 | </md-tab> | 262 | </md-tab> |
251 | </md-tabs> | 263 | </md-tabs> |
252 | <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 && | 264 | <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 && |
253 | - vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle" | 265 | + vm.rpcWidgetTypes.length === 0 && vm.alarmWidgetTypes.length === 0 && |
266 | + vm.staticWidgetTypes.length === 0 && vm.widgetsBundle" | ||
254 | layout-align="center center" | 267 | layout-align="center center" |
255 | style="text-transform: uppercase; display: flex;" | 268 | style="text-transform: uppercase; display: flex;" |
256 | class="md-headline tb-absolute-fill">widgets-bundle.empty</span> | 269 | class="md-headline tb-absolute-fill">widgets-bundle.empty</span> |
@@ -131,6 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | @@ -131,6 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | ||
131 | scope: { | 131 | scope: { |
132 | dashboard: '=', | 132 | dashboard: '=', |
133 | aliasController: '=', | 133 | aliasController: '=', |
134 | + widgetEditMode: '=', | ||
134 | widget: '=', | 135 | widget: '=', |
135 | widgetLayout: '=', | 136 | widgetLayout: '=', |
136 | theForm: '=' | 137 | theForm: '=' |
@@ -22,7 +22,7 @@ | @@ -22,7 +22,7 @@ | ||
22 | widget-settings-schema="settingsSchema" | 22 | widget-settings-schema="settingsSchema" |
23 | datakey-settings-schema="dataKeySettingsSchema" | 23 | datakey-settings-schema="dataKeySettingsSchema" |
24 | alias-controller="aliasController" | 24 | alias-controller="aliasController" |
25 | - functions-only="functionsOnly" | 25 | + functions-only="widgetEditMode" |
26 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" | 26 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" |
27 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" | 27 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" |
28 | the-form="theForm"></tb-widget-config> | 28 | the-form="theForm"></tb-widget-config> |
@@ -87,6 +87,7 @@ export default angular.module('thingsboard.help', []) | @@ -87,6 +87,7 @@ export default angular.module('thingsboard.help', []) | ||
87 | widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries", | 87 | widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries", |
88 | widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest", | 88 | widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest", |
89 | widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc", | 89 | widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc", |
90 | + widgetsConfigAlarm: helpBaseUrl + "/docs/user-guide/ui/dashboards#alarm", | ||
90 | widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static", | 91 | widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static", |
91 | }, | 92 | }, |
92 | getPluginLink: function(plugin) { | 93 | getPluginLink: function(plugin) { |
@@ -131,6 +131,7 @@ export default angular.module('thingsboard.locale', []) | @@ -131,6 +131,7 @@ export default angular.module('thingsboard.locale', []) | ||
131 | "type": "Type", | 131 | "type": "Type", |
132 | "severity": "Severity", | 132 | "severity": "Severity", |
133 | "originator": "Originator", | 133 | "originator": "Originator", |
134 | + "originator-type": "Originator type", | ||
134 | "details": "Details", | 135 | "details": "Details", |
135 | "status": "Status", | 136 | "status": "Status", |
136 | "alarm-details": "Alarm details", | 137 | "alarm-details": "Alarm details", |
@@ -144,7 +145,10 @@ export default angular.module('thingsboard.locale', []) | @@ -144,7 +145,10 @@ export default angular.module('thingsboard.locale', []) | ||
144 | "severity-warning": "Warning", | 145 | "severity-warning": "Warning", |
145 | "severity-indeterminate": "Indeterminate", | 146 | "severity-indeterminate": "Indeterminate", |
146 | "acknowledge": "Acknowledge", | 147 | "acknowledge": "Acknowledge", |
147 | - "clear": "Clear" | 148 | + "clear": "Clear", |
149 | + "search": "Search alarms", | ||
150 | + "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected", | ||
151 | + "no-data": "No data to display" | ||
148 | }, | 152 | }, |
149 | "alias": { | 153 | "alias": { |
150 | "add": "Add alias", | 154 | "add": "Add alias", |
@@ -486,8 +490,10 @@ export default angular.module('thingsboard.locale', []) | @@ -486,8 +490,10 @@ export default angular.module('thingsboard.locale', []) | ||
486 | "configuration": "Data key configuration", | 490 | "configuration": "Data key configuration", |
487 | "timeseries": "Timeseries", | 491 | "timeseries": "Timeseries", |
488 | "attributes": "Attributes", | 492 | "attributes": "Attributes", |
493 | + "alarm": "Alarm fields", | ||
489 | "timeseries-required": "Entity timeseries are required.", | 494 | "timeseries-required": "Entity timeseries are required.", |
490 | "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", | 495 | "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", |
496 | + "alarm-fields-required": "Alarm fields are required.", | ||
491 | "function-types": "Function types", | 497 | "function-types": "Function types", |
492 | "function-types-required": "Function types are required." | 498 | "function-types-required": "Function types are required." |
493 | }, | 499 | }, |
@@ -1046,6 +1052,7 @@ export default angular.module('thingsboard.locale', []) | @@ -1046,6 +1052,7 @@ export default angular.module('thingsboard.locale', []) | ||
1046 | "timeseries": "Time series", | 1052 | "timeseries": "Time series", |
1047 | "latest-values": "Latest values", | 1053 | "latest-values": "Latest values", |
1048 | "rpc": "Control widget", | 1054 | "rpc": "Control widget", |
1055 | + "alarm": "Alarm widget", | ||
1049 | "static": "Static widget", | 1056 | "static": "Static widget", |
1050 | "select-widget-type": "Select widget type", | 1057 | "select-widget-type": "Select widget type", |
1051 | "missing-widget-title-error": "Widget title must be specified!", | 1058 | "missing-widget-title-error": "Widget title must be specified!", |
@@ -1133,7 +1140,8 @@ export default angular.module('thingsboard.locale', []) | @@ -1133,7 +1140,8 @@ export default angular.module('thingsboard.locale', []) | ||
1133 | "datasource-parameters": "Parameters", | 1140 | "datasource-parameters": "Parameters", |
1134 | "remove-datasource": "Remove datasource", | 1141 | "remove-datasource": "Remove datasource", |
1135 | "add-datasource": "Add datasource", | 1142 | "add-datasource": "Add datasource", |
1136 | - "target-device": "Target device" | 1143 | + "target-device": "Target device", |
1144 | + "alarm-source": "Alarm source" | ||
1137 | }, | 1145 | }, |
1138 | "widget-type": { | 1146 | "widget-type": { |
1139 | "import": "Import widget type", | 1147 | "import": "Import widget type", |
ui/src/app/widget/lib/alarms-table-widget.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import './alarms-table-widget.scss'; | ||
18 | + | ||
19 | +/* eslint-disable import/no-unresolved, import/default */ | ||
20 | + | ||
21 | +import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; | ||
22 | + | ||
23 | +/* eslint-enable import/no-unresolved, import/default */ | ||
24 | + | ||
25 | +import tinycolor from 'tinycolor2'; | ||
26 | +import cssjs from '../../../vendor/css.js/css'; | ||
27 | + | ||
28 | +export default angular.module('thingsboard.widgets.alarmsTableWidget', []) | ||
29 | + .directive('tbAlarmsTableWidget', AlarmsTableWidget) | ||
30 | + .name; | ||
31 | + | ||
32 | +/*@ngInject*/ | ||
33 | +function AlarmsTableWidget() { | ||
34 | + return { | ||
35 | + restrict: "E", | ||
36 | + scope: true, | ||
37 | + bindToController: { | ||
38 | + tableId: '=', | ||
39 | + config: '=', | ||
40 | + subscription: '=' | ||
41 | + }, | ||
42 | + controller: AlarmsTableWidgetController, | ||
43 | + controllerAs: 'vm', | ||
44 | + templateUrl: alarmsTableWidgetTemplate | ||
45 | + }; | ||
46 | +} | ||
47 | + | ||
48 | +/*@ngInject*/ | ||
49 | +function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUtil, $translate, utils, types) { | ||
50 | + var vm = this; | ||
51 | + | ||
52 | + vm.stylesInfo = {}; | ||
53 | + vm.contentsInfo = {}; | ||
54 | + | ||
55 | + vm.showData = true; | ||
56 | + vm.hasData = false; | ||
57 | + | ||
58 | + vm.alarms = []; | ||
59 | + vm.alarmsCount = 0; | ||
60 | + vm.selectedAlarms = [] | ||
61 | + | ||
62 | + vm.alarmSource = null; | ||
63 | + vm.allAlarms = null; | ||
64 | + | ||
65 | + vm.currentAlarm = null; | ||
66 | + | ||
67 | + vm.query = { | ||
68 | + order: '-'+types.alarmFields.createdTime.value, | ||
69 | + limit: 10, | ||
70 | + page: 1, | ||
71 | + search: null | ||
72 | + }; | ||
73 | + | ||
74 | + vm.alarmsTitle = $translate.instant('alarm.alarms'); | ||
75 | + | ||
76 | + vm.enterFilterMode = enterFilterMode; | ||
77 | + vm.exitFilterMode = exitFilterMode; | ||
78 | + vm.onReorder = onReorder; | ||
79 | + vm.onPaginate = onPaginate; | ||
80 | + | ||
81 | + vm.cellStyle = cellStyle; | ||
82 | + vm.cellContent = cellContent; | ||
83 | + | ||
84 | + $scope.$watch('vm.config', function() { | ||
85 | + if (vm.config) { | ||
86 | + vm.settings = vm.config.settings; | ||
87 | + vm.widgetConfig = vm.config.widgetConfig; | ||
88 | + initializeConfig(); | ||
89 | + } | ||
90 | + }); | ||
91 | + | ||
92 | + $scope.$watch("vm.query.search", function(newVal, prevVal) { | ||
93 | + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { | ||
94 | + updateAlarms(); | ||
95 | + } | ||
96 | + }); | ||
97 | + | ||
98 | + $scope.$watch('vm.subscription', function() { | ||
99 | + if (vm.subscription) { | ||
100 | + vm.alarmSource = vm.subscription.alarmSource; | ||
101 | + updateAlarmSource(); | ||
102 | + } | ||
103 | + }); | ||
104 | + | ||
105 | + $scope.$on('alarms-table-data-updated', function(event, tableId) { | ||
106 | + if (vm.tableId == tableId) { | ||
107 | + if (vm.subscription) { | ||
108 | + vm.allAlarms = vm.subscription.alarms; | ||
109 | + updateAlarms(true); | ||
110 | + $scope.$digest(); | ||
111 | + } | ||
112 | + } | ||
113 | + }); | ||
114 | + | ||
115 | + $scope.$watch(function() { return $mdMedia('gt-xs'); }, function(isGtXs) { | ||
116 | + vm.isGtXs = isGtXs; | ||
117 | + }); | ||
118 | + | ||
119 | + $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) { | ||
120 | + vm.isGtMd = isGtMd; | ||
121 | + if (vm.isGtMd) { | ||
122 | + vm.limitOptions = [5, 10, 15]; | ||
123 | + } else { | ||
124 | + vm.limitOptions = null; | ||
125 | + } | ||
126 | + }); | ||
127 | + | ||
128 | + function initializeConfig() { | ||
129 | + | ||
130 | + if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { | ||
131 | + vm.alarmsTitle = vm.settings.alarmsTitle; | ||
132 | + } | ||
133 | + //TODO: | ||
134 | + | ||
135 | + var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; | ||
136 | + var defaultColor = tinycolor(origColor); | ||
137 | + var mdDark = defaultColor.setAlpha(0.87).toRgbString(); | ||
138 | + var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); | ||
139 | + var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); | ||
140 | + //var mdDarkIcon = mdDarkSecondary; | ||
141 | + var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); | ||
142 | + | ||
143 | + var cssString = 'table.md-table th.md-column {\n'+ | ||
144 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
145 | + '}\n'+ | ||
146 | + 'table.md-table th.md-column md-icon.md-sort-icon {\n'+ | ||
147 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
148 | + '}\n'+ | ||
149 | + 'table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\n'+ | ||
150 | + 'color: ' + mdDark + ';\n'+ | ||
151 | + '}\n'+ | ||
152 | + 'table.md-table td.md-cell {\n'+ | ||
153 | + 'color: ' + mdDark + ';\n'+ | ||
154 | + 'border-top: 1px '+mdDarkDivider+' solid;\n'+ | ||
155 | + '}\n'+ | ||
156 | + 'table.md-table td.md-cell.md-placeholder {\n'+ | ||
157 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
158 | + '}\n'+ | ||
159 | + 'table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\n'+ | ||
160 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
161 | + '}\n'+ | ||
162 | + '.md-table-pagination {\n'+ | ||
163 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
164 | + 'border-top: 1px '+mdDarkDivider+' solid;\n'+ | ||
165 | + '}\n'+ | ||
166 | + '.md-table-pagination .buttons md-icon {\n'+ | ||
167 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
168 | + '}\n'+ | ||
169 | + '.md-table-pagination md-select:not([disabled]):focus .md-select-value {\n'+ | ||
170 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
171 | + '}'; | ||
172 | + | ||
173 | + var cssParser = new cssjs(); | ||
174 | + cssParser.testMode = false; | ||
175 | + var namespace = 'ts-table-' + hashCode(cssString); | ||
176 | + cssParser.cssPreviewNamespace = namespace; | ||
177 | + cssParser.createStyleElement(namespace, cssString); | ||
178 | + $element.addClass(namespace); | ||
179 | + | ||
180 | + function hashCode(str) { | ||
181 | + var hash = 0; | ||
182 | + var i, char; | ||
183 | + if (str.length === 0) return hash; | ||
184 | + for (i = 0; i < str.length; i++) { | ||
185 | + char = str.charCodeAt(i); | ||
186 | + hash = ((hash << 5) - hash) + char; | ||
187 | + hash = hash & hash; | ||
188 | + } | ||
189 | + return hash; | ||
190 | + } | ||
191 | + } | ||
192 | + | ||
193 | + function enterFilterMode () { | ||
194 | + vm.query.search = ''; | ||
195 | + } | ||
196 | + | ||
197 | + function exitFilterMode () { | ||
198 | + vm.query.search = null; | ||
199 | + updateAlarms(); | ||
200 | + } | ||
201 | + | ||
202 | + function onReorder () { | ||
203 | + updateAlarms(); | ||
204 | + } | ||
205 | + | ||
206 | + function onPaginate () { | ||
207 | + updateAlarms(); | ||
208 | + } | ||
209 | + | ||
210 | + function updateAlarms(preserveSelections) { | ||
211 | + if (!preserveSelections) { | ||
212 | + vm.selectedAlarms = []; | ||
213 | + } | ||
214 | + var result = $filter('orderBy')(vm.allAlarms, vm.query.order); | ||
215 | + if (vm.query.search != null) { | ||
216 | + result = $filter('filter')(result, {$: vm.query.search}); | ||
217 | + } | ||
218 | + vm.alarmsCount = result.length; | ||
219 | + var startIndex = vm.query.limit * (vm.query.page - 1); | ||
220 | + vm.alarms = result.slice(startIndex, startIndex + vm.query.limit); | ||
221 | + if (preserveSelections) { | ||
222 | + var newSelectedAlarms = []; | ||
223 | + if (vm.selectedAlarms && vm.selectedAlarms.length) { | ||
224 | + var i = vm.selectedAlarms.length; | ||
225 | + while (i--) { | ||
226 | + var selectedAlarm = vm.selectedAlarms[i]; | ||
227 | + if (selectedAlarm.id) { | ||
228 | + result = $filter('filter')(vm.alarms, {id: {id: selectedAlarm.id.id} }); | ||
229 | + if (result && result.length) { | ||
230 | + newSelectedAlarms.push(result[0]); | ||
231 | + } | ||
232 | + } | ||
233 | + } | ||
234 | + } | ||
235 | + vm.selectedAlarms = newSelectedAlarms; | ||
236 | + } | ||
237 | + } | ||
238 | + | ||
239 | + function cellStyle(alarm, key) { | ||
240 | + var style = {}; | ||
241 | + if (alarm && key) { | ||
242 | + var styleInfo = vm.stylesInfo[key.label]; | ||
243 | + var value = getAlarmValue(alarm, key); | ||
244 | + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { | ||
245 | + try { | ||
246 | + style = styleInfo.cellStyleFunction(value); | ||
247 | + } catch (e) { | ||
248 | + style = {}; | ||
249 | + } | ||
250 | + } else { | ||
251 | + style = defaultStyle(key, value); | ||
252 | + } | ||
253 | + } | ||
254 | + return style; | ||
255 | + } | ||
256 | + | ||
257 | + function cellContent(alarm, key) { | ||
258 | + var strContent = ''; | ||
259 | + if (alarm && key) { | ||
260 | + var contentInfo = vm.contentsInfo[key.label]; | ||
261 | + var value = getAlarmValue(alarm, key); | ||
262 | + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { | ||
263 | + if (angular.isDefined(value)) { | ||
264 | + strContent = '' + value; | ||
265 | + } | ||
266 | + var content = strContent; | ||
267 | + try { | ||
268 | + content = contentInfo.cellContentFunction(value, alarm, $filter); | ||
269 | + } catch (e) { | ||
270 | + content = strContent; | ||
271 | + } | ||
272 | + } else { | ||
273 | + content = defaultContent(key, value); | ||
274 | + } | ||
275 | + return content; | ||
276 | + } else { | ||
277 | + return strContent; | ||
278 | + } | ||
279 | + } | ||
280 | + | ||
281 | + function defaultContent(key, value) { | ||
282 | + if (angular.isDefined(value)) { | ||
283 | + var alarmField = types.alarmFields[key.name]; | ||
284 | + if (alarmField) { | ||
285 | + if (alarmField.time) { | ||
286 | + return $filter('date')(value, 'yyyy-MM-dd HH:mm:ss'); | ||
287 | + } else if (alarmField.value == types.alarmFields.severity.value) { | ||
288 | + return $translate.instant(types.alarmSeverity[value].name); | ||
289 | + } else if (alarmField.value == types.alarmFields.status.value) { | ||
290 | + return $translate.instant('alarm.display-status.'+value); | ||
291 | + } else if (alarmField.value == types.alarmFields.originatorType.value) { | ||
292 | + return $translate.instant(types.entityTypeTranslations[value].type); | ||
293 | + } | ||
294 | + else { | ||
295 | + return value; | ||
296 | + } | ||
297 | + } else { | ||
298 | + return ''; | ||
299 | + } | ||
300 | + } else { | ||
301 | + return ''; | ||
302 | + } | ||
303 | + } | ||
304 | + function defaultStyle(key, value) { | ||
305 | + if (angular.isDefined(value)) { | ||
306 | + var alarmField = types.alarmFields[key.name]; | ||
307 | + if (alarmField) { | ||
308 | + if (alarmField.value == types.alarmFields.severity.value) { | ||
309 | + return { | ||
310 | + fontWeight: 'bold', | ||
311 | + color: types.alarmSeverity[value].color | ||
312 | + }; | ||
313 | + } else { | ||
314 | + return {}; | ||
315 | + } | ||
316 | + } | ||
317 | + } else { | ||
318 | + return {}; | ||
319 | + } | ||
320 | + } | ||
321 | + | ||
322 | + const getDescendantProp = (obj, path) => ( | ||
323 | + path.split('.').reduce((acc, part) => acc && acc[part], obj) | ||
324 | + ); | ||
325 | + | ||
326 | + function getAlarmValue(alarm, key) { | ||
327 | + var alarmField = types.alarmFields[key.name]; | ||
328 | + if (alarmField) { | ||
329 | + return getDescendantProp(alarm, alarmField.value); | ||
330 | + } else { | ||
331 | + return getDescendantProp(alarm, key.name); | ||
332 | + } | ||
333 | + } | ||
334 | + | ||
335 | + function updateAlarmSource() { | ||
336 | + | ||
337 | + if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { | ||
338 | + vm.alarmsTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.settings.alarmsTitle); | ||
339 | + } | ||
340 | + | ||
341 | + vm.stylesInfo = {}; | ||
342 | + vm.contentsInfo = {}; | ||
343 | + | ||
344 | + for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { | ||
345 | + var dataKey = vm.alarmSource.dataKeys[d]; | ||
346 | + var keySettings = dataKey.settings; | ||
347 | + | ||
348 | + var cellStyleFunction = null; | ||
349 | + var useCellStyleFunction = false; | ||
350 | + | ||
351 | + if (keySettings.useCellStyleFunction === true) { | ||
352 | + if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { | ||
353 | + try { | ||
354 | + cellStyleFunction = new Function('value', keySettings.cellStyleFunction); | ||
355 | + useCellStyleFunction = true; | ||
356 | + } catch (e) { | ||
357 | + cellStyleFunction = null; | ||
358 | + useCellStyleFunction = false; | ||
359 | + } | ||
360 | + } | ||
361 | + } | ||
362 | + | ||
363 | + vm.stylesInfo[dataKey.label] = { | ||
364 | + useCellStyleFunction: useCellStyleFunction, | ||
365 | + cellStyleFunction: cellStyleFunction | ||
366 | + }; | ||
367 | + | ||
368 | + var cellContentFunction = null; | ||
369 | + var useCellContentFunction = false; | ||
370 | + | ||
371 | + if (keySettings.useCellContentFunction === true) { | ||
372 | + if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { | ||
373 | + try { | ||
374 | + cellContentFunction = new Function('value, alarm, filter', keySettings.cellContentFunction); | ||
375 | + useCellContentFunction = true; | ||
376 | + } catch (e) { | ||
377 | + cellContentFunction = null; | ||
378 | + useCellContentFunction = false; | ||
379 | + } | ||
380 | + } | ||
381 | + } | ||
382 | + | ||
383 | + vm.contentsInfo[dataKey.label] = { | ||
384 | + useCellContentFunction: useCellContentFunction, | ||
385 | + cellContentFunction: cellContentFunction | ||
386 | + }; | ||
387 | + } | ||
388 | + } | ||
389 | + | ||
390 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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-alarms-table { | ||
18 | + margin-top: 15px; | ||
19 | + &.tb-data-table { | ||
20 | + table.md-table { | ||
21 | + tbody { | ||
22 | + tr { | ||
23 | + td { | ||
24 | + &.ag-action-cell { | ||
25 | + min-width: 40px; | ||
26 | + max-width: 40px; | ||
27 | + width: 40px; | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | + } | ||
34 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="tb-absolute-fill tb-alarms-table tb-data-table" layout="column"> | ||
19 | + <div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column"> | ||
20 | + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedAlarms.length | ||
21 | + && vm.query.search === null"> | ||
22 | + <div class="md-toolbar-tools"> | ||
23 | + <span>{{ vm.alarmsTitle }}</span> | ||
24 | + <span flex></span> | ||
25 | + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> | ||
26 | + <md-icon>search</md-icon> | ||
27 | + <md-tooltip md-direction="top"> | ||
28 | + {{ 'action.search' | translate }} | ||
29 | + </md-tooltip> | ||
30 | + </md-button> | ||
31 | + </div> | ||
32 | + </md-toolbar> | ||
33 | + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedAlarms.length && | ||
34 | + vm.query.search != null"> | ||
35 | + <div class="md-toolbar-tools"> | ||
36 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | ||
37 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | ||
38 | + <md-tooltip md-direction="top"> | ||
39 | + {{'alarm.search' | translate}} | ||
40 | + </md-tooltip> | ||
41 | + </md-button> | ||
42 | + <md-input-container flex> | ||
43 | + <label> </label> | ||
44 | + <input ng-model="vm.query.search" placeholder="{{'alarm.search' | translate}}"/> | ||
45 | + </md-input-container> | ||
46 | + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()"> | ||
47 | + <md-icon aria-label="Close" class="material-icons">close</md-icon> | ||
48 | + <md-tooltip md-direction="top"> | ||
49 | + {{ 'action.close' | translate }} | ||
50 | + </md-tooltip> | ||
51 | + </md-button> | ||
52 | + </div> | ||
53 | + </md-toolbar> | ||
54 | + <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedAlarms.length"> | ||
55 | + <div class="md-toolbar-tools"> | ||
56 | + <span translate="alarm.selected-alarms" | ||
57 | + translate-values="{count: vm.selectedAlarms.length}" | ||
58 | + translate-interpolation="messageformat"></span> | ||
59 | + <span flex></span> | ||
60 | + <md-button class="md-icon-button" ng-click="vm.ackAlarms($event)"> | ||
61 | + <md-icon>done</md-icon> | ||
62 | + <md-tooltip md-direction="top"> | ||
63 | + {{ 'alarm.acknowledge' | translate }} | ||
64 | + </md-tooltip> | ||
65 | + </md-button> | ||
66 | + <md-button class="md-icon-button" ng-click="vm.clearAlarms($event)"> | ||
67 | + <md-icon>clear</md-icon> | ||
68 | + <md-tooltip md-direction="top"> | ||
69 | + {{ 'alarm.clear' | translate }} | ||
70 | + </md-tooltip> | ||
71 | + </md-button> | ||
72 | + </div> | ||
73 | + </md-toolbar> | ||
74 | + <md-table-container flex> | ||
75 | + <table md-table md-row-select multiple="" ng-model="vm.selectedAlarms"> | ||
76 | + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> | ||
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> </span></th> | ||
80 | + </tr> | ||
81 | + </thead> | ||
82 | + <tbody md-body> | ||
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" | ||
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" | ||
87 | + ng-style="vm.cellStyle(alarm, key)" | ||
88 | + ng-bind-html="vm.cellContent(alarm, key)"> | ||
89 | + </td> | ||
90 | + <td md-cell class="tb-action-cell"> | ||
91 | + <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}" | ||
92 | + ng-click="vm.openAlarmDetails($event, alarm)"> | ||
93 | + <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon> | ||
94 | + <md-tooltip md-direction="top"> | ||
95 | + {{ 'alarm.details' | translate }} | ||
96 | + </md-tooltip> | ||
97 | + </md-button> | ||
98 | + </td> | ||
99 | + </tr> | ||
100 | + </tbody> | ||
101 | + </table> | ||
102 | + <md-divider></md-divider> | ||
103 | + <span ng-show="!vm.alarms.length" | ||
104 | + layout-align="center center" | ||
105 | + class="no-data-found" translate>alarm.no-alarms-prompt</span> | ||
106 | + </md-table-container> | ||
107 | + <md-table-pagination md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions" | ||
108 | + md-page="vm.query.page" md-total="{{vm.alarmsCount}}" | ||
109 | + md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd"> | ||
110 | + </md-table-pagination> | ||
111 | + </div> | ||
112 | + <span ng-show="!vm.showData" | ||
113 | + layout-align="center center" | ||
114 | + style="text-transform: uppercase; display: flex;" | ||
115 | + class="tb-absolute-fill" translate>alarm.no-data</span> | ||
116 | +</div> |
@@ -54,6 +54,13 @@ | @@ -54,6 +54,13 @@ | ||
54 | <span translate>{{vm.types.widgetType.rpc.name}}</span> | 54 | <span translate>{{vm.types.widgetType.rpc.name}}</span> |
55 | </md-button> | 55 | </md-button> |
56 | <md-button class="tb-card-button md-raised md-primary" layout="column" | 56 | <md-button class="tb-card-button md-raised md-primary" layout="column" |
57 | + ng-click="vm.typeSelected(vm.types.widgetType.alarm.value)"> | ||
58 | + <md-icon class="material-icons tb-md-96" | ||
59 | + aria-label="{{ vm.types.widgetType.alarm.name | translate }}">error | ||
60 | + </md-icon> | ||
61 | + <span translate>{{vm.types.widgetType.alarm.name}}</span> | ||
62 | + </md-button> | ||
63 | + <md-button class="tb-card-button md-raised md-primary" layout="column" | ||
57 | ng-click="vm.typeSelected(vm.types.widgetType.static.value)"> | 64 | ng-click="vm.typeSelected(vm.types.widgetType.static.value)"> |
58 | <md-icon class="material-icons tb-md-96" | 65 | <md-icon class="material-icons tb-md-96" |
59 | aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download | 66 | aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download |
@@ -329,10 +329,14 @@ export default function WidgetEditorController(widgetService, userService, types | @@ -329,10 +329,14 @@ export default function WidgetEditorController(widgetService, userService, types | ||
329 | $scope.$watch('vm.widget.type', function (newVal, oldVal) { | 329 | $scope.$watch('vm.widget.type', function (newVal, oldVal) { |
330 | if (!angular.equals(newVal, oldVal)) { | 330 | if (!angular.equals(newVal, oldVal)) { |
331 | var config = angular.fromJson(vm.widget.defaultConfig); | 331 | var config = angular.fromJson(vm.widget.defaultConfig); |
332 | - if (vm.widget.type !== types.widgetType.rpc.value) { | 332 | + if (vm.widget.type !== types.widgetType.rpc.value |
333 | + && vm.widget.type !== types.widgetType.alarm.value) { | ||
333 | if (config.targetDeviceAliases) { | 334 | if (config.targetDeviceAliases) { |
334 | delete config.targetDeviceAliases; | 335 | delete config.targetDeviceAliases; |
335 | } | 336 | } |
337 | + if (config.alarmSource) { | ||
338 | + delete config.alarmSource; | ||
339 | + } | ||
336 | if (!config.datasources) { | 340 | if (!config.datasources) { |
337 | config.datasources = []; | 341 | config.datasources = []; |
338 | } | 342 | } |
@@ -346,22 +350,38 @@ export default function WidgetEditorController(widgetService, userService, types | @@ -346,22 +350,38 @@ export default function WidgetEditorController(widgetService, userService, types | ||
346 | for (var i = 0; i < config.datasources.length; i++) { | 350 | for (var i = 0; i < config.datasources.length; i++) { |
347 | var datasource = config.datasources[i]; | 351 | var datasource = config.datasources[i]; |
348 | datasource.type = vm.widget.type; | 352 | datasource.type = vm.widget.type; |
349 | - if (vm.widget.type !== types.widgetType.timeseries.value && datasource.intervalSec) { | ||
350 | - delete datasource.intervalSec; | ||
351 | - } else if (vm.widget.type === types.widgetType.timeseries.value && !datasource.intervalSec) { | ||
352 | - datasource.intervalSec = 60; | ||
353 | - } | ||
354 | } | 353 | } |
355 | - } else { | 354 | + } else if (vm.widget.type == types.widgetType.rpc.value) { |
356 | if (config.datasources) { | 355 | if (config.datasources) { |
357 | delete config.datasources; | 356 | delete config.datasources; |
358 | } | 357 | } |
358 | + if (config.alarmSource) { | ||
359 | + delete config.alarmSource; | ||
360 | + } | ||
359 | if (config.timewindow) { | 361 | if (config.timewindow) { |
360 | delete config.timewindow; | 362 | delete config.timewindow; |
361 | } | 363 | } |
362 | if (!config.targetDeviceAliases) { | 364 | if (!config.targetDeviceAliases) { |
363 | config.targetDeviceAliases = []; | 365 | config.targetDeviceAliases = []; |
364 | } | 366 | } |
367 | + } else { // alarm | ||
368 | + if (config.datasources) { | ||
369 | + delete config.datasources; | ||
370 | + } | ||
371 | + if (config.targetDeviceAliases) { | ||
372 | + delete config.targetDeviceAliases; | ||
373 | + } | ||
374 | + if (!config.alarmSource) { | ||
375 | + config.alarmSource = {}; | ||
376 | + config.alarmSource.type = vm.widget.type | ||
377 | + } | ||
378 | + if (!config.timewindow) { | ||
379 | + config.timewindow = { | ||
380 | + realtime: { | ||
381 | + timewindowMs: 24 * 60 * 60 * 1000 | ||
382 | + } | ||
383 | + }; | ||
384 | + } | ||
365 | } | 385 | } |
366 | vm.widget.defaultConfig = angular.toJson(config); | 386 | vm.widget.defaultConfig = angular.toJson(config); |
367 | } | 387 | } |