thingsboard-memoizeit
Changes
ui/src/app/api/alarm.service.js 94(+91 -3)
ui/src/app/api/alias-controller.js 58(+35 -23)
ui/src/app/api/subscription.js 214(+173 -41)
ui/src/app/api/widget.service.js 3(+2 -1)
ui/src/app/common/types.constant.js 73(+67 -6)
ui/src/app/common/utils.service.js 54(+52 -2)
ui/src/app/components/widget.controller.js 29(+22 -7)
ui/src/app/components/widget-config.directive.js 80(+63 -17)
ui/src/app/components/widget-config.tpl.html 28(+25 -3)
ui/src/app/dashboard/dashboard.controller.js 24(+9 -15)
ui/src/app/dashboard/dashboard.tpl.html 19(+16 -3)
ui/src/app/locale/locale.constant.js 12(+10 -2)
ui/src/app/widget/lib/alarms-table-widget.js 390(+390 -0)
ui/src/app/widget/lib/alarms-table-widget.tpl.html 116(+116 -0)
Details
ui/src/app/api/alarm.service.js 94(+91 -3)
diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js
index 34e6b59..c2ee07a 100644
--- a/ui/src/app/api/alarm.service.js
+++ b/ui/src/app/api/alarm.service.js
@@ -18,7 +18,29 @@ export default angular.module('thingsboard.api.alarm', [])
.name;
/*@ngInject*/
-function AlarmService($http, $q, $interval, $filter) {
+function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
+
+ var alarmSourceListeners = {};
+
+ var simulatedAlarm = {
+ createdTime: (new Date).getTime(),
+ startTs: (new Date).getTime(),
+ endTs: 0,
+ ackTs: 0,
+ clearTs: 0,
+ originatorName: 'Simulated',
+ originator: {
+ entityType: "DEVICE",
+ id: "1"
+ },
+ type: 'TEMPERATURE',
+ severity: "MAJOR",
+ status: types.alarmStatus.activeUnack,
+ details: {
+ message: "Temperature is high!"
+ }
+ };
+
var service = {
getAlarm: getAlarm,
getAlarmInfo: getAlarmInfo,
@@ -27,7 +49,9 @@ function AlarmService($http, $q, $interval, $filter) {
clearAlarm: clearAlarm,
getAlarms: getAlarms,
pollAlarms: pollAlarms,
- cancelPollAlarms: cancelPollAlarms
+ cancelPollAlarms: cancelPollAlarms,
+ subscribeForAlarms: subscribeForAlarms,
+ unsubscribeFromAlarms: unsubscribeFromAlarms
}
return service;
@@ -171,12 +195,21 @@ function AlarmService($http, $q, $interval, $filter) {
pageLink = {
limit: alarmsQuery.limit
};
- } else {
+ } else if (alarmsQuery.interval) {
pageLink = {
limit: 100,
startTime: time - alarmsQuery.interval
};
+ } else if (alarmsQuery.startTime) {
+ pageLink = {
+ limit: 100,
+ startTime: alarmsQuery.startTime
+ }
+ if (alarmsQuery.endTime) {
+ pageLink.endTime = alarmsQuery.endTime;
+ }
}
+
fetchAlarms(alarmsQuery, pageLink, deferred);
return deferred.promise;
}
@@ -211,4 +244,59 @@ function AlarmService($http, $q, $interval, $filter) {
}
}
+ function subscribeForAlarms(alarmSourceListener) {
+ alarmSourceListener.id = utils.guid();
+ alarmSourceListeners[alarmSourceListener.id] = alarmSourceListener;
+ var alarmSource = alarmSourceListener.alarmSource;
+ if (alarmSource.type == types.datasourceType.function) {
+ $timeout(function() {
+ alarmSourceListener.alarmsUpdated([simulatedAlarm], false);
+ });
+ } else {
+ var pollingInterval = 5000; //TODO:
+ alarmSourceListener.alarmsQuery = {
+ entityType: alarmSource.entityType,
+ entityId: alarmSource.entityId,
+ alarmSearchStatus: null, //TODO:
+ alarmStatus: null
+ }
+ var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'});
+ if (originatorKeys && originatorKeys.length) {
+ alarmSourceListener.alarmsQuery.fetchOriginator = true;
+ }
+ var subscriptionTimewindow = alarmSourceListener.subscriptionTimewindow;
+ if (subscriptionTimewindow.realtimeWindowMs) {
+ alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.startTs;
+ } else {
+ alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.fixedWindow.startTimeMs;
+ alarmSourceListener.alarmsQuery.endTime = subscriptionTimewindow.fixedWindow.endTimeMs;
+ }
+ alarmSourceListener.alarmsQuery.onAlarms = function(alarms) {
+ if (subscriptionTimewindow.realtimeWindowMs) {
+ var now = Date.now();
+ if (alarmSourceListener.lastUpdateTs) {
+ var interval = now - alarmSourceListener.lastUpdateTs;
+ alarmSourceListener.alarmsQuery.startTime += interval;
+ } else {
+ alarmSourceListener.lastUpdateTs = now;
+ }
+ }
+ alarmSourceListener.alarmsUpdated(alarms, false);
+ }
+ onPollAlarms(alarmSourceListener.alarmsQuery);
+ alarmSourceListener.pollPromise = $interval(onPollAlarms, pollingInterval,
+ 0, false, alarmSourceListener.alarmsQuery);
+ }
+
+ }
+
+ function unsubscribeFromAlarms(alarmSourceListener) {
+ if (alarmSourceListener && alarmSourceListener.id) {
+ if (alarmSourceListener.pollPromise) {
+ $interval.cancel(alarmSourceListener.pollPromise);
+ alarmSourceListener.pollPromise = null;
+ }
+ delete alarmSourceListeners[alarmSourceListener.id];
+ }
+ }
}
ui/src/app/api/alias-controller.js 58(+35 -23)
diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js
index 60e8a31..9acd5aa 100644
--- a/ui/src/app/api/alias-controller.js
+++ b/ui/src/app/api/alias-controller.js
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-const varsRegex = /\$\{([^\}]*)\}/g;
-
export default class AliasController {
constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) {
@@ -113,14 +111,14 @@ export default class AliasController {
}
}
- resolveDatasource(datasource) {
+ resolveDatasource(datasource, isSingle) {
var deferred = this.$q.defer();
if (datasource.type === this.types.datasourceType.entity) {
if (datasource.entityAliasId) {
this.getAliasInfo(datasource.entityAliasId).then(
function success(aliasInfo) {
datasource.aliasName = aliasInfo.alias;
- if (aliasInfo.resolveMultiple) {
+ if (aliasInfo.resolveMultiple && !isSingle) {
var newDatasource;
var resolvedEntities = aliasInfo.resolvedEntities;
if (resolvedEntities && resolvedEntities.length) {
@@ -178,30 +176,44 @@ export default class AliasController {
return deferred.promise;
}
+ resolveAlarmSource(alarmSource) {
+ var deferred = this.$q.defer();
+ var aliasCtrl = this;
+ this.resolveDatasource(alarmSource, true).then(
+ function success(datasources) {
+ var datasource = datasources[0];
+ if (datasource.type === aliasCtrl.types.datasourceType.function) {
+ var name;
+ if (datasource.name && datasource.name.length) {
+ name = datasource.name;
+ } else {
+ name = aliasCtrl.types.datasourceType.function;
+ }
+ datasource.name = name;
+ datasource.aliasName = name;
+ datasource.entityName = name;
+ } else if (datasource.unresolvedStateEntity) {
+ datasource.name = "Unresolved";
+ datasource.entityName = "Unresolved";
+ }
+ deferred.resolve(datasource);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
resolveDatasources(datasources) {
+ var aliasCtrl = this;
+
function updateDataKeyLabel(dataKey, datasource) {
if (!dataKey.pattern) {
dataKey.pattern = angular.copy(dataKey.label);
}
- var pattern = dataKey.pattern;
- var label = dataKey.pattern;
- var match = varsRegex.exec(pattern);
- while (match !== null) {
- var variable = match[0];
- var variableName = match[1];
- if (variableName === 'dsName') {
- label = label.split(variable).join(datasource.name);
- } else if (variableName === 'entityName') {
- label = label.split(variable).join(datasource.entityName);
- } else if (variableName === 'deviceName') {
- label = label.split(variable).join(datasource.entityName);
- } else if (variableName === 'aliasName') {
- label = label.split(variable).join(datasource.aliasName);
- }
- match = varsRegex.exec(pattern);
- }
- dataKey.label = label;
+ dataKey.label = aliasCtrl.utils.createLabelFromDatasource(datasource, dataKey.pattern);
}
function updateDatasourceKeyLabels(datasource) {
@@ -213,7 +225,7 @@ export default class AliasController {
var deferred = this.$q.defer();
var newDatasources = angular.copy(datasources);
var datasorceResolveTasks = [];
- var aliasCtrl = this;
+
newDatasources.forEach(function (datasource) {
var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource);
datasorceResolveTasks.push(resolveDatasourceTask);
ui/src/app/api/subscription.js 214(+173 -41)
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 1d4eb30..16e4cc3 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -64,6 +64,39 @@ export default class Subscription {
deferred.resolve(subscription);
}
);
+ } else if (this.type === this.ctx.types.widgetType.alarm.value) {
+ this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
+ this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
+ this.callbacks.dataLoading = this.callbacks.dataLoading || function(){};
+ this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){};
+ this.alarmSource = options.alarmSource;
+ this.alarmSourceListener = null;
+ this.alarms = [];
+
+ this.originalTimewindow = null;
+ this.timeWindow = {
+ stDiff: this.ctx.stDiff
+ }
+ this.useDashboardTimewindow = options.useDashboardTimewindow;
+
+ if (this.useDashboardTimewindow) {
+ this.timeWindowConfig = angular.copy(options.dashboardTimewindow);
+ } else {
+ this.timeWindowConfig = angular.copy(options.timeWindowConfig);
+ }
+
+ this.subscriptionTimewindow = null;
+
+ this.loadingData = false;
+ this.displayLegend = false;
+ this.initAlarmSubscription().then(
+ function success() {
+ deferred.resolve(subscription);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
} else {
this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
@@ -132,6 +165,44 @@ export default class Subscription {
return deferred.promise;
}
+ initAlarmSubscription() {
+ var deferred = this.ctx.$q.defer();
+ if (!this.ctx.aliasController) {
+ this.configureAlarmsData();
+ deferred.resolve();
+ } else {
+ var subscription = this;
+ this.ctx.aliasController.resolveAlarmSource(this.alarmSource).then(
+ function success(alarmSource) {
+ subscription.alarmSource = alarmSource;
+ subscription.configureAlarmsData();
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ configureAlarmsData() {
+ var subscription = this;
+ var registration;
+ if (this.useDashboardTimewindow) {
+ registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
+ if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
+ subscription.timeWindowConfig = angular.copy(newDashboardTimewindow);
+ subscription.unsubscribe();
+ subscription.subscribe();
+ }
+ });
+ this.registrations.push(registration);
+ } else {
+ this.startWatchingTimewindow();
+ }
+ }
+
initDataSubscription() {
var deferred = this.ctx.$q.defer();
if (!this.ctx.aliasController) {
@@ -393,6 +464,8 @@ export default class Subscription {
onAliasesChanged(aliasIds) {
if (this.type === this.ctx.types.widgetType.rpc.value) {
return this.checkRpcTarget(aliasIds);
+ } else if (this.type === this.ctx.types.widgetType.alarm.value) {
+ return this.checkAlarmSource(aliasIds);
} else {
return this.checkSubscriptions(aliasIds);
}
@@ -516,6 +589,15 @@ export default class Subscription {
}
}
+ alarmsUpdated(alarms, apply) {
+ this.notifyDataLoaded();
+ this.alarms = alarms;
+ if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
+ this.updateTimewindow();
+ }
+ this.onDataUpdated(apply);
+ }
+
updateLegend(dataIndex, data, apply) {
var dataKey = this.legendData.keys[dataIndex].dataKey;
var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals;
@@ -540,62 +622,104 @@ export default class Subscription {
if (this.type === this.ctx.types.widgetType.rpc.value) {
return;
}
+ if (this.type === this.ctx.types.widgetType.alarm.value) {
+ this.alarmsSubscribe();
+ } else {
+ this.notifyDataLoading();
+ if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) {
+ this.updateRealtimeSubscription();
+ if (this.subscriptionTimewindow.fixedWindow) {
+ this.onDataUpdated();
+ }
+ }
+ var index = 0;
+ for (var i = 0; i < this.datasources.length; i++) {
+ var datasource = this.datasources[i];
+ if (angular.isFunction(datasource))
+ continue;
+
+ var subscription = this;
+
+ var listener = {
+ subscriptionType: this.type,
+ subscriptionTimewindow: this.subscriptionTimewindow,
+ datasource: datasource,
+ entityType: datasource.entityType,
+ entityId: datasource.entityId,
+ dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
+ subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
+ },
+ updateRealtimeSubscription: function () {
+ this.subscriptionTimewindow = subscription.updateRealtimeSubscription();
+ return this.subscriptionTimewindow;
+ },
+ setRealtimeSubscription: function (subscriptionTimewindow) {
+ subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow));
+ },
+ datasourceIndex: index
+ };
+
+ for (var a = 0; a < datasource.dataKeys.length; a++) {
+ this.data[index + a].data = [];
+ }
+
+ index += datasource.dataKeys.length;
+
+ this.datasourceListeners.push(listener);
+ this.ctx.datasourceService.subscribeToDatasource(listener);
+ if (datasource.unresolvedStateEntity) {
+ this.notifyDataLoaded();
+ this.onDataUpdated();
+ }
+
+ }
+ }
+ }
+
+ alarmsSubscribe() {
this.notifyDataLoading();
- if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) {
+ if (this.timeWindowConfig) {
this.updateRealtimeSubscription();
if (this.subscriptionTimewindow.fixedWindow) {
this.onDataUpdated();
}
}
- var index = 0;
- for (var i = 0; i < this.datasources.length; i++) {
- var datasource = this.datasources[i];
- if (angular.isFunction(datasource))
- continue;
-
- var subscription = this;
-
- var listener = {
- subscriptionType: this.type,
- subscriptionTimewindow: this.subscriptionTimewindow,
- datasource: datasource,
- entityType: datasource.entityType,
- entityId: datasource.entityId,
- dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
- subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
- },
- updateRealtimeSubscription: function () {
- this.subscriptionTimewindow = subscription.updateRealtimeSubscription();
- return this.subscriptionTimewindow;
- },
- setRealtimeSubscription: function (subscriptionTimewindow) {
- subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow));
- },
- datasourceIndex: index
- };
-
- for (var a = 0; a < datasource.dataKeys.length; a++) {
- this.data[index + a].data = [];
+ var subscription = this;
+ this.alarmSourceListener = {
+ subscriptionTimewindow: this.subscriptionTimewindow,
+ alarmSource: this.alarmSource,
+ alarmsUpdated: function(alarms, apply) {
+ subscription.alarmsUpdated(alarms, apply);
}
+ }
+ this.alarms = [];
- index += datasource.dataKeys.length;
+ this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener);
- this.datasourceListeners.push(listener);
- this.ctx.datasourceService.subscribeToDatasource(listener);
- if (datasource.unresolvedStateEntity) {
- this.notifyDataLoaded();
- this.onDataUpdated();
- }
+ if (this.alarmSource.unresolvedStateEntity) {
+ this.notifyDataLoaded();
+ this.onDataUpdated();
}
}
unsubscribe() {
if (this.type !== this.ctx.types.widgetType.rpc.value) {
- for (var i = 0; i < this.datasourceListeners.length; i++) {
- var listener = this.datasourceListeners[i];
- this.ctx.datasourceService.unsubscribeFromDatasource(listener);
+ if (this.type == this.ctx.types.widgetType.alarm.value) {
+ this.alarmsUnsubscribe();
+ } else {
+ for (var i = 0; i < this.datasourceListeners.length; i++) {
+ var listener = this.datasourceListeners[i];
+ this.ctx.datasourceService.unsubscribeFromDatasource(listener);
+ }
+ this.datasourceListeners = [];
}
- this.datasourceListeners = [];
+ }
+ }
+
+ alarmsUnsubscribe() {
+ if (this.alarmSourceListener) {
+ this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener);
+ this.alarmSourceListener = null;
}
}
@@ -607,6 +731,14 @@ export default class Subscription {
}
}
+ checkAlarmSource(aliasIds) {
+ if (this.alarmSource && this.alarmSource.entityAliasId) {
+ return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1;
+ } else {
+ return false;
+ }
+ }
+
checkSubscriptions(aliasIds) {
var subscriptionsChanged = false;
for (var i = 0; i < this.datasourceListeners.length; i++) {
ui/src/app/api/widget.service.js 3(+2 -1)
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index 21c38e2..1c5e5f4 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -19,6 +19,7 @@ import tinycolor from 'tinycolor2';
import thingsboardLedLight from '../components/led-light.directive';
import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
+import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
import TbFlot from '../widget/lib/flot-widget';
import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
@@ -33,7 +34,7 @@ import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
- thingsboardTypes, thingsboardUtils])
+ thingsboardAlarmsTableWidget, thingsboardTypes, thingsboardUtils])
.factory('widgetService', WidgetService)
.name;
ui/src/app/common/types.constant.js 73(+67 -6)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index b281ad1..9610360 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -59,6 +59,53 @@ export default angular.module('thingsboard.types', [])
name: "aggregation.none"
}
},
+ alarmFields: {
+ createdTime: {
+ value: "createdTime",
+ name: "alarm.created-time",
+ time: true
+ },
+ startTime: {
+ value: "startTs",
+ name: "alarm.start-time",
+ time: true
+ },
+ endTime: {
+ value: "endTs",
+ name: "alarm.end-time",
+ time: true
+ },
+ ackTime: {
+ value: "ackTs",
+ name: "alarm.ack-time",
+ time: true
+ },
+ clearTime: {
+ value: "clearTs",
+ name: "alarm.clear-time",
+ time: true
+ },
+ originator: {
+ value: "originatorName",
+ name: "alarm.originator"
+ },
+ originatorType: {
+ value: "originator.entityType",
+ name: "alarm.originator-type"
+ },
+ type: {
+ value: "type",
+ name: "alarm.type"
+ },
+ severity: {
+ value: "severity",
+ name: "alarm.severity"
+ },
+ status: {
+ value: "status",
+ name: "alarm.status"
+ }
+ },
alarmStatus: {
activeUnack: "ACTIVE_UNACK",
activeAck: "ACTIVE_ACK",
@@ -75,23 +122,28 @@ export default angular.module('thingsboard.types', [])
alarmSeverity: {
"CRITICAL": {
name: "alarm.severity-critical",
- class: "tb-critical"
+ class: "tb-critical",
+ color: "red"
},
"MAJOR": {
name: "alarm.severity-major",
- class: "tb-major"
+ class: "tb-major",
+ color: "orange"
},
"MINOR": {
name: "alarm.severity-minor",
- class: "tb-minor"
+ class: "tb-minor",
+ color: "#ffca3d"
},
"WARNING": {
name: "alarm.severity-warning",
- class: "tb-warning"
+ class: "tb-warning",
+ color: "#abab00"
},
"INDETERMINATE": {
name: "alarm.severity-indeterminate",
- class: "tb-indeterminate"
+ class: "tb-indeterminate",
+ color: "green"
}
},
aliasFilterType: {
@@ -153,7 +205,8 @@ export default angular.module('thingsboard.types', [])
dataKeyType: {
timeseries: "timeseries",
attribute: "attribute",
- function: "function"
+ function: "function",
+ alarm: "alarm"
},
componentType: {
filter: "FILTER",
@@ -319,6 +372,14 @@ export default angular.module('thingsboard.types', [])
alias: "basic_gpio_control"
}
},
+ alarm: {
+ value: "alarm",
+ name: "widget.alarm",
+ template: {
+ bundleAlias: "alarm_widgets",
+ alias: "alarms_table"
+ }
+ },
static: {
value: "static",
name: "widget.static",
ui/src/app/common/utils.service.js 54(+52 -2)
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index 8d2e565..ebded08 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -21,8 +21,10 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
.factory('utils', Utils)
.name;
+const varsRegex = /\$\{([^\}]*)\}/g;
+
/*@ngInject*/
-function Utils($mdColorPalette, $rootScope, $window, types) {
+function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
var predefinedFunctions = {},
predefinedFunctionsList = [],
@@ -93,9 +95,32 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
dataKeys: [angular.copy(defaultDataKey)]
};
+ var defaultAlarmFields = [
+ 'createdTime',
+ 'originator',
+ 'type',
+ 'severity',
+ 'status'
+ ];
+
+ var defaultAlarmDataKeys = [];
+ for (var i=0;i<defaultAlarmFields.length;i++) {
+ var name = defaultAlarmFields[i];
+ var dataKey = {
+ name: name,
+ type: types.dataKeyType.alarm,
+ label: $translate.instant(types.alarmFields[name].name)+'',
+ color: getMaterialColor(i),
+ settings: {},
+ _hash: Math.random()
+ };
+ defaultAlarmDataKeys.push(dataKey);
+ }
+
var service = {
getDefaultDatasource: getDefaultDatasource,
getDefaultDatasourceJson: getDefaultDatasourceJson,
+ getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
getMaterialColor: getMaterialColor,
getPredefinedFunctionBody: getPredefinedFunctionBody,
getPredefinedFunctionsList: getPredefinedFunctionsList,
@@ -109,7 +134,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
cleanCopy: cleanCopy,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
- createKey: createKey
+ createKey: createKey,
+ createLabelFromDatasource: createLabelFromDatasource
}
return service;
@@ -212,6 +238,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return angular.toJson(getDefaultDatasource(dataKeySchema));
}
+ function getDefaultAlarmDataKeys() {
+ return angular.copy(defaultAlarmDataKeys);
+ }
+
function isDescriptorSchemaNotEmpty(descriptor) {
if (descriptor && descriptor.schema && descriptor.schema.properties) {
for(var prop in descriptor.schema.properties) {
@@ -357,4 +387,24 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return dataKey;
}
+ function createLabelFromDatasource(datasource, pattern) {
+ var label = angular.copy(pattern);
+ var match = varsRegex.exec(pattern);
+ while (match !== null) {
+ var variable = match[0];
+ var variableName = match[1];
+ if (variableName === 'dsName') {
+ label = label.split(variable).join(datasource.name);
+ } else if (variableName === 'entityName') {
+ label = label.split(variable).join(datasource.entityName);
+ } else if (variableName === 'deviceName') {
+ label = label.split(variable).join(datasource.entityName);
+ } else if (variableName === 'aliasName') {
+ label = label.split(variable).join(datasource.aliasName);
+ }
+ match = varsRegex.exec(pattern);
+ }
+ return label;
+ }
+
}
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index a88e656..c6b4349 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -181,6 +181,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
vm.dropWidgetShadow = dropWidgetShadow;
vm.enableWidgetFullscreen = enableWidgetFullscreen;
vm.hasTimewindow = hasTimewindow;
+ vm.hasAggregation = hasAggregation;
vm.editWidget = editWidget;
vm.exportWidget = exportWidget;
vm.removeWidget = removeWidget;
@@ -771,7 +772,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
}
function hasTimewindow(widget) {
- if (widget.type === types.widgetType.timeseries.value) {
+ if (widget.type === types.widgetType.timeseries.value || widget.type === types.widgetType.alarm.value) {
return angular.isDefined(widget.config.useDashboardTimewindow) ?
!widget.config.useDashboardTimewindow : false;
} else {
@@ -779,6 +780,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
}
}
+ function hasAggregation(widget) {
+ return widget.type === types.widgetType.timeseries.value;
+ }
+
function adoptMaxRows() {
if (vm.widgets) {
var maxRows = vm.gridsterOpts.maxRows;
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 8d77a6f..08bdd42 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -47,7 +47,7 @@
padding: vm.widgetPadding(widget)}">
<div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span>
- <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
+ <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center" tb-mousedown="$event.stopPropagation()">
<md-button id="expand-button"
diff --git a/ui/src/app/components/datasource.directive.js b/ui/src/app/components/datasource.directive.js
index eb9eafc..b5327e2 100644
--- a/ui/src/app/components/datasource.directive.js
+++ b/ui/src/app/components/datasource.directive.js
@@ -30,7 +30,7 @@ export default angular.module('thingsboard.directives.datasource', [thingsboardT
.name;
/*@ngInject*/
-function Datasource($compile, $templateCache, types) {
+function Datasource($compile, $templateCache, utils, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@@ -53,8 +53,12 @@ function Datasource($compile, $templateCache, types) {
}
scope.$watch('model.type', function (newType, prevType) {
- if (newType != prevType) {
- scope.model.dataKeys = [];
+ if (newType && prevType && newType != prevType) {
+ if (scope.widgetType == types.widgetType.alarm.value) {
+ scope.model.dataKeys = utils.getDefaultAlarmDataKeys();
+ } else {
+ scope.model.dataKeys = [];
+ }
}
});
@@ -63,9 +67,10 @@ function Datasource($compile, $templateCache, types) {
}, true);
ngModelCtrl.$render = function () {
- scope.model = {};
if (ngModelCtrl.$viewValue) {
scope.model = ngModelCtrl.$viewValue;
+ } else {
+ scope.model = {};
}
};
diff --git a/ui/src/app/components/datasource.tpl.html b/ui/src/app/components/datasource.tpl.html
index 0e91d98..0633f5f 100644
--- a/ui/src/app/components/datasource.tpl.html
+++ b/ui/src/app/components/datasource.tpl.html
@@ -29,6 +29,7 @@
ng-model="model"
datakey-settings-schema="datakeySettingsSchema"
ng-required="model.type === types.datasourceType.function"
+ widget-type="widgetType"
generate-data-key="generateDataKey({chip: chip, type: type})">
</tb-datasource-func>
<tb-datasource-entity flex
diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js
index da43274..e6ec8f8 100644
--- a/ui/src/app/components/datasource-entity.directive.js
+++ b/ui/src/app/components/datasource-entity.directive.js
@@ -45,12 +45,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
scope.ngModelCtrl = ngModelCtrl;
scope.types = types;
+ scope.alarmFields = [];
+ for (var alarmField in types.alarmFields) {
+ scope.alarmFields.push(alarmField);
+ }
+
scope.selectedTimeseriesDataKey = null;
scope.timeseriesDataKeySearchText = null;
scope.selectedAttributeDataKey = null;
scope.attributeDataKeySearchText = null;
+ scope.selectedAlarmDataKey = null;
+ scope.alarmDataKeySearchText = null;
+
scope.updateValidity = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
@@ -81,24 +89,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
});
scope.$watch('timeseriesDataKeys', function () {
- if (ngModelCtrl.$viewValue) {
- var dataKeys = [];
- dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
- dataKeys = dataKeys.concat(scope.attributeDataKeys);
- ngModelCtrl.$viewValue.dataKeys = dataKeys;
- scope.updateValidity();
- }
+ updateDataKeys();
}, true);
scope.$watch('attributeDataKeys', function () {
+ updateDataKeys();
+ }, true);
+
+ scope.$watch('alarmDataKeys', function () {
+ updateDataKeys();
+ }, true);
+
+ function updateDataKeys() {
if (ngModelCtrl.$viewValue) {
var dataKeys = [];
dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
dataKeys = dataKeys.concat(scope.attributeDataKeys);
+ dataKeys = dataKeys.concat(scope.alarmDataKeys);
ngModelCtrl.$viewValue.dataKeys = dataKeys;
scope.updateValidity();
}
- }, true);
+ }
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
@@ -111,16 +122,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
}
var timeseriesDataKeys = [];
var attributeDataKeys = [];
+ var alarmDataKeys = [];
for (var d in ngModelCtrl.$viewValue.dataKeys) {
var dataKey = ngModelCtrl.$viewValue.dataKeys[d];
if (dataKey.type === types.dataKeyType.timeseries) {
timeseriesDataKeys.push(dataKey);
} else if (dataKey.type === types.dataKeyType.attribute) {
attributeDataKeys.push(dataKey);
+ } else if (dataKey.type === types.dataKeyType.alarm) {
+ alarmDataKeys.push(dataKey);
}
}
scope.timeseriesDataKeys = timeseriesDataKeys;
scope.attributeDataKeys = attributeDataKeys;
+ scope.alarmDataKeys = alarmDataKeys;
}
};
@@ -135,6 +150,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') {
scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : '';
}
+ if (!scope.alarmDataKeySearchText || scope.alarmDataKeySearchText === '') {
+ scope.alarmDataKeySearchText = scope.alarmDataKeySearchText === '' ? null : '';
+ }
};
scope.transformTimeseriesDataKeyChip = function (chip) {
@@ -145,6 +163,10 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
};
+ scope.transformAlarmDataKeyChip = function (chip) {
+ return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm});
+ };
+
scope.showColorPicker = function (event, dataKey) {
$mdColorPicker.show({
value: dataKey.color,
@@ -196,6 +218,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
scope.timeseriesDataKeys[index] = dataKey;
} else if (dataKey.type === types.dataKeyType.attribute) {
scope.attributeDataKeys[index] = dataKey;
+ } else if (dataKey.type === types.dataKeyType.alarm) {
+ scope.alarmDataKeys[index] = dataKey;
}
ngModelCtrl.$setDirty();
}, function () {
@@ -203,20 +227,33 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
};
scope.dataKeysSearch = function (searchText, type) {
- if (scope.entityAlias) {
- var deferred = $q.defer();
- scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type})
- .then(function (dataKeys) {
- deferred.resolve(dataKeys);
- }, function (e) {
- deferred.reject(e);
- });
- return deferred.promise;
+ if (scope.widgetType == types.widgetType.alarm.value) {
+ var dataKeys = searchText ? scope.alarmFields.filter(
+ scope.createFilterForDataKey(searchText)) : scope.alarmFields;
+ return dataKeys;
} else {
- return $q.when([]);
+ if (scope.entityAlias) {
+ var deferred = $q.defer();
+ scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type})
+ .then(function (dataKeys) {
+ deferred.resolve(dataKeys);
+ }, function (e) {
+ deferred.reject(e);
+ });
+ return deferred.promise;
+ } else {
+ return $q.when([]);
+ }
}
};
+ scope.createFilterForDataKey = function (query) {
+ var lowercaseQuery = angular.lowercase(query);
+ return function filterFn(dataKey) {
+ return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0);
+ };
+ };
+
scope.createKey = function (event, chipsId) {
var chipsChild = $(chipsId, element)[0].firstElementChild;
var el = angular.element(chipsChild);
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index 2fb4608..d5cd782 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -24,7 +24,7 @@
</tb-entity-alias-select>
<section flex layout='column'>
<section flex layout='column' layout-align="center" style="padding-left: 4px;">
- <md-chips flex
+ <md-chips flex ng-if="widgetType != types.widgetType.alarm.value"
id="timeseries_datakey_chips"
ng-required="true"
ng-model="timeseriesDataKeys" md-autocomplete-snap
@@ -128,10 +128,60 @@
</div>
</md-chip-template>
</md-chips>
+ <md-chips flex ng-if="widgetType == types.widgetType.alarm.value"
+ id="alarm_datakey_chips"
+ ng-required="true"
+ ng-model="alarmDataKeys" md-autocomplete-snap
+ md-transform-chip="transformAlarmDataKeyChip($chip)"
+ md-require-match="true">
+ <md-autocomplete
+ md-no-cache="true"
+ id="alarm_datakey"
+ md-selected-item="selectedAlarmDataKey"
+ md-search-text="alarmDataKeySearchText"
+ md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{'datakey.alarm' | translate }}"
+ md-menu-class="tb-alarm-datakey-autocomplete">
+ <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+ <md-not-found>
+ <div class="tb-not-found">
+ <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)">
+ <span translate>entity.no-keys-found</span>
+ </div>
+ <div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
+ <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span>
+ </div>
+ </div>
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <div layout="row" layout-align="start center" class="tb-attribute-chip">
+ <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+ <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
+ </div>
+ <div layout="row" flex>
+ <div class="tb-chip-label">
+ {{$chip.label}}
+ </div>
+ <div class="tb-chip-separator">: </div>
+ <div class="tb-chip-label">
+ <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
+ <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
+ </div>
+ </div>
+ <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+ <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+ </md-button>
+ </div>
+ </md-chip-template>
+ </md-chips>
</section>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
<div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div>
<div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div>
+ <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
</div>
</section>
</section>
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index fe54696..2a2ae82 100644
--- a/ui/src/app/components/datasource-func.directive.js
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -43,18 +43,27 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
+ scope.types = types;
+
scope.functionTypes = utils.getPredefinedFunctionsList();
+ scope.alarmFields = [];
+ for (var alarmField in types.alarmFields) {
+ scope.alarmFields.push(alarmField);
+ }
scope.selectedDataKey = null;
scope.dataKeySearchText = null;
+ scope.selectedAlarmDataKey = null;
+ scope.alarmDataKeySearchText = null;
+
scope.updateValidity = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
var dataValid = angular.isDefined(value) && value != null;
ngModelCtrl.$setValidity('deviceData', dataValid);
if (dataValid) {
- ngModelCtrl.$setValidity('funcTypes',
+ ngModelCtrl.$setValidity('datasourceKeys',
angular.isDefined(value.dataKeys) &&
value.dataKeys != null &&
value.dataKeys.length > 0);
@@ -63,13 +72,22 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
};
scope.$watch('funcDataKeys', function () {
+ updateDataKeys();
+ }, true);
+
+ scope.$watch('alarmDataKeys', function () {
+ updateDataKeys();
+ }, true);
+
+ function updateDataKeys() {
if (ngModelCtrl.$viewValue) {
var dataKeys = [];
dataKeys = dataKeys.concat(scope.funcDataKeys);
+ dataKeys = dataKeys.concat(scope.alarmDataKeys);
ngModelCtrl.$viewValue.dataKeys = dataKeys;
scope.updateValidity();
}
- }, true);
+ }
scope.$watch('datasourceName', function () {
if (ngModelCtrl.$viewValue) {
@@ -81,18 +99,31 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var funcDataKeys = [];
+ var alarmDataKeys = [];
if (ngModelCtrl.$viewValue.dataKeys) {
- funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
+ for (var d=0;d<ngModelCtrl.$viewValue.dataKeys.length;d++) {
+ var dataKey = ngModelCtrl.$viewValue.dataKeys[d];
+ if (dataKey.type === types.dataKeyType.function) {
+ funcDataKeys.push(dataKey);
+ } else if (dataKey.type === types.dataKeyType.alarm) {
+ alarmDataKeys.push(dataKey);
+ }
+ }
}
scope.funcDataKeys = funcDataKeys;
+ scope.alarmDataKeys = alarmDataKeys;
scope.datasourceName = ngModelCtrl.$viewValue.name;
}
};
- scope.transformDataKeyChip = function (chip) {
+ scope.transformFuncDataKeyChip = function (chip) {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.function});
};
+ scope.transformAlarmDataKeyChip = function (chip) {
+ return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm});
+ };
+
scope.showColorPicker = function (event, dataKey) {
$mdColorPicker.show({
value: dataKey.color,
@@ -129,7 +160,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
dataKey: angular.copy(dataKey),
dataKeySettingsSchema: scope.datakeySettingsSchema,
entityAlias: null,
- entityAliases: null
+ aliasController: null
},
parent: angular.element($document[0].body),
fullscreen: true,
@@ -140,7 +171,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
w.triggerHandler('resize');
}
}).then(function (dataKey) {
- scope.funcDataKeys[index] = dataKey;
+ if (dataKey.type === types.dataKeyType.function) {
+ scope.funcDataKeys[index] = dataKey;
+ } else if (dataKey.type === types.dataKeyType.alarm) {
+ scope.alarmDataKeys[index] = dataKey;
+ }
ngModelCtrl.$setDirty();
}, function () {
});
@@ -151,8 +186,9 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
}
scope.dataKeysSearch = function (dataKeySearchText) {
- var dataKeys = dataKeySearchText ? scope.functionTypes.filter(
- scope.createFilterForDataKey(dataKeySearchText)) : scope.functionTypes;
+ var targetKeys = scope.widgetType == types.widgetType.alarm.value ? scope.alarmFields : scope.functionTypes;
+ var dataKeys = dataKeySearchText ? targetKeys.filter(
+ scope.createFilterForDataKey(dataKeySearchText)) : targetKeys;
return dataKeys;
};
@@ -180,6 +216,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
restrict: "E",
require: "^ngModel",
scope: {
+ widgetType: '=',
generateDataKey: '&',
datakeySettingsSchema: '='
},
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
index e8e08f7..1ce1108 100644
--- a/ui/src/app/components/datasource-func.tpl.html
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -17,18 +17,19 @@
-->
<section class="tb-datasource-func" flex layout='column'
layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
- <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
+ <md-input-container ng-if="widgetType != types.widgetType.alarm.value"
+ class="tb-datasource-name" md-no-float style="min-width: 200px;">
<input name="datasourceName"
placeholder="{{ 'datasource.name' | translate }}"
ng-model="datasourceName"
aria-label="{{ 'datasource.name' | translate }}">
</md-input-container>
<section flex layout='column' style="padding-left: 4px;">
- <md-chips flex
+ <md-chips flex ng-if="widgetType != types.widgetType.alarm.value"
id="function_datakey_chips"
ng-required="true"
ng-model="funcDataKeys" md-autocomplete-snap
- md-transform-chip="transformDataKeyChip($chip)"
+ md-transform-chip="transformFuncDataKeyChip($chip)"
md-require-match="false">
<md-autocomplete
md-no-cache="false"
@@ -75,8 +76,58 @@
</div>
</md-chip-template>
</md-chips>
+ <md-chips flex ng-if="widgetType == types.widgetType.alarm.value"
+ id="alarm_datakey_chips"
+ ng-required="true"
+ ng-model="alarmDataKeys" md-autocomplete-snap
+ md-transform-chip="transformAlarmDataKeyChip($chip)"
+ md-require-match="true">
+ <md-autocomplete
+ md-no-cache="true"
+ id="alarm_datakey"
+ md-selected-item="selectedAlarmDataKey"
+ md-search-text="alarmDataKeySearchText"
+ md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{'datakey.alarm' | translate }}"
+ md-menu-class="tb-alarm-datakey-autocomplete">
+ <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+ <md-not-found>
+ <div class="tb-not-found">
+ <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)">
+ <span translate>entity.no-keys-found</span>
+ </div>
+ <div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
+ <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span>
+ </div>
+ </div>
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <div layout="row" layout-align="start center" class="tb-attribute-chip">
+ <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+ <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
+ </div>
+ <div layout="row" flex>
+ <div class="tb-chip-label">
+ {{$chip.label}}
+ </div>
+ <div class="tb-chip-separator">: </div>
+ <div class="tb-chip-label">
+ <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
+ <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
+ </div>
+ </div>
+ <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+ <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+ </md-button>
+ </div>
+ </md-chip-template>
+ </md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
- <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
+ <div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div>
+ <div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
</div>
</section>
</section>
diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js
index 1642a34..3206d47 100644
--- a/ui/src/app/components/timewindow.directive.js
+++ b/ui/src/app/components/timewindow.directive.js
@@ -64,7 +64,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
scope.historyOnly = angular.isDefined(attrs.historyOnly);
- scope.aggregation = angular.isDefined(attrs.aggregation);
+ scope.aggregation = scope.$eval(attrs.aggregation);
scope.isToolbar = angular.isDefined(attrs.isToolbar);
ui/src/app/components/widget.controller.js 29(+22 -7)
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index 08885b8..2f76396 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -21,7 +21,7 @@ import Subscription from '../api/subscription';
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
- datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
+ datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
var vm = this;
@@ -44,7 +44,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var widgetContext = {
inited: false,
- $scope: $scope,
$container: null,
$containerParent: null,
width: 0,
@@ -113,6 +112,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
timeService: timeService,
deviceService: deviceService,
datasourceService: datasourceService,
+ alarmService: alarmService,
utils: utils,
widgetUtils: widgetContext.utils,
dashboardTimewindowApi: dashboardTimewindowApi,
@@ -285,9 +285,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var deferred = $q.defer();
if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
options = {
- type: widget.type,
- datasources: angular.copy(widget.config.datasources)
- };
+ type: widget.type
+ }
+ if (widget.type == types.widgetType.alarm.value) {
+ options.alarmSource = angular.copy(widget.config.alarmSource);
+ } else {
+ options.datasources = angular.copy(widget.config.datasources)
+ }
+
defaultComponentsOptions(options);
createSubscription(options).then(
@@ -320,7 +325,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.executingRpcRequest = subscription.executingRpcRequest;
},
onRpcSuccess: function(subscription) {
- $scope.executingRpcRequest = subscription.executingRpcRequest;
+ $scope.executingRpcRequest = subscription.executingRpcRequest;
$scope.rpcErrorText = subscription.rpcErrorText;
$scope.rpcRejection = subscription.rpcRejection;
},
@@ -436,7 +441,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
widgetContext.$container = $('#container', containerElement);
widgetContext.$containerParent = $(containerElement);
- $compile($element.contents())($scope);
+ if (widgetSizeDetected) {
+ widgetContext.$container.css('height', widgetContext.height + 'px');
+ widgetContext.$container.css('width', widgetContext.width + 'px');
+ }
+
+ widgetContext.$scope = $scope.$new();
+
+ $compile($element.contents())(widgetContext.$scope);
addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
}
@@ -444,6 +456,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function destroyWidgetElement() {
removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
$element.html('');
+ if (widgetContext.$scope) {
+ widgetContext.$scope.$destroy();
+ }
widgetContext.$container = null;
widgetContext.$containerParent = null;
}
ui/src/app/components/widget-config.directive.js 80(+63 -17)
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index d5b4239..e0b4800 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget-config.directive.js
@@ -43,7 +43,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
.name;
/*@ngInject*/
-function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, utils) {
+function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout, types, utils) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@@ -87,6 +87,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
value: null
}
+ scope.alarmSource = {
+ value: null
+ }
+
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var config = ngModelCtrl.$viewValue.config;
@@ -113,7 +117,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.showLegend = angular.isDefined(config.showLegend) ?
config.showLegend : scope.widgetType === types.widgetType.timeseries.value;
scope.legendConfig = config.legendConfig;
- if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value
+ if (scope.widgetType !== types.widgetType.rpc.value &&
+ scope.widgetType !== types.widgetType.alarm.value &&
+ scope.widgetType !== types.widgetType.static.value
&& scope.isDataEnabled) {
if (scope.datasources) {
scope.datasources.splice(0, scope.datasources.length);
@@ -137,6 +143,12 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
} else {
scope.targetDeviceAlias.value = null;
}
+ } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) {
+ if (config.alarmSource) {
+ scope.alarmSource.value = config.alarmSource;
+ } else {
+ scope.alarmSource.value = null;
+ }
}
scope.settings = config.settings;
@@ -175,6 +187,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0;
ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
+ } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) {
+ valid = config && config.alarmSource;
+ ngModelCtrl.$setValidity('alarmSource', valid);
} else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
valid = config && config.datasources && config.datasources.length > 0;
ngModelCtrl.$setValidity('datasources', valid);
@@ -253,7 +268,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}, true);
scope.$watch('datasources', function () {
- if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType !== types.widgetType.rpc.value
+ if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config
+ && scope.widgetType !== types.widgetType.rpc.value
+ && scope.widgetType !== types.widgetType.alarm.value
&& scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
var value = ngModelCtrl.$viewValue;
var config = value.config;
@@ -286,6 +303,20 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}
});
+ scope.$watch('alarmSource.value', function () {
+ if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) {
+ var value = ngModelCtrl.$viewValue;
+ var config = value.config;
+ if (scope.alarmSource.value) {
+ config.alarmSource = scope.alarmSource.value;
+ } else {
+ config.alarmSource = null;
+ }
+ ngModelCtrl.$setViewValue(value);
+ scope.updateValidity();
+ }
+ });
+
scope.addDatasource = function () {
var newDatasource;
if (scope.functionsOnly) {
@@ -320,10 +351,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
return chip;
}
+ var label = chip;
+ if (type === types.dataKeyType.alarm) {
+ var alarmField = types.alarmFields[chip];
+ if (alarmField) {
+ label = $translate.instant(alarmField.name)+'';
+ }
+ }
+ label = scope.genNextLabel(label);
+
var result = {
name: chip,
type: type,
- label: scope.genNextLabel(chip),
+ label: label,
color: scope.genNextColor(),
settings: {},
_hash: Math.random()
@@ -351,15 +391,18 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
var matches = false;
do {
matches = false;
- if (value.config.datasources) {
- for (var d=0;d<value.config.datasources.length;d++) {
- var datasource = value.config.datasources[d];
- for (var k=0;k<datasource.dataKeys.length;k++) {
- var dataKey = datasource.dataKeys[k];
- if (dataKey.label === label) {
- i++;
- label = name + ' ' + i;
- matches = true;
+ var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources;
+ if (datasources) {
+ for (var d=0;d<datasources.length;d++) {
+ var datasource = datasources[d];
+ if (datasource && datasource.dataKeys) {
+ for (var k = 0; k < datasource.dataKeys.length; k++) {
+ var dataKey = datasource.dataKeys[k];
+ if (dataKey.label === label) {
+ i++;
+ label = name + ' ' + i;
+ matches = true;
+ }
}
}
}
@@ -371,10 +414,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.genNextColor = function () {
var i = 0;
var value = ngModelCtrl.$viewValue;
- if (value.config.datasources) {
- for (var d=0;d<value.config.datasources.length;d++) {
- var datasource = value.config.datasources[d];
- i += datasource.dataKeys.length;
+ var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources;
+ if (datasources) {
+ for (var d=0;d<datasources.length;d++) {
+ var datasource = datasources[d];
+ if (datasource && datasource.dataKeys) {
+ i += datasource.dataKeys.length;
+ }
}
}
return utils.getMaterialColor(i);
ui/src/app/components/widget-config.tpl.html 28(+25 -3)
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html
index 0500e91..386f33a 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -20,18 +20,22 @@
<md-tab label="{{ 'widget-config.data' | translate }}"
ng-show="widgetType !== types.widgetType.static.value">
<md-content class="md-padding" layout="column">
- <div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center"
+ <div ng-show="widgetType === types.widgetType.timeseries.value || widgetType === types.widgetType.alarm.value" layout='column' layout-align="center"
layout-gt-sm='row' layout-align-gt-sm="start center">
<md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
</md-checkbox>
<section flex layout="row" layout-align="start center" style="margin-bottom: 16px;">
<span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span>
- <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
+ <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation="{{ widgetType === types.widgetType.timeseries.value }}"
+ flex ng-model="timewindow"></tb-timewindow>
</section>
</div>
<v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
- ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value && isDataEnabled">
+ ng-show="widgetType !== types.widgetType.rpc.value
+ && widgetType !== types.widgetType.alarm.value
+ && widgetType !== types.widgetType.static.value
+ && isDataEnabled">
<v-pane id="datasources-pane" expanded="true">
<v-pane-header>
{{ 'widget-config.datasources' | translate }}
@@ -112,6 +116,24 @@
</v-pane-content>
</v-pane>
</v-accordion>
+ <v-accordion id="alarn-source-accordion" control="alarmSourceAccordion" class="vAccordion--default"
+ ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled">
+ <v-pane id="alarm-source-pane" expanded="true">
+ <v-pane-header>
+ {{ 'widget-config.alarm-source' | translate }}
+ </v-pane-header>
+ <v-pane-content style="padding: 0 5px;">
+ <tb-datasource flex
+ ng-model="alarmSource.value"
+ widget-type="widgetType"
+ functions-only="functionsOnly"
+ alias-controller="aliasController"
+ datakey-settings-schema="datakeySettingsSchema"
+ generate-data-key="generateDataKey(chip,type)"
+ on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"></tb-datasource>
+ </v-pane-content>
+ </v-pane>
+ </v-accordion>
</md-content>
</md-tab>
<md-tab label="{{ 'widget-config.settings' | translate }}">
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index 5372b62..fd2ada7 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -76,6 +76,10 @@ export default function AddWidgetController($scope, widgetService, entityService
link = 'widgetsConfigRpc';
break;
}
+ case types.widgetType.alarm.value: {
+ link = 'widgetsConfigAlarm';
+ break;
+ }
case types.widgetType.static.value: {
link = 'widgetsConfigStatic';
break;
ui/src/app/dashboard/dashboard.controller.js 24(+9 -15)
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 65eb833..6b9b0b3 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -47,6 +47,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.latestWidgetTypes = [];
vm.timeseriesWidgetTypes = [];
vm.rpcWidgetTypes = [];
+ vm.alarmWidgetTypes = [];
vm.staticWidgetTypes = [];
vm.widgetEditMode = $state.$current.data.widgetEditMode;
vm.iframeMode = $rootScope.iframeMode;
@@ -263,6 +264,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.latestWidgetTypes = [];
vm.timeseriesWidgetTypes = [];
vm.rpcWidgetTypes = [];
+ vm.alarmWidgetTypes = [];
vm.staticWidgetTypes = [];
if (vm.widgetsBundle) {
var bundleAlias = vm.widgetsBundle.alias;
@@ -308,6 +310,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.latestWidgetTypes.push(widget);
} else if (widgetTypeInfo.type === types.widgetType.rpc.value) {
vm.rpcWidgetTypes.push(widget);
+ } else if (widgetTypeInfo.type === types.widgetType.alarm.value) {
+ vm.alarmWidgetTypes.push(widget);
} else if (widgetTypeInfo.type === types.widgetType.static.value) {
vm.staticWidgetTypes.push(widget);
}
@@ -358,21 +362,6 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
-
- /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
- .then(
- function(resolution) {
- if (resolution.error && !isTenantAdmin()) {
- vm.configurationError = true;
- showAliasesResolutionError(resolution.error);
- } else {
- vm.dashboardConfiguration = vm.dashboard.configuration;
- vm.dashboardCtx.dashboard = vm.dashboard;
- vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;
- vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
- }
- }
- );*/
}, function fail() {
vm.configurationError = true;
});
@@ -744,6 +733,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget
link = 'widgetsConfigRpc';
break;
}
+ case types.widgetType.alarm.value: {
+ link = 'widgetsConfigAlarm';
+ break;
+ }
case types.widgetType.static.value: {
link = 'widgetsConfigStatic';
break;
@@ -851,6 +844,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.timeseriesWidgetTypes = [];
vm.latestWidgetTypes = [];
vm.rpcWidgetTypes = [];
+ vm.alarmWidgetTypes = [];
vm.staticWidgetTypes = [];
}
ui/src/app/dashboard/dashboard.tpl.html 19(+16 -3)
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 202268f..96ce711 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -52,7 +52,7 @@
<tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
is-toolbar
direction="left"
- tooltip-direction="bottom" aggregation
+ tooltip-direction="bottom" aggregation="true"
ng-model="vm.dashboardCtx.dashboardTimewindow">
</tb-timewindow>
<tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
@@ -179,6 +179,7 @@
<tb-edit-widget
dashboard="vm.dashboard"
alias-controller="vm.dashboardCtx.aliasController"
+ widget-edit-mode="vm.widgetEditMode"
widget="vm.editingWidget"
widget-layout="vm.editingWidgetLayout"
the-form="vm.widgetForm">
@@ -205,7 +206,8 @@
</header-pane>
<div ng-if="vm.isAddingWidget">
<md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
- vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
+ vm.rpcWidgetTypes.length > 0 || vm.alarmWidgetTypes.length > 0 ||
+ vm.staticWidgetTypes.length > 0"
flex
class="tb-absolute-fill" md-border-bottom>
<md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
@@ -238,6 +240,16 @@
on-widget-clicked="vm.addWidgetFromType(event, widget)">
</tb-dashboard>
</md-tab>
+ <md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}">
+ <tb-dashboard
+ widgets="vm.alarmWidgetTypes"
+ is-edit="false"
+ is-mobile="true"
+ is-edit-action-enabled="false"
+ is-remove-action-enabled="false"
+ on-widget-clicked="vm.addWidgetFromType(event, widget)">
+ </tb-dashboard>
+ </md-tab>
<md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
<tb-dashboard
widgets="vm.staticWidgetTypes"
@@ -250,7 +262,8 @@
</md-tab>
</md-tabs>
<span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
- vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
+ vm.rpcWidgetTypes.length === 0 && vm.alarmWidgetTypes.length === 0 &&
+ vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
layout-align="center center"
style="text-transform: uppercase; display: flex;"
class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index 6532914..d0c651a 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -131,6 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
scope: {
dashboard: '=',
aliasController: '=',
+ widgetEditMode: '=',
widget: '=',
widgetLayout: '=',
theForm: '='
diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html
index a9ff0b6..4b4382e 100644
--- a/ui/src/app/dashboard/edit-widget.tpl.html
+++ b/ui/src/app/dashboard/edit-widget.tpl.html
@@ -22,7 +22,7 @@
widget-settings-schema="settingsSchema"
datakey-settings-schema="dataKeySettingsSchema"
alias-controller="aliasController"
- functions-only="functionsOnly"
+ functions-only="widgetEditMode"
fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
the-form="theForm"></tb-widget-config>
diff --git a/ui/src/app/help/help-links.constant.js b/ui/src/app/help/help-links.constant.js
index 22f9d75..faed304 100644
--- a/ui/src/app/help/help-links.constant.js
+++ b/ui/src/app/help/help-links.constant.js
@@ -87,6 +87,7 @@ export default angular.module('thingsboard.help', [])
widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries",
widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest",
widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc",
+ widgetsConfigAlarm: helpBaseUrl + "/docs/user-guide/ui/dashboards#alarm",
widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static",
},
getPluginLink: function(plugin) {
ui/src/app/locale/locale.constant.js 12(+10 -2)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index a54eba4..85fe2b0 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -131,6 +131,7 @@ export default angular.module('thingsboard.locale', [])
"type": "Type",
"severity": "Severity",
"originator": "Originator",
+ "originator-type": "Originator type",
"details": "Details",
"status": "Status",
"alarm-details": "Alarm details",
@@ -144,7 +145,10 @@ export default angular.module('thingsboard.locale', [])
"severity-warning": "Warning",
"severity-indeterminate": "Indeterminate",
"acknowledge": "Acknowledge",
- "clear": "Clear"
+ "clear": "Clear",
+ "search": "Search alarms",
+ "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected",
+ "no-data": "No data to display"
},
"alias": {
"add": "Add alias",
@@ -486,8 +490,10 @@ export default angular.module('thingsboard.locale', [])
"configuration": "Data key configuration",
"timeseries": "Timeseries",
"attributes": "Attributes",
+ "alarm": "Alarm fields",
"timeseries-required": "Entity timeseries are required.",
"timeseries-or-attributes-required": "Entity timeseries/attributes are required.",
+ "alarm-fields-required": "Alarm fields are required.",
"function-types": "Function types",
"function-types-required": "Function types are required."
},
@@ -1046,6 +1052,7 @@ export default angular.module('thingsboard.locale', [])
"timeseries": "Time series",
"latest-values": "Latest values",
"rpc": "Control widget",
+ "alarm": "Alarm widget",
"static": "Static widget",
"select-widget-type": "Select widget type",
"missing-widget-title-error": "Widget title must be specified!",
@@ -1133,7 +1140,8 @@ export default angular.module('thingsboard.locale', [])
"datasource-parameters": "Parameters",
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
- "target-device": "Target device"
+ "target-device": "Target device",
+ "alarm-source": "Alarm source"
},
"widget-type": {
"import": "Import widget type",
ui/src/app/widget/lib/alarms-table-widget.js 390(+390 -0)
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
new file mode 100644
index 0000000..9a3edcf
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -0,0 +1,390 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './alarms-table-widget.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import tinycolor from 'tinycolor2';
+import cssjs from '../../../vendor/css.js/css';
+
+export default angular.module('thingsboard.widgets.alarmsTableWidget', [])
+ .directive('tbAlarmsTableWidget', AlarmsTableWidget)
+ .name;
+
+/*@ngInject*/
+function AlarmsTableWidget() {
+ return {
+ restrict: "E",
+ scope: true,
+ bindToController: {
+ tableId: '=',
+ config: '=',
+ subscription: '='
+ },
+ controller: AlarmsTableWidgetController,
+ controllerAs: 'vm',
+ templateUrl: alarmsTableWidgetTemplate
+ };
+}
+
+/*@ngInject*/
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUtil, $translate, utils, types) {
+ var vm = this;
+
+ vm.stylesInfo = {};
+ vm.contentsInfo = {};
+
+ vm.showData = true;
+ vm.hasData = false;
+
+ vm.alarms = [];
+ vm.alarmsCount = 0;
+ vm.selectedAlarms = []
+
+ vm.alarmSource = null;
+ vm.allAlarms = null;
+
+ vm.currentAlarm = null;
+
+ vm.query = {
+ order: '-'+types.alarmFields.createdTime.value,
+ limit: 10,
+ page: 1,
+ search: null
+ };
+
+ vm.alarmsTitle = $translate.instant('alarm.alarms');
+
+ vm.enterFilterMode = enterFilterMode;
+ vm.exitFilterMode = exitFilterMode;
+ vm.onReorder = onReorder;
+ vm.onPaginate = onPaginate;
+
+ vm.cellStyle = cellStyle;
+ vm.cellContent = cellContent;
+
+ $scope.$watch('vm.config', function() {
+ if (vm.config) {
+ vm.settings = vm.config.settings;
+ vm.widgetConfig = vm.config.widgetConfig;
+ initializeConfig();
+ }
+ });
+
+ $scope.$watch("vm.query.search", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
+ updateAlarms();
+ }
+ });
+
+ $scope.$watch('vm.subscription', function() {
+ if (vm.subscription) {
+ vm.alarmSource = vm.subscription.alarmSource;
+ updateAlarmSource();
+ }
+ });
+
+ $scope.$on('alarms-table-data-updated', function(event, tableId) {
+ if (vm.tableId == tableId) {
+ if (vm.subscription) {
+ vm.allAlarms = vm.subscription.alarms;
+ updateAlarms(true);
+ $scope.$digest();
+ }
+ }
+ });
+
+ $scope.$watch(function() { return $mdMedia('gt-xs'); }, function(isGtXs) {
+ vm.isGtXs = isGtXs;
+ });
+
+ $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) {
+ vm.isGtMd = isGtMd;
+ if (vm.isGtMd) {
+ vm.limitOptions = [5, 10, 15];
+ } else {
+ vm.limitOptions = null;
+ }
+ });
+
+ function initializeConfig() {
+
+ if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
+ vm.alarmsTitle = vm.settings.alarmsTitle;
+ }
+ //TODO:
+
+ var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
+ var defaultColor = tinycolor(origColor);
+ var mdDark = defaultColor.setAlpha(0.87).toRgbString();
+ var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString();
+ var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString();
+ //var mdDarkIcon = mdDarkSecondary;
+ var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();
+
+ var cssString = 'table.md-table th.md-column {\n'+
+ 'color: ' + mdDarkSecondary + ';\n'+
+ '}\n'+
+ 'table.md-table th.md-column md-icon.md-sort-icon {\n'+
+ 'color: ' + mdDarkDisabled + ';\n'+
+ '}\n'+
+ 'table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\n'+
+ 'color: ' + mdDark + ';\n'+
+ '}\n'+
+ 'table.md-table td.md-cell {\n'+
+ 'color: ' + mdDark + ';\n'+
+ 'border-top: 1px '+mdDarkDivider+' solid;\n'+
+ '}\n'+
+ 'table.md-table td.md-cell.md-placeholder {\n'+
+ 'color: ' + mdDarkDisabled + ';\n'+
+ '}\n'+
+ 'table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\n'+
+ 'color: ' + mdDarkSecondary + ';\n'+
+ '}\n'+
+ '.md-table-pagination {\n'+
+ 'color: ' + mdDarkSecondary + ';\n'+
+ 'border-top: 1px '+mdDarkDivider+' solid;\n'+
+ '}\n'+
+ '.md-table-pagination .buttons md-icon {\n'+
+ 'color: ' + mdDarkSecondary + ';\n'+
+ '}\n'+
+ '.md-table-pagination md-select:not([disabled]):focus .md-select-value {\n'+
+ 'color: ' + mdDarkSecondary + ';\n'+
+ '}';
+
+ var cssParser = new cssjs();
+ cssParser.testMode = false;
+ var namespace = 'ts-table-' + hashCode(cssString);
+ cssParser.cssPreviewNamespace = namespace;
+ cssParser.createStyleElement(namespace, cssString);
+ $element.addClass(namespace);
+
+ function hashCode(str) {
+ var hash = 0;
+ var i, char;
+ if (str.length === 0) return hash;
+ for (i = 0; i < str.length; i++) {
+ char = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash;
+ }
+ return hash;
+ }
+ }
+
+ function enterFilterMode () {
+ vm.query.search = '';
+ }
+
+ function exitFilterMode () {
+ vm.query.search = null;
+ updateAlarms();
+ }
+
+ function onReorder () {
+ updateAlarms();
+ }
+
+ function onPaginate () {
+ updateAlarms();
+ }
+
+ function updateAlarms(preserveSelections) {
+ if (!preserveSelections) {
+ vm.selectedAlarms = [];
+ }
+ var result = $filter('orderBy')(vm.allAlarms, vm.query.order);
+ if (vm.query.search != null) {
+ result = $filter('filter')(result, {$: vm.query.search});
+ }
+ vm.alarmsCount = result.length;
+ var startIndex = vm.query.limit * (vm.query.page - 1);
+ vm.alarms = result.slice(startIndex, startIndex + vm.query.limit);
+ if (preserveSelections) {
+ var newSelectedAlarms = [];
+ if (vm.selectedAlarms && vm.selectedAlarms.length) {
+ var i = vm.selectedAlarms.length;
+ while (i--) {
+ var selectedAlarm = vm.selectedAlarms[i];
+ if (selectedAlarm.id) {
+ result = $filter('filter')(vm.alarms, {id: {id: selectedAlarm.id.id} });
+ if (result && result.length) {
+ newSelectedAlarms.push(result[0]);
+ }
+ }
+ }
+ }
+ vm.selectedAlarms = newSelectedAlarms;
+ }
+ }
+
+ function cellStyle(alarm, key) {
+ var style = {};
+ if (alarm && key) {
+ var styleInfo = vm.stylesInfo[key.label];
+ var value = getAlarmValue(alarm, key);
+ if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
+ try {
+ style = styleInfo.cellStyleFunction(value);
+ } catch (e) {
+ style = {};
+ }
+ } else {
+ style = defaultStyle(key, value);
+ }
+ }
+ return style;
+ }
+
+ function cellContent(alarm, key) {
+ var strContent = '';
+ if (alarm && key) {
+ var contentInfo = vm.contentsInfo[key.label];
+ var value = getAlarmValue(alarm, key);
+ if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
+ if (angular.isDefined(value)) {
+ strContent = '' + value;
+ }
+ var content = strContent;
+ try {
+ content = contentInfo.cellContentFunction(value, alarm, $filter);
+ } catch (e) {
+ content = strContent;
+ }
+ } else {
+ content = defaultContent(key, value);
+ }
+ return content;
+ } else {
+ return strContent;
+ }
+ }
+
+ function defaultContent(key, value) {
+ if (angular.isDefined(value)) {
+ var alarmField = types.alarmFields[key.name];
+ if (alarmField) {
+ if (alarmField.time) {
+ return $filter('date')(value, 'yyyy-MM-dd HH:mm:ss');
+ } else if (alarmField.value == types.alarmFields.severity.value) {
+ return $translate.instant(types.alarmSeverity[value].name);
+ } else if (alarmField.value == types.alarmFields.status.value) {
+ return $translate.instant('alarm.display-status.'+value);
+ } else if (alarmField.value == types.alarmFields.originatorType.value) {
+ return $translate.instant(types.entityTypeTranslations[value].type);
+ }
+ else {
+ return value;
+ }
+ } else {
+ return '';
+ }
+ } else {
+ return '';
+ }
+ }
+ function defaultStyle(key, value) {
+ if (angular.isDefined(value)) {
+ var alarmField = types.alarmFields[key.name];
+ if (alarmField) {
+ if (alarmField.value == types.alarmFields.severity.value) {
+ return {
+ fontWeight: 'bold',
+ color: types.alarmSeverity[value].color
+ };
+ } else {
+ return {};
+ }
+ }
+ } else {
+ return {};
+ }
+ }
+
+ const getDescendantProp = (obj, path) => (
+ path.split('.').reduce((acc, part) => acc && acc[part], obj)
+ );
+
+ function getAlarmValue(alarm, key) {
+ var alarmField = types.alarmFields[key.name];
+ if (alarmField) {
+ return getDescendantProp(alarm, alarmField.value);
+ } else {
+ return getDescendantProp(alarm, key.name);
+ }
+ }
+
+ function updateAlarmSource() {
+
+ if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
+ vm.alarmsTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.settings.alarmsTitle);
+ }
+
+ vm.stylesInfo = {};
+ vm.contentsInfo = {};
+
+ for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
+ var dataKey = vm.alarmSource.dataKeys[d];
+ var keySettings = dataKey.settings;
+
+ var cellStyleFunction = null;
+ var useCellStyleFunction = false;
+
+ if (keySettings.useCellStyleFunction === true) {
+ if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {
+ try {
+ cellStyleFunction = new Function('value', keySettings.cellStyleFunction);
+ useCellStyleFunction = true;
+ } catch (e) {
+ cellStyleFunction = null;
+ useCellStyleFunction = false;
+ }
+ }
+ }
+
+ vm.stylesInfo[dataKey.label] = {
+ useCellStyleFunction: useCellStyleFunction,
+ cellStyleFunction: cellStyleFunction
+ };
+
+ var cellContentFunction = null;
+ var useCellContentFunction = false;
+
+ if (keySettings.useCellContentFunction === true) {
+ if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {
+ try {
+ cellContentFunction = new Function('value, alarm, filter', keySettings.cellContentFunction);
+ useCellContentFunction = true;
+ } catch (e) {
+ cellContentFunction = null;
+ useCellContentFunction = false;
+ }
+ }
+ }
+
+ vm.contentsInfo[dataKey.label] = {
+ useCellContentFunction: useCellContentFunction,
+ cellContentFunction: cellContentFunction
+ };
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss
new file mode 100644
index 0000000..afe50f0
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.scss
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-alarms-table {
+ margin-top: 15px;
+ &.tb-data-table {
+ table.md-table {
+ tbody {
+ tr {
+ td {
+ &.ag-action-cell {
+ min-width: 40px;
+ max-width: 40px;
+ width: 40px;
+ }
+ }
+ }
+ }
+ }
+ }
+}
ui/src/app/widget/lib/alarms-table-widget.tpl.html 116(+116 -0)
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
new file mode 100644
index 0000000..f1732b0
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -0,0 +1,116 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<div class="tb-absolute-fill tb-alarms-table tb-data-table" layout="column">
+ <div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column">
+ <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedAlarms.length
+ && vm.query.search === null">
+ <div class="md-toolbar-tools">
+ <span>{{ vm.alarmsTitle }}</span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-icon>search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.search' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedAlarms.length &&
+ vm.query.search != null">
+ <div class="md-toolbar-tools">
+ <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+ <md-tooltip md-direction="top">
+ {{'alarm.search' | translate}}
+ </md-tooltip>
+ </md-button>
+ <md-input-container flex>
+ <label> </label>
+ <input ng-model="vm.query.search" placeholder="{{'alarm.search' | translate}}"/>
+ </md-input-container>
+ <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
+ <md-icon aria-label="Close" class="material-icons">close</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedAlarms.length">
+ <div class="md-toolbar-tools">
+ <span translate="alarm.selected-alarms"
+ translate-values="{count: vm.selectedAlarms.length}"
+ translate-interpolation="messageformat"></span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.ackAlarms($event)">
+ <md-icon>done</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'alarm.acknowledge' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" ng-click="vm.clearAlarms($event)">
+ <md-icon>clear</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'alarm.clear' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-table-container flex>
+ <table md-table md-row-select multiple="" ng-model="vm.selectedAlarms">
+ <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
+ <tr md-row>
+ <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.label }}</span></th>
+ <th md-column><span> </span></th>
+ </tr>
+ </thead>
+ <tbody md-body>
+ <tr ng-show="vm.alarms.length" md-row md-select="alarm"
+ md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
+ ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
+ <td md-cell ng-repeat="key in vm.alarmSource.dataKeys"
+ ng-style="vm.cellStyle(alarm, key)"
+ ng-bind-html="vm.cellContent(alarm, key)">
+ </td>
+ <td md-cell class="tb-action-cell">
+ <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
+ ng-click="vm.openAlarmDetails($event, alarm)">
+ <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'alarm.details' | translate }}
+ </md-tooltip>
+ </md-button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <md-divider></md-divider>
+ <span ng-show="!vm.alarms.length"
+ layout-align="center center"
+ class="no-data-found" translate>alarm.no-alarms-prompt</span>
+ </md-table-container>
+ <md-table-pagination md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
+ md-page="vm.query.page" md-total="{{vm.alarmsCount}}"
+ md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd">
+ </md-table-pagination>
+ </div>
+ <span ng-show="!vm.showData"
+ layout-align="center center"
+ style="text-transform: uppercase; display: flex;"
+ class="tb-absolute-fill" translate>alarm.no-data</span>
+</div>
diff --git a/ui/src/app/widget/select-widget-type.tpl.html b/ui/src/app/widget/select-widget-type.tpl.html
index e035273..b7ebcb1 100644
--- a/ui/src/app/widget/select-widget-type.tpl.html
+++ b/ui/src/app/widget/select-widget-type.tpl.html
@@ -54,6 +54,13 @@
<span translate>{{vm.types.widgetType.rpc.name}}</span>
</md-button>
<md-button class="tb-card-button md-raised md-primary" layout="column"
+ ng-click="vm.typeSelected(vm.types.widgetType.alarm.value)">
+ <md-icon class="material-icons tb-md-96"
+ aria-label="{{ vm.types.widgetType.alarm.name | translate }}">error
+ </md-icon>
+ <span translate>{{vm.types.widgetType.alarm.name}}</span>
+ </md-button>
+ <md-button class="tb-card-button md-raised md-primary" layout="column"
ng-click="vm.typeSelected(vm.types.widgetType.static.value)">
<md-icon class="material-icons tb-md-96"
aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download
diff --git a/ui/src/app/widget/widget-editor.controller.js b/ui/src/app/widget/widget-editor.controller.js
index 24f8fc7..7eb6b98 100644
--- a/ui/src/app/widget/widget-editor.controller.js
+++ b/ui/src/app/widget/widget-editor.controller.js
@@ -329,10 +329,14 @@ export default function WidgetEditorController(widgetService, userService, types
$scope.$watch('vm.widget.type', function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) {
var config = angular.fromJson(vm.widget.defaultConfig);
- if (vm.widget.type !== types.widgetType.rpc.value) {
+ if (vm.widget.type !== types.widgetType.rpc.value
+ && vm.widget.type !== types.widgetType.alarm.value) {
if (config.targetDeviceAliases) {
delete config.targetDeviceAliases;
}
+ if (config.alarmSource) {
+ delete config.alarmSource;
+ }
if (!config.datasources) {
config.datasources = [];
}
@@ -346,22 +350,38 @@ export default function WidgetEditorController(widgetService, userService, types
for (var i = 0; i < config.datasources.length; i++) {
var datasource = config.datasources[i];
datasource.type = vm.widget.type;
- if (vm.widget.type !== types.widgetType.timeseries.value && datasource.intervalSec) {
- delete datasource.intervalSec;
- } else if (vm.widget.type === types.widgetType.timeseries.value && !datasource.intervalSec) {
- datasource.intervalSec = 60;
- }
}
- } else {
+ } else if (vm.widget.type == types.widgetType.rpc.value) {
if (config.datasources) {
delete config.datasources;
}
+ if (config.alarmSource) {
+ delete config.alarmSource;
+ }
if (config.timewindow) {
delete config.timewindow;
}
if (!config.targetDeviceAliases) {
config.targetDeviceAliases = [];
}
+ } else { // alarm
+ if (config.datasources) {
+ delete config.datasources;
+ }
+ if (config.targetDeviceAliases) {
+ delete config.targetDeviceAliases;
+ }
+ if (!config.alarmSource) {
+ config.alarmSource = {};
+ config.alarmSource.type = vm.widget.type
+ }
+ if (!config.timewindow) {
+ config.timewindow = {
+ realtime: {
+ timewindowMs: 24 * 60 * 60 * 1000
+ }
+ };
+ }
}
vm.widget.defaultConfig = angular.toJson(config);
}