thingsboard-memoizeit

Merge pull request #168 from thingsboard/feature/TB-63 TB-63:

6/14/2017 7:44:01 AM

Changes

Details

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];
+        }
+    }
 }
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);
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++) {
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;
 
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",
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:&apos;...&apos;}}" }'>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:&apos;...&apos;}}" }'>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);
 
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;
     }
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);
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;
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 = [];
     }
 
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) {
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",
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;
+            }
+          }
+        }
+      }
+    }
+  }
+}
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>&nbsp;</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>&nbsp</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);
             }