thingsboard-memoizeit

Changes

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index 19c75e7..baee889 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -143,4 +143,30 @@ public class AlarmController extends BaseController {
         }
     }
 
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
+    @ResponseBody
+    public AlarmSeverity getHighestAlarmSeverity(
+            @PathVariable("entityType") String strEntityType,
+            @PathVariable("entityId") String strEntityId,
+            @RequestParam(required = false) String searchStatus,
+            @RequestParam(required = false) String status
+    ) throws ThingsboardException {
+        checkParameter("EntityId", strEntityId);
+        checkParameter("EntityType", strEntityType);
+        EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
+        AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus);
+        AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
+        if (alarmSearchStatus != null && alarmStatus != null) {
+            throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
+                    "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+        }
+        checkEntityId(entityId);
+        try {
+            return alarmService.findHighestAlarmSeverity(entityId, alarmSearchStatus, alarmStatus);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index 3556d51..63fba03 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
@@ -16,10 +16,8 @@
 package org.thingsboard.server.dao.alarm;
 
 import com.google.common.util.concurrent.ListenableFuture;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmId;
-import org.thingsboard.server.common.data.alarm.AlarmInfo;
-import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.alarm.*;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.page.TimePageData;
 
 /**
@@ -39,4 +37,7 @@ public interface AlarmService {
 
     ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query);
 
+    AlarmSeverity findHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus alarmSearchStatus,
+                                           AlarmStatus alarmStatus);
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index 94f8fdb..c3c1886 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.Tenant;
 import org.thingsboard.server.common.data.alarm.*;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.common.data.relation.EntityRelation;
 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 import org.thingsboard.server.dao.entity.AbstractEntityService;
@@ -45,6 +46,7 @@ import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -240,6 +242,46 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
         });
     }
 
+    @Override
+    public AlarmSeverity findHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus alarmSearchStatus,
+                                                                    AlarmStatus alarmStatus) {
+        TimePageLink nextPageLink = new TimePageLink(100);
+        boolean hasNext = true;
+        AlarmSeverity highestSeverity = null;
+        AlarmQuery query;
+        while (hasNext) {
+            query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
+            List<AlarmInfo> alarms;
+            try {
+                alarms = alarmDao.findAlarms(query).get();
+            } catch (ExecutionException | InterruptedException e) {
+                log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]",
+                        entityId, alarmSearchStatus, alarmStatus);
+                throw new RuntimeException(e);
+            }
+            hasNext = alarms.size() == nextPageLink.getLimit();
+            if (hasNext) {
+                nextPageLink = new TimePageData<>(alarms, nextPageLink).getNextPageLink();
+            }
+            if (alarms.isEmpty()) {
+                continue;
+            } else {
+                List<AlarmInfo> sorted = new ArrayList(alarms);
+                sorted.sort((p1, p2) -> p1.getSeverity().compareTo(p2.getSeverity()));
+                AlarmSeverity severity = sorted.get(0).getSeverity();
+                if (severity == AlarmSeverity.CRITICAL) {
+                    highestSeverity = severity;
+                    break;
+                } else if (highestSeverity == null) {
+                    highestSeverity = severity;
+                } else {
+                    highestSeverity = highestSeverity.compareTo(severity) < 0 ? highestSeverity : severity;
+                }
+            }
+        }
+        return highestSeverity;
+    }
+
     private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException {
         log.debug("Deleting Alarm relation: {}", alarmRelation);
         relationService.deleteRelation(alarmRelation).get();
diff --git a/ui/src/app/alarm/alarm-details-dialog.controller.js b/ui/src/app/alarm/alarm-details-dialog.controller.js
index 0cc05ef..6c82e20 100644
--- a/ui/src/app/alarm/alarm-details-dialog.controller.js
+++ b/ui/src/app/alarm/alarm-details-dialog.controller.js
@@ -23,11 +23,14 @@ import './alarm-details-dialog.scss';
 const js_beautify = beautify.js;
 
 /*@ngInject*/
-export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) {
+export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types,
+                                                     alarmService, alarmId, allowAcknowledgment, allowClear, showingCallback) {
 
     var vm = this;
 
     vm.alarmId = alarmId;
+    vm.allowAcknowledgment = allowAcknowledgment;
+    vm.allowClear = allowClear;
     vm.types = types;
     vm.alarm = null;
 
diff --git a/ui/src/app/alarm/alarm-details-dialog.tpl.html b/ui/src/app/alarm/alarm-details-dialog.tpl.html
index c958201..48f991c 100644
--- a/ui/src/app/alarm/alarm-details-dialog.tpl.html
+++ b/ui/src/app/alarm/alarm-details-dialog.tpl.html
@@ -84,16 +84,16 @@
         </div>
     </md-dialog-content>
     <md-dialog-actions layout="row">
-        <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||
-                          vm.alarm.status==vm.types.alarmStatus.clearedUnack"
+        <md-button ng-if="vm.allowAcknowledgment && (vm.alarm.status==vm.types.alarmStatus.activeUnack ||
+                          vm.alarm.status==vm.types.alarmStatus.clearedUnack)"
                    class="md-raised md-primary"
                    ng-disabled="loading"
                    ng-click="vm.acknowledge()"
                    style="margin-right:20px;">{{ 'alarm.acknowledge' |
             translate }}
         </md-button>
-        <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||
-                          vm.alarm.status==vm.types.alarmStatus.activeUnack"
+        <md-button ng-if="vm.allowClear && (vm.alarm.status==vm.types.alarmStatus.activeAck ||
+                          vm.alarm.status==vm.types.alarmStatus.activeUnack)"
                    class="md-raised md-primary"
                    ng-disabled="loading"
                    ng-click="vm.clear()">{{ 'alarm.clear' |
diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js
index 9cb9bed..09c04f9 100644
--- a/ui/src/app/alarm/alarm-row.directive.js
+++ b/ui/src/app/alarm/alarm-row.directive.js
@@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi
                 controller: 'AlarmDetailsDialogController',
                 controllerAs: 'vm',
                 templateUrl: alarmDetailsDialogTemplate,
-                locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback},
+                locals: {
+                    alarmId: scope.alarm.id.id,
+                    allowAcknowledgment: true,
+                    allowClear: true,
+                    showingCallback: onShowingCallback
+                },
                 parent: angular.element($document[0].body),
                 targetEvent: $event,
                 fullscreen: true,
diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js
index 34e6b59..3b7a735 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,
@@ -26,8 +48,11 @@ function AlarmService($http, $q, $interval, $filter) {
         ackAlarm: ackAlarm,
         clearAlarm: clearAlarm,
         getAlarms: getAlarms,
+        getHighestAlarmSeverity: getHighestAlarmSeverity,
         pollAlarms: pollAlarms,
-        cancelPollAlarms: cancelPollAlarms
+        cancelPollAlarms: cancelPollAlarms,
+        subscribeForAlarms: subscribeForAlarms,
+        unsubscribeFromAlarms: unsubscribeFromAlarms
     }
 
     return service;
@@ -141,6 +166,23 @@ function AlarmService($http, $q, $interval, $filter) {
         return deferred.promise;
     }
 
+    function getHighestAlarmSeverity(entityType, entityId, alarmSearchStatus, alarmStatus, config) {
+        var deferred = $q.defer();
+        var url = '/api/alarm/highestSeverity/' + entityType + '/' + entityId;
+
+        if (alarmSearchStatus) {
+            url += '?searchStatus=' + alarmSearchStatus;
+        } else if (alarmStatus) {
+            url += '?status=' + alarmStatus;
+        }
+        $http.get(url, config).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
     function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) {
         getAlarms(alarmsQuery.entityType, alarmsQuery.entityId,
             pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus,
@@ -171,12 +213,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 +262,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 if (alarmSource.entityType && alarmSource.entityId) {
+            var pollingInterval = alarmSourceListener.alarmsPollingInterval;
+            alarmSourceListener.alarmsQuery = {
+                entityType: alarmSource.entityType,
+                entityId: alarmSource.entityId,
+                alarmSearchStatus: alarmSourceListener.alarmSearchStatus,
+                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..030b852 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -64,6 +64,45 @@ 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.alarmSearchStatus = angular.isDefined(options.alarmSearchStatus) ?
+                options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any;
+            this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ?
+                options.alarmsPollingInterval : 5000;
+
+            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 +171,43 @@ 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.update();
+                }
+            });
+            this.registrations.push(registration);
+        } else {
+            this.startWatchingTimewindow();
+        }
+    }
+
     initDataSubscription() {
         var deferred = this.ctx.$q.defer();
         if (!this.ctx.aliasController) {
@@ -210,8 +286,7 @@ export default class Subscription {
                 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();
+                        subscription.update();
                     }
                 });
                 this.registrations.push(registration);
@@ -227,8 +302,7 @@ export default class Subscription {
             return subscription.timeWindowConfig;
         }, function (newTimewindow, prevTimewindow) {
             if (!angular.equals(newTimewindow, prevTimewindow)) {
-                subscription.unsubscribe();
-                subscription.subscribe();
+                subscription.update();
             }
         }, true);
         this.registrations.push(this.timeWindowWatchRegistration);
@@ -393,6 +467,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);
         }
@@ -429,8 +505,7 @@ export default class Subscription {
                 this.timeWindowConfig = angular.copy(this.originalTimewindow);
                 this.originalTimewindow = null;
                 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
-                this.unsubscribe();
-                this.subscribe();
+                this.update();
                 this.startWatchingTimewindow();
             }
         }
@@ -446,8 +521,7 @@ export default class Subscription {
             }
             this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs);
             this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
-            this.unsubscribe();
-            this.subscribe();
+            this.update();
             this.startWatchingTimewindow();
         }
     }
@@ -516,6 +590,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;
@@ -536,66 +619,115 @@ export default class Subscription {
         this.callbacks.legendDataUpdated(this, apply !== false);
     }
 
+    update() {
+        this.unsubscribe();
+        this.subscribe();
+    }
+
     subscribe() {
         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,
+            alarmSearchStatus: this.alarmSearchStatus,
+            alarmsPollingInterval: this.alarmsPollingInterval,
+            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 +739,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/asset/assets.tpl.html b/ui/src/app/asset/assets.tpl.html
index 8370d3d..c4c0267 100644
--- a/ui/src/app/asset/assets.tpl.html
+++ b/ui/src/app/asset/assets.tpl.html
@@ -31,7 +31,7 @@
                        on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
                        on-delete-asset="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-asset>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.asset}}"
@@ -39,7 +39,7 @@
                                 default-attribute-scope="{{vm.types.attributesScope.server.value}}">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.asset}}"
@@ -48,19 +48,19 @@
                                 disable-attribute-scope-selection="true">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
             <tb-alarm-table flex entity-type="vm.types.entityType.asset"
                             entity-id="vm.grid.operatingItem().id.id">
             </tb-alarm-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'asset.events' | translate }}">
             <tb-event-table flex entity-type="vm.types.entityType.asset"
                             entity-id="vm.grid.operatingItem().id.id"
                             tenant-id="vm.grid.operatingItem().tenantId.id"
                             default-event-type="{{vm.types.eventType.error.value}}">
             </tb-event-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
             <tb-relation-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.asset}}">
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index b281ad1..81ce0fa 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",
@@ -333,7 +394,7 @@ export default angular.module('thingsboard.types', [])
                 cards: "cards"
             },
             translate: {
-                dashboardStatePrefix: "dashboardState.state."
+                customTranslationsPrefix: "custom."
             }
         }
     ).name;
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index 8d2e565..a3433c2 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,9 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
         cleanCopy: cleanCopy,
         isLocalUrl: isLocalUrl,
         validateDatasources: validateDatasources,
-        createKey: createKey
+        createKey: createKey,
+        createLabelFromDatasource: createLabelFromDatasource,
+        insertVariable: insertVariable
     }
 
     return service;
@@ -212,6 +239,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 +388,38 @@ 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;
+    }
+
+    function insertVariable(pattern, name, value) {
+        var result = angular.copy(pattern);
+        var match = varsRegex.exec(pattern);
+        while (match !== null) {
+            var variable = match[0];
+            var variableName = match[1];
+            if (variableName === name) {
+                result = result.split(variable).join(value);
+            }
+            match = varsRegex.exec(pattern);
+        }
+        return result;
+    }
+
 }
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index a88e656..1c2c654 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -58,6 +58,7 @@ function Dashboard() {
             columns: '=',
             margins: '=',
             isEdit: '=',
+            autofillHeight: '=',
             isMobile: '=',
             isMobileDisabled: '=?',
             isEditActionEnabled: '=',
@@ -102,6 +103,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
 
     vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
 
+    vm.isMobileSize = false;
+
     if (!('dashboardTimewindow' in vm)) {
         vm.dashboardTimewindow = timeService.defaultTimewindow();
     }
@@ -177,10 +180,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     vm.widgetBackgroundColor = widgetBackgroundColor;
     vm.widgetPadding = widgetPadding;
     vm.showWidgetTitle = showWidgetTitle;
+    vm.showWidgetTitlePanel = showWidgetTitlePanel;
+    vm.showWidgetActions = showWidgetActions;
     vm.widgetTitleStyle = widgetTitleStyle;
+    vm.widgetTitle = widgetTitle;
+    vm.widgetActions = widgetActions;
     vm.dropWidgetShadow = dropWidgetShadow;
     vm.enableWidgetFullscreen = enableWidgetFullscreen;
     vm.hasTimewindow = hasTimewindow;
+    vm.hasAggregation = hasAggregation;
     vm.editWidget = editWidget;
     vm.exportWidget = exportWidget;
     vm.removeWidget = removeWidget;
@@ -267,12 +275,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
 
     function updateMobileOpts() {
         var isMobileDisabled = vm.isMobileDisabled === true;
-        var isMobile = vm.isMobile === true && !isMobileDisabled;
+        var isMobile = vm.isMobile === true && !isMobileDisabled || vm.autofillHeight;
         var mobileBreakPoint = isMobileDisabled ? 0 : (isMobile ? 20000 : 960);
+
         if (!isMobile && !isMobileDisabled) {
             isMobile = !$mdMedia('gt-sm');
         }
-        var rowHeight = isMobile ? 70 : 'match';
+
+        var rowHeight = detectRowSize(isMobile);
+
         if (vm.gridsterOpts.isMobile != isMobile) {
             vm.gridsterOpts.isMobile = isMobile;
             vm.gridsterOpts.mobileModeEnabled = isMobile;
@@ -283,6 +294,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         if (vm.gridsterOpts.rowHeight != rowHeight) {
             vm.gridsterOpts.rowHeight = rowHeight;
         }
+
+        vm.isMobileSize = checkIsMobileSize();
+    }
+
+    function checkIsMobileSize() {
+        var isMobileDisabled = vm.isMobileDisabled === true;
+        var isMobileSize = vm.isMobile === true && !isMobileDisabled;
+        if (!isMobileSize && !isMobileDisabled) {
+            isMobileSize = !$mdMedia('gt-sm');
+        }
+        return isMobileSize;
     }
 
     $scope.$watch(function() { return $mdMedia('gt-sm'); }, function() {
@@ -293,6 +315,34 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         updateMobileOpts();
     });
 
+    $scope.$watch('vm.autofillHeight', function () {
+        if (vm.autofillHeight) {
+            //if (gridsterParent.height()) {
+            //    updateMobileOpts();
+            //} else {
+            if ($scope.parentHeighWatcher) {
+                $scope.parentHeighWatcher();
+            }
+            if (gridsterParent.height()) {
+                updateMobileOpts();
+            }
+            $scope.parentHeighWatcher = $scope.$watch(function() { return gridsterParent.height(); },
+                function(newHeight) {
+                    if (newHeight) {
+                        updateMobileOpts();
+                    }
+               }
+            );
+        } else {
+            if ($scope.parentHeighWatcher) {
+                $scope.parentHeighWatcher();
+                $scope.parentHeighWatcher = null;
+            }
+
+            updateMobileOpts();
+        }
+    });
+
     $scope.$watch('vm.isMobileDisabled', function () {
         updateMobileOpts();
     });
@@ -329,6 +379,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         $scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
     });
 
+    $scope.$watch('vm.isMobileSize', function (newVal, prevVal) {
+        if (!angular.equals(newVal, prevVal)) {
+            $scope.$broadcast('mobileModeChanged', vm.isMobileSize);
+        }
+    });
+
     $scope.$on('gridster-resized', function (event, sizes, theGridster) {
         if (checkIsLocalGridsterElement(theGridster)) {
             vm.gridster = theGridster;
@@ -341,13 +397,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     $scope.$on('gridster-mobile-changed', function (event, theGridster) {
         if (checkIsLocalGridsterElement(theGridster)) {
             vm.gridster = theGridster;
-            var rowHeight = vm.gridster.isMobile ? 70 : 'match';
+            var rowHeight = detectRowSize(vm.gridster.isMobile);
             if (vm.gridsterOpts.rowHeight != rowHeight) {
                 vm.gridsterOpts.rowHeight = rowHeight;
                 updateGridsterParams();
             }
-
-            $scope.$broadcast('mobileModeChanged', vm.gridster.isMobile);
+            vm.isMobileSize = checkIsMobileSize();
 
             //TODO: widgets visibility
             /*$timeout(function () {
@@ -356,6 +411,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         }
     });
 
+    function detectRowSize(isMobile) {
+        var rowHeight = isMobile ? 70 : 'match';
+        if (vm.autofillHeight) {
+            var viewportHeight = gridsterParent.height();
+            var totalRows = 0;
+            for (var i = 0; i < vm.widgets.length; i++) {
+                var w = vm.widgets[i];
+                var sizeY = widgetSizeY(w);
+                totalRows += sizeY;
+            }
+            rowHeight = (viewportHeight - (vm.gridsterOpts.margins[1])) / totalRows;
+        }
+        return rowHeight;
+    }
+
     function widgetOrder(widget) {
         var order;
         if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
@@ -646,7 +716,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     }
 
     function widgetSizeY(widget) {
-        if (vm.gridsterOpts.isMobile) {
+        if (vm.gridsterOpts.isMobile && !vm.autofillHeight) {
             var mobileHeight;
             if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
                 mobileHeight = vm.widgetLayouts[widget.id].mobileHeight;
@@ -669,7 +739,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     }
 
     function setWidgetSizeY(widget, sizeY) {
-        if (!vm.gridsterOpts.isMobile) {
+        if (!vm.gridsterOpts.isMobile && !vm.autofillHeight) {
             if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
                 vm.widgetLayouts[widget.id].sizeY = sizeY;
             } else {
@@ -746,6 +816,24 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         }
     }
 
+    function showWidgetTitlePanel(widget) {
+        var ctx = widgetContext(widget);
+        if (ctx && ctx.hideTitlePanel) {
+            return false;
+        } else {
+            return showWidgetTitle(widget) || hasTimewindow(widget);
+        }
+    }
+
+    function showWidgetActions(widget) {
+        var ctx = widgetContext(widget);
+        if (ctx && ctx.hideTitlePanel) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     function widgetTitleStyle(widget) {
         if (angular.isDefined(widget.config.titleStyle)) {
             return widget.config.titleStyle;
@@ -754,6 +842,33 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         }
     }
 
+    function widgetTitle(widget) {
+        var ctx = widgetContext(widget);
+        if (ctx && ctx.widgetTitle
+            && ctx.widgetTitle.length) {
+            return ctx.widgetTitle;
+        } else {
+            return widget.config.title;
+        }
+    }
+
+    function widgetActions(widget) {
+        var ctx = widgetContext(widget);
+        if (ctx && ctx.widgetActions && ctx.widgetActions.length) {
+            return ctx.widgetActions;
+        } else {
+            return [];
+        }
+    }
+
+    function widgetContext(widget) {
+        var context;
+        if (widget.$ctx) {
+            context = widget.$ctx();
+        }
+        return context;
+    }
+
     function dropWidgetShadow(widget) {
         if (angular.isDefined(widget.config.dropShadow)) {
             return widget.config.dropShadow;
@@ -771,7 +886,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 +894,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..478e2e5 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -23,7 +23,7 @@
 </md-content>
 <md-menu md-position-mode="target target" tb-mousepoint-menu>
 	<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)">
-		<div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%;">
+		<div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%; display: inline;">
 			<div id="gridster-child" gridster="vm.gridsterOpts">
 				<ul>
 					<li gridster-item="vm.widgetItemMap" class="tb-noselect" ng-repeat="widget in vm.widgets">
@@ -37,7 +37,8 @@
 								 		class="tb-widget"
 										ng-class="{'tb-highlighted': vm.isHighlighted(widget),
 										           'tb-not-highlighted': vm.isNotHighlighted(widget),
-										           'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}"
+										           'md-whiteframe-4dp': vm.dropWidgetShadow(widget),
+										           'tb-has-timewindow': vm.hasTimewindow(widget)}"
 										tb-mousedown="vm.widgetMouseDown($event, widget)"
 										ng-click="vm.widgetClicked($event, widget)"
 										tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)"
@@ -45,11 +46,21 @@
             									   color: vm.widgetColor(widget),
             									   backgroundColor: vm.widgetBackgroundColor(widget),
             									   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>
+								<div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitlePanel(widget)">
+									<span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{vm.widgetTitle(widget)}}</span>
+									<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()">
+								<div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()">
+									<md-button ng-repeat="action in vm.widgetActions(widget)"
+											   aria-label="{{ action.name | translate }}"
+											   ng-show="!vm.isEdit && action.show"
+											   ng-click="action.onAction($event)"
+											   class="md-icon-button">
+											   <md-tooltip md-direction="top">
+												   {{ action.name | translate }}
+											   </md-tooltip>
+											   <ng-md-icon size="20" icon="{{action.icon}}"></ng-md-icon>
+									</md-button>
 									<md-button id="expand-button"
 											   ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)"
 											   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
@@ -92,6 +103,7 @@
 										 aliasController: vm.aliasController,
 										 stateController: vm.stateController,
 										 isEdit: vm.isEdit,
+										 isMobile: vm.isMobileSize,
 										 stDiff: vm.stDiff,
 										 dashboardTimewindow: vm.dashboardTimewindow,
 										 dashboardTimewindowApi: vm.dashboardTimewindowApi }">
diff --git a/ui/src/app/components/datakey-config.directive.js b/ui/src/app/components/datakey-config.directive.js
index 90810af..d19617f 100644
--- a/ui/src/app/components/datakey-config.directive.js
+++ b/ui/src/app/components/datakey-config.directive.js
@@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
         element.html(template);
 
         scope.types = types;
+
+        scope.alarmFields = [];
+        for (var alarmField in types.alarmFields) {
+            scope.alarmFields.push(alarmField);
+        }
+
         scope.selectedKey = null;
         scope.keySearchText = null;
         scope.usePostProcessing = false;
@@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
         }, true);
 
         scope.keysSearch = function (searchText) {
-            if (scope.entityAlias) {
-                var deferred = $q.defer();
-                scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: scope.model.type})
-                    .then(function (keys) {
-                        keys.push(searchText);
-                        deferred.resolve(keys);
-                    }, function (e) {
-                        deferred.reject(e);
-                    });
-                return deferred.promise;
+            if (scope.model.type === types.dataKeyType.alarm) {
+                var dataKeys = searchText ? scope.alarmFields.filter(
+                    scope.createFilterForDataKey(searchText)) : scope.alarmFields;
+                dataKeys.push(searchText);
+                return dataKeys;
             } else {
-                return $q.when([]);
+                if (scope.entityAlias) {
+                    var deferred = $q.defer();
+                    scope.fetchEntityKeys({
+                        entityAliasId: scope.entityAlias.id,
+                        query: searchText,
+                        type: scope.model.type
+                    })
+                        .then(function (keys) {
+                            keys.push(searchText);
+                            deferred.resolve(keys);
+                        }, 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);
+            };
+        };
+
         $compile(element.contents())(scope);
     }
 
diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html
index 755c4f4..bb7a4b9 100644
--- a/ui/src/app/components/datakey-config.tpl.html
+++ b/ui/src/app/components/datakey-config.tpl.html
@@ -16,7 +16,9 @@
 
 -->
 <md-content class="md-padding" layout="column">
-    <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute"
+    <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries ||
+    						model.type === types.dataKeyType.attribute ||
+    						model.type === types.dataKeyType.alarm"
     	   style="padding-bottom: 8px;"
            ng-required="true"
    		   md-no-cache="true"
@@ -27,8 +29,8 @@
 		   md-items="item in keysSearch(keySearchText)"
 	 	   md-item-text="item"
 		   md-min-length="0"
-           placeholder="Key name"
-           md-floating-label="Key">
+           placeholder="{{ 'entity.key-name' | translate }}"
+           md-floating-label="{{ 'entity.key' | translate }}">
            <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span>
     </md-autocomplete>
 	<div layout="row" layout-align="start center">     
@@ -48,7 +50,7 @@
 		    md-color-history="false">
 		</div>    
 	</div>
-	<div layout="row" layout-align="start center">
+	<div layout="row" layout-align="start center" ng-if="model.type !== types.dataKeyType.alarm">
 		<md-input-container flex>
 			<label translate>datakey.units</label>
 			<input name="units" ng-model="model.units">
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.scss b/ui/src/app/components/datasource-entity.scss
index 7a87fc7..b9e892b 100644
--- a/ui/src/app/components/datasource-entity.scss
+++ b/ui/src/app/components/datasource-entity.scss
@@ -15,7 +15,7 @@
  */
 @import '../../scss/constants';
 
-.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete {
+.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
   .tb-not-found {
     display: block;
     line-height: 1.5;
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index 2fb4608..9876491 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,63 @@
 						  </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="false">
+				   <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>
+								   <span>
+										<a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
+								   </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.scss b/ui/src/app/components/datasource-func.scss
index 8739ed8..fdda0e4 100644
--- a/ui/src/app/components/datasource-func.scss
+++ b/ui/src/app/components/datasource-func.scss
@@ -26,7 +26,7 @@
     }
   }
 
-  .tb-func-datakey-autocomplete {
+  .tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
     .tb-not-found {
       display: block;
       line-height: 1.5;
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
index e8e08f7..7fa4aac 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"
@@ -47,9 +48,9 @@
 									<span translate>device.no-keys-found</span>
 								</div>
 								<div ng-if="textIsNotEmpty(dataKeySearchText)">
-									<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+									<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
 									<span>
-										<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
+										<a translate ng-click="createKey($event, '#function_datakey_chips')">entity.create-new-key</a>
 									</span>
 								</div>
 							</div>
@@ -75,8 +76,61 @@
 					  </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="false">
+			<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>
+							<span>
+								<a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
+							</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/grid.directive.js b/ui/src/app/components/grid.directive.js
index 296456a..422501b 100644
--- a/ui/src/app/components/grid.directive.js
+++ b/ui/src/app/components/grid.directive.js
@@ -124,7 +124,7 @@ function Grid() {
 }
 
 /*@ngInject*/
-function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $translate, $mdMedia, $templateCache) {
+function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $translate, $mdMedia, $templateCache, $window) {
 
     var vm = this;
 
@@ -155,6 +155,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
     vm.refreshList = refreshList;
     vm.saveItem = saveItem;
     vm.toggleItemSelection = toggleItemSelection;
+    vm.triggerResize = triggerResize;
 
     $scope.$watch(function () {
         return $mdMedia('xs') || $mdMedia('sm');
@@ -600,6 +601,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
         }
     }
 
+    function triggerResize() {
+        var w = angular.element($window);
+        w.triggerHandler('resize');
+    }
+
     function moveToTop() {
         moveToIndex(0, true);
     }
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..8d91b2d 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, isMobile, stDiff, dashboardTimewindow,
                                          dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
 
     var vm = this;
@@ -44,13 +44,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
 
     var widgetContext = {
         inited: false,
-        $scope: $scope,
         $container: null,
         $containerParent: null,
         width: 0,
         height: 0,
+        hideTitlePanel: false,
         isEdit: isEdit,
-        isMobile: false,
+        isMobile: isMobile,
         widgetConfig: widget.config,
         settings: widget.config.settings,
         units: widget.config.units || '',
@@ -113,6 +113,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,
@@ -121,6 +122,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         aliasController: aliasController
     };
 
+    widget.$ctx = function() {
+        return widgetContext;
+    }
+
     var widgetTypeInstance;
 
     vm.useCustomDatasources = false;
@@ -285,9 +290,18 @@ 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);
+                options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ?
+                    widget.config.alarmSearchStatus : types.alarmSearchStatus.any;
+                options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ?
+                    widget.config.alarmsPollingInterval * 1000 : 5000;
+            } else {
+                options.datasources = angular.copy(widget.config.datasources)
+            }
+
             defaultComponentsOptions(options);
 
             createSubscription(options).then(
@@ -320,7 +334,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 +450,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 +465,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;
     }
@@ -594,7 +618,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
 
     function gridsterItemInitialized(item) {
         if (item && item.gridster) {
-            widgetContext.isMobile = item.gridster.isMobile;
             gridsterItemInited = true;
             onInit();
             // gridsterItemElement = $(item.$element);
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index d5b4239..3849cd3 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,16 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
                         } else {
                             scope.targetDeviceAlias.value = null;
                         }
+                    } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) {
+                        scope.alarmSearchStatus = angular.isDefined(config.alarmSearchStatus) ?
+                            config.alarmSearchStatus : types.alarmSearchStatus.any;
+                        scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ?
+                            config.alarmsPollingInterval : 5;
+                        if (config.alarmSource) {
+                            scope.alarmSource.value = config.alarmSource;
+                        } else {
+                            scope.alarmSource.value = null;
+                        }
                     }
 
                     scope.settings = config.settings;
@@ -175,6 +191,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);
@@ -190,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
         };
 
         scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' +
-            'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () {
+            'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' +
+            'alarmSearchStatus + alarmsPollingInterval + showLegend', function () {
             if (ngModelCtrl.$viewValue) {
                 var value = ngModelCtrl.$viewValue;
                 if (value.config) {
@@ -210,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
                     config.units = scope.units;
                     config.decimals = scope.decimals;
                     config.useDashboardTimewindow = scope.useDashboardTimewindow;
+                    config.alarmSearchStatus = scope.alarmSearchStatus;
+                    config.alarmsPollingInterval = scope.alarmsPollingInterval;
                     config.showLegend = scope.showLegend;
                 }
                 if (value.layout) {
@@ -253,7 +275,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 +310,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 +358,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 +398,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 +421,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..4bfe55c 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -20,18 +20,46 @@
     <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>
+            <div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center"
+                 layout-gt-sm='row' layout-align-gt-sm="start center">
+                <md-input-container class="md-block" flex>
+                    <label translate>alarm.alarm-status</label>
+                    <md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;">
+                        <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
+                            {{ ('alarm.search-status.' + searchStatus) | translate }}
+                        </md-option>
+                    </md-select>
+                </md-input-container>
+                <md-input-container flex class="md-block">
+                    <label translate>alarm.polling-interval</label>
+                    <input ng-required="widgetType === types.widgetType.alarm.value"
+                           type="number"
+                           step="1"
+                           min="1"
+                           name="alarmsPollingInterval"
+                           ng-model="alarmsPollingInterval"/>
+                    <div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false">
+                        <div ng-message="required" translate>alarm.polling-interval-required</div>
+                        <div ng-message="min" translate>alarm.min-polling-interval-message</div>
+                    </div>
+                </md-input-container>
+            </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 +140,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/customer/customers.tpl.html b/ui/src/app/customer/customers.tpl.html
index a6521f0..da0a2e9 100644
--- a/ui/src/app/customer/customers.tpl.html
+++ b/ui/src/app/customer/customers.tpl.html
@@ -31,7 +31,7 @@
 				on-manage-dashboards="vm.openCustomerDashboards(event, vm.grid.detailsConfig.currentItem)"
 				on-delete-customer="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-customer>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
 			<tb-attribute-table flex
 								entity-id="vm.grid.operatingItem().id.id"
 								entity-type="{{vm.types.entityType.customer}}"
@@ -39,7 +39,7 @@
 								default-attribute-scope="{{vm.types.attributesScope.server.value}}">
 			</tb-attribute-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
 			<tb-attribute-table flex
 								entity-id="vm.grid.operatingItem().id.id"
 								entity-type="{{vm.types.entityType.customer}}"
@@ -48,19 +48,19 @@
 								disable-attribute-scope-selection="true">
 			</tb-attribute-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
 			<tb-alarm-table flex entity-type="vm.types.entityType.customer"
 							entity-id="vm.grid.operatingItem().id.id">
 			</tb-alarm-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'customer.events' | translate }}">
 			<tb-event-table flex entity-type="vm.types.entityType.customer"
 							entity-id="vm.grid.operatingItem().id.id"
 							tenant-id="vm.grid.operatingItem().tenantId.id"
 							default-event-type="{{vm.types.eventType.error.value}}">
 			</tb-event-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
 			<tb-relation-table flex
 							   entity-id="vm.grid.operatingItem().id.id"
 							   entity-type="{{vm.types.entityType.customer}}">
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/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index ac98709..c5743c9 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -68,6 +68,7 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
         vm.gridSettings.color = vm.gridSettings.color || 'rgba(0,0,0,0.870588)';
         vm.gridSettings.columns = vm.gridSettings.columns || 24;
         vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
+        vm.gridSettings.autoFillHeight = angular.isDefined(vm.gridSettings.autoFillHeight) ? vm.gridSettings.autoFillHeight : false;
         vm.hMargin = vm.gridSettings.margins[0];
         vm.vMargin = vm.gridSettings.margins[1];
         vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%';
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index f3ff704..427ca19 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -121,6 +121,9 @@
                                 </div>
                             </md-input-container>
                         </div>
+                        <md-checkbox flex aria-label="{{ 'dashboard.autofill-height' | translate }}"
+                                     ng-model="vm.gridSettings.autoFillHeight">{{ 'dashboard.autofill-height' | translate }}
+                        </md-checkbox>
                         <div flex
                              ng-required="false"
                              md-color-picker
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/dashboard/layouts/dashboard-layout.tpl.html b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
index 4a4b25e..3df1327 100644
--- a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
+++ b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
@@ -49,6 +49,7 @@
             state-controller="vm.dashboardCtx.stateController"
             dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
             is-edit="vm.isEdit"
+            autofill-height="vm.layoutCtx.gridSettings.autoFillHeight && !vm.isEdit"
             is-mobile="vm.isMobile"
             is-mobile-disabled="vm.widgetEditMode"
             is-edit-action-enabled="vm.isEdit"
diff --git a/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js b/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js
index 86eb9ec..8af5a77 100644
--- a/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js
+++ b/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js
@@ -53,12 +53,6 @@ export default function DashboardStateDialogController($scope, $mdDialog, $filte
         if (!vm.stateIdTouched && vm.isAdd) {
             vm.state.id = vm.state.name.toLowerCase().replace(/\W/g,"_");
         }
-        var result = $filter('filter')(vm.allStates, {name: vm.state.name}, true);
-        if (result && result.length && result[0].id !== vm.prevStateId) {
-            $scope.theForm.name.$setValidity('stateExists', false);
-        } else {
-            $scope.theForm.name.$setValidity('stateExists', true);
-        }
     }
 
     function checkStateId() {
diff --git a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html b/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
index fa45d7a..385de94 100644
--- a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
+++ b/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
@@ -37,18 +37,15 @@
                             <input name="name" required ng-model="vm.state.name">
                             <div ng-messages="theForm.name.$error">
                                 <div ng-message="required" translate>dashboard.state-name-required</div>
-                                <div ng-message="stateExists" translate>dashboard.state-name-exists</div>
                             </div>
                         </md-input-container>
                         <md-input-container class="md-block">
                             <label translate>dashboard.state-id</label>
-                            <input name="stateId" ng-model="vm.state.id"
-                                   ng-change="vm.stateIdTouched = true"
-                                   ng-pattern="/^[a-zA-Z0-9_]*$/">
+                            <input name="stateId" required ng-model="vm.state.id"
+                                   ng-change="vm.stateIdTouched = true">
                             <div ng-messages="theForm.stateId.$error">
                                 <div ng-message="required" translate>dashboard.state-id-required</div>
                                 <div ng-message="stateExists" translate>dashboard.state-id-exists</div>
-                                <div ng-message="pattern" translate>dashboard.invalid-state-id-format</div>
                             </div>
                         </md-input-container>
                         <md-checkbox flex aria-label="{{ 'dashboard.is-root-state' | translate }}"
diff --git a/ui/src/app/dashboard/states/default-state-controller.js b/ui/src/app/dashboard/states/default-state-controller.js
index 76ea9b8..4844b4c 100644
--- a/ui/src/app/dashboard/states/default-state-controller.js
+++ b/ui/src/app/dashboard/states/default-state-controller.js
@@ -97,10 +97,10 @@ export default function DefaultStateController($scope, $location, $state, $state
 
     function getStateName(id, state) {
         var result = '';
-        var translationId = types.translate.dashboardStatePrefix + id;
+        var translationId = types.translate.customTranslationsPrefix + state.name;
         var translation = $translate.instant(translationId);
         if (translation != translationId) {
-            result = translation;
+            result = translation + '';
         } else {
             result = state.name;
         }
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
index 51cf67d..6e7685e 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.js
+++ b/ui/src/app/dashboard/states/entity-state-controller.js
@@ -17,7 +17,7 @@
 import './entity-state-controller.scss';
 
 /*@ngInject*/
-export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, types, dashboardUtils, entityService) {
+export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, utils, types, dashboardUtils, entityService) {
 
     var vm = this;
 
@@ -106,18 +106,17 @@ export default function EntityStateController($scope, $location, $state, $stateP
     function getStateName(index) {
         var result = '';
         if (vm.stateObject[index]) {
+            var stateName = vm.states[vm.stateObject[index].id].name;
+            var translationId = types.translate.customTranslationsPrefix + stateName;
+            var translation = $translate.instant(translationId);
+            if (translation != translationId) {
+                stateName = translation + '';
+            }
             var params = vm.stateObject[index].params;
             if (params && params.entityName) {
-                result = params.entityName;
+                result = utils.insertVariable(stateName, 'entityName', params.entityName);
             } else {
-                var id = vm.stateObject[index].id;
-                var translationId = types.translate.dashboardStatePrefix + id;
-                var translation = $translate.instant(translationId);
-                if (translation != translationId) {
-                    result = translation;
-                } else {
-                    result = vm.states[vm.stateObject[index].id].name;
-                }
+                result = stateName;
             }
         }
         return result;
@@ -243,11 +242,9 @@ export default function EntityStateController($scope, $location, $state, $stateP
     }
 
     function gotoState(stateId, update, openRightLayout) {
-        if (vm.dashboardCtrl.dashboardCtx.state != stateId) {
-            vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);
-            if (update) {
-                updateLocation();
-            }
+        vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);
+        if (update) {
+            updateLocation();
         }
     }
 
diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html
index 1e467be..b8394c7 100644
--- a/ui/src/app/device/devices.tpl.html
+++ b/ui/src/app/device/devices.tpl.html
@@ -32,7 +32,7 @@
                        on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)"
                        on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.device}}"
@@ -40,7 +40,7 @@
                                 default-attribute-scope="{{vm.types.attributesScope.client.value}}">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.device}}"
@@ -49,19 +49,19 @@
                                 disable-attribute-scope-selection="true">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
             <tb-alarm-table flex entity-type="vm.types.entityType.device"
                             entity-id="vm.grid.operatingItem().id.id">
             </tb-alarm-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'device.events' | translate }}">
             <tb-event-table flex entity-type="vm.types.entityType.device"
                             entity-id="vm.grid.operatingItem().id.id"
                             tenant-id="vm.grid.operatingItem().tenantId.id"
                             default-event-type="{{vm.types.eventType.error.value}}">
             </tb-event-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
             <tb-relation-table flex
                                entity-id="vm.grid.operatingItem().id.id"
                                entity-type="{{vm.types.entityType.device}}">
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..b213746 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,17 @@ 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",
+                    "polling-interval": "Alarms polling interval (sec)",
+                    "polling-interval-required": "Alarms polling interval is required.",
+                    "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
+                    "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }",
+                    "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?",
+                    "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }",
+                    "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?"
                 },
                 "alias": {
                     "add": "Add alias",
@@ -420,6 +431,7 @@ export default angular.module('thingsboard.locale', [])
                     "vertical-margin-required": "Vertical margin value is required.",
                     "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
                     "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
+                    "autofill-height": "Auto fill layout height",
                     "display-title": "Display dashboard title",
                     "toolbar-always-open": "Keep toolbar opened",
                     "title-color": "Title color",
@@ -461,11 +473,9 @@ export default angular.module('thingsboard.locale', [])
                     "state": "Dashboard state",
                     "state-name": "Name",
                     "state-name-required": "Dashboard state name is required.",
-                    "state-name-exists": "Dashboard state with the same name is already exists.",
                     "state-id": "State Id",
                     "state-id-required": "Dashboard state id is required.",
                     "state-id-exists": "Dashboard state with the same id is already exists.",
-                    "invalid-state-id-format": "Only alphanumeric characters and underscore are allowed.",
                     "is-root-state": "Root state",
                     "delete-state-title": "Delete dashboard state",
                     "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",
@@ -486,8 +496,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."
                 },
@@ -637,6 +649,8 @@ export default angular.module('thingsboard.locale', [])
                     "no-aliases-found": "No aliases found.",
                     "no-alias-matching": "'{{alias}}' not found.",
                     "create-new-alias": "Create a new one!",
+                    "key": "Key",
+                    "key-name": "Key name",
                     "no-keys-found": "No keys found.",
                     "no-key-matching": "'{{key}}' not found.",
                     "create-new-key": "Create a new one!",
@@ -1046,6 +1060,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 +1148,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",
@@ -1150,6 +1166,8 @@ export default angular.module('thingsboard.locale', [])
                     "zh_CN": "Chinese",
                     "ru_RU": "Russian",
                     "es_ES": "Spanish"
+                },
+                "custom": {
                 }
             }
         }
diff --git a/ui/src/app/locale/translate-handler.js b/ui/src/app/locale/translate-handler.js
index d227041..7410062 100644
--- a/ui/src/app/locale/translate-handler.js
+++ b/ui/src/app/locale/translate-handler.js
@@ -18,7 +18,7 @@
 export default function ThingsboardMissingTranslateHandler($log, types) {
 
     return function (translationId) {
-        if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) {
+        if (translationId && !translationId.startsWith(types.translate.customTranslationsPrefix)) {
             $log.warn('Translation for ' + translationId + ' doesn\'t exist');
         }
     };
diff --git a/ui/src/app/plugin/plugins.tpl.html b/ui/src/app/plugin/plugins.tpl.html
index 73b0adc..b3563e9 100644
--- a/ui/src/app/plugin/plugins.tpl.html
+++ b/ui/src/app/plugin/plugins.tpl.html
@@ -31,7 +31,7 @@
                  on-export-plugin="vm.exportPlugin(event, vm.grid.detailsConfig.currentItem)"
                  on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'attribute.attributes' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.plugin}}"
@@ -39,7 +39,7 @@
                                 default-attribute-scope="{{vm.types.attributesScope.server.value}}">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'attribute.latest-telemetry' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.plugin}}"
@@ -48,19 +48,19 @@
                                 disable-attribute-scope-selection="true">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
             <tb-alarm-table flex entity-type="vm.types.entityType.plugin"
                             entity-id="vm.grid.operatingItem().id.id">
             </tb-alarm-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'plugin.events' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'plugin.events' | translate }}">
             <tb-event-table flex entity-type="vm.types.entityType.plugin"
                             entity-id="vm.grid.operatingItem().id.id"
                             tenant-id="vm.grid.operatingItem().tenantId.id"
                             default-event-type="{{vm.types.eventType.lcEvent.value}}">
             </tb-event-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
             <tb-relation-table flex
                                entity-id="vm.grid.operatingItem().id.id"
                                entity-type="{{vm.types.entityType.plugin}}">
diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html
index 336fe63..064728b 100644
--- a/ui/src/app/rule/rules.tpl.html
+++ b/ui/src/app/rule/rules.tpl.html
@@ -31,7 +31,7 @@
                                on-export-rule="vm.exportRule(event, vm.grid.detailsConfig.currentItem)"
                                on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'attribute.attributes' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.rule}}"
@@ -39,7 +39,7 @@
                                 default-attribute-scope="{{vm.types.attributesScope.server.value}}">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'attribute.latest-telemetry' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
             <tb-attribute-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
                                 entity-type="{{vm.types.entityType.rule}}"
@@ -48,19 +48,19 @@
                                 disable-attribute-scope-selection="true">
             </tb-attribute-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
             <tb-alarm-table flex entity-type="vm.types.entityType.rule"
                             entity-id="vm.grid.operatingItem().id.id">
             </tb-alarm-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'rule.events' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'rule.events' | translate }}">
             <tb-event-table flex entity-type="vm.types.entityType.rule"
                             entity-id="vm.grid.operatingItem().id.id"
                             tenant-id="vm.grid.operatingItem().tenantId.id"
                             default-event-type="{{vm.types.eventType.lcEvent.value}}">
             </tb-event-table>
         </md-tab>
-        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
             <tb-relation-table flex
                                entity-id="vm.grid.operatingItem().id.id"
                                entity-type="{{vm.types.entityType.rule}}">
diff --git a/ui/src/app/tenant/tenants.tpl.html b/ui/src/app/tenant/tenants.tpl.html
index e407291..94a827f 100644
--- a/ui/src/app/tenant/tenants.tpl.html
+++ b/ui/src/app/tenant/tenants.tpl.html
@@ -29,7 +29,7 @@
 				   on-manage-users="vm.openTenantUsers(event, vm.grid.detailsConfig.currentItem)"
 				   on-delete-tenant="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-tenant>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
 			<tb-attribute-table flex
 								entity-id="vm.grid.operatingItem().id.id"
 								entity-type="{{vm.types.entityType.tenant}}"
@@ -37,7 +37,7 @@
 								default-attribute-scope="{{vm.types.attributesScope.server.value}}">
 			</tb-attribute-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
 			<tb-attribute-table flex
 								entity-id="vm.grid.operatingItem().id.id"
 								entity-type="{{vm.types.entityType.tenant}}"
@@ -46,19 +46,19 @@
 								disable-attribute-scope-selection="true">
 			</tb-attribute-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
 			<tb-alarm-table flex entity-type="vm.types.entityType.tenant"
 							entity-id="vm.grid.operatingItem().id.id">
 			</tb-alarm-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'tenant.events' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'tenant.events' | translate }}">
 			<tb-event-table flex entity-type="vm.types.entityType.tenant"
 							entity-id="vm.grid.operatingItem().id.id"
 							tenant-id="vm.types.id.nullUid"
 							default-event-type="{{vm.types.eventType.error.value}}">
 			</tb-event-table>
 		</md-tab>
-		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
+		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
 			<tb-relation-table flex
 							   entity-id="vm.grid.operatingItem().id.id"
 							   entity-type="{{vm.types.entityType.tenant}}">
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..5a65af2
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -0,0 +1,597 @@
+/*
+ * 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';
+import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.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: '=',
+            ctx: '='
+        },
+        controller: AlarmsTableWidgetController,
+        controllerAs: 'vm',
+        templateUrl: alarmsTableWidgetTemplate
+    };
+}
+
+/*@ngInject*/
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, alarmService, utils, types) {
+    var vm = this;
+
+    vm.stylesInfo = {};
+    vm.contentsInfo = {};
+    vm.columnWidth = {};
+
+    vm.showData = true;
+    vm.hasData = false;
+
+    vm.alarms = [];
+    vm.alarmsCount = 0;
+    vm.selectedAlarms = []
+
+    vm.alarmSource = null;
+    vm.allAlarms = null;
+
+    vm.currentAlarm = null;
+
+    vm.enableSelection = true;
+    vm.displayDetails = true;
+    vm.allowAcknowledgment = true;
+    vm.allowClear = true;
+    vm.displayPagination = true;
+    vm.defaultPageSize = 10;
+    vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value;
+
+    vm.query = {
+        order: vm.defaultSortOrder,
+        limit: vm.defaultPageSize,
+        page: 1,
+        search: null
+    };
+
+    vm.searchAction = {
+        name: 'action.search',
+        show: true,
+        onAction: function() {
+            vm.enterFilterMode();
+        },
+        icon: 'search'
+    };
+
+    vm.enterFilterMode = enterFilterMode;
+    vm.exitFilterMode = exitFilterMode;
+    vm.onReorder = onReorder;
+    vm.onPaginate = onPaginate;
+    vm.onRowClick = onRowClick;
+    vm.isCurrent = isCurrent;
+    vm.openAlarmDetails = openAlarmDetails;
+    vm.ackAlarms = ackAlarms;
+    vm.clearAlarms = clearAlarms;
+
+    vm.cellStyle = cellStyle;
+    vm.cellContent = cellContent;
+
+    $scope.$watch('vm.ctx', function() {
+        if (vm.ctx) {
+            vm.settings = vm.ctx.settings;
+            vm.widgetConfig = vm.ctx.widgetConfig;
+            vm.subscription = vm.ctx.defaultSubscription;
+            vm.alarmSource = vm.subscription.alarmSource;
+            initializeConfig();
+            updateAlarmSource();
+        }
+    });
+
+    $scope.$watch("vm.query.search", function(newVal, prevVal) {
+        if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
+            updateAlarms();
+        }
+    });
+
+    $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 = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
+        } else {
+            vm.limitOptions = null;
+        }
+    });
+
+    $scope.$watch('vm.selectedAlarms.length', function (newLength) {
+        var selectionMode = newLength ? true : false;
+        if (vm.ctx) {
+            if (selectionMode) {
+                vm.ctx.hideTitlePanel = true;
+            } else if (vm.query.search == null) {
+                vm.ctx.hideTitlePanel = false;
+            }
+        }
+    });
+
+    function initializeConfig() {
+
+        vm.ctx.widgetActions = [ vm.searchAction ];
+
+        if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
+            var translationId = types.translate.customTranslationsPrefix + vm.settings.alarmsTitle;
+            var translation = $translate.instant(translationId);
+            if (translation != translationId) {
+                vm.alarmsTitle = translation + '';
+            } else {
+                vm.alarmsTitle = vm.settings.alarmsTitle;
+            }
+        } else {
+            vm.alarmsTitle = $translate.instant('alarm.alarms');
+        }
+
+        vm.ctx.widgetTitle = vm.alarmsTitle;
+
+        vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
+        vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
+        vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
+        vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
+        vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
+        if (!vm.allowAcknowledgment && !vm.allowClear) {
+            vm.enableSelection = false;
+        }
+
+        vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
+
+        var pageSize = vm.settings.defaultPageSize;
+        if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 0) {
+            vm.defaultPageSize = pageSize;
+        }
+
+        if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) {
+            vm.defaultSortOrder = vm.settings.defaultSortOrder;
+        }
+
+        vm.query.order = vm.defaultSortOrder;
+        vm.query.limit = vm.defaultPageSize;
+        if (vm.isGtMd) {
+            vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
+        } else {
+            vm.limitOptions = null;
+        }
+
+        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-checkbox-column md-checkbox:not(.md-checked) .md-icon {\n'+
+            'border-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-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
+            'border-color: ' + mdDarkSecondary + ';\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 = '';
+        vm.ctx.hideTitlePanel = true;
+    }
+
+    function exitFilterMode () {
+        vm.query.search = null;
+        updateAlarms();
+        vm.ctx.hideTitlePanel = false;
+    }
+
+    function onReorder () {
+        updateAlarms();
+    }
+
+    function onPaginate () {
+        updateAlarms();
+    }
+
+    function onRowClick($event, alarm) {
+        if (vm.currentAlarm != alarm) {
+            vm.currentAlarm = alarm;
+        }
+    }
+
+    function isCurrent(alarm) {
+        return (vm.currentAlarm && alarm && vm.currentAlarm.id && alarm.id) &&
+            (vm.currentAlarm.id.id === alarm.id.id);
+    }
+
+    function openAlarmDetails($event, alarm) {
+        if (alarm && alarm.id) {
+            var onShowingCallback = {
+                onShowing: function(){}
+            }
+            $mdDialog.show({
+                controller: 'AlarmDetailsDialogController',
+                controllerAs: 'vm',
+                templateUrl: alarmDetailsDialogTemplate,
+                locals: {
+                    alarmId: alarm.id.id,
+                    allowAcknowledgment: vm.allowAcknowledgment,
+                    allowClear: vm.allowClear,
+                    showingCallback: onShowingCallback
+                },
+                parent: angular.element($document[0].body),
+                targetEvent: $event,
+                fullscreen: true,
+                skipHide: true,
+                onShowing: function(scope, element) {
+                    onShowingCallback.onShowing(scope, element);
+                }
+            }).then(function (alarm) {
+                if (alarm) {
+                    vm.subscription.update();
+                }
+            });
+
+        }
+    }
+
+    function ackAlarms($event) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.selectedAlarms && vm.selectedAlarms.length > 0) {
+            var title = $translate.instant('alarm.aknowledge-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat');
+            var content = $translate.instant('alarm.aknowledge-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat');
+            var confirm = $mdDialog.confirm()
+                .targetEvent($event)
+                .title(title)
+                .htmlContent(content)
+                .ariaLabel(title)
+                .cancel($translate.instant('action.no'))
+                .ok($translate.instant('action.yes'));
+            $mdDialog.show(confirm).then(function () {
+                var tasks = [];
+                for (var i=0;i<vm.selectedAlarms.length;i++) {
+                    var alarm = vm.selectedAlarms[i];
+                    if (alarm.id) {
+                        tasks.push(alarmService.ackAlarm(alarm.id.id));
+                    }
+                }
+                if (tasks.length) {
+                    $q.all(tasks).then(function () {
+                        vm.selectedAlarms = [];
+                        vm.subscription.update();
+                    });
+                }
+
+            });
+        }
+    }
+
+    function clearAlarms($event) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.selectedAlarms && vm.selectedAlarms.length > 0) {
+            var title = $translate.instant('alarm.clear-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat');
+            var content = $translate.instant('alarm.clear-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat');
+            var confirm = $mdDialog.confirm()
+                .targetEvent($event)
+                .title(title)
+                .htmlContent(content)
+                .ariaLabel(title)
+                .cancel($translate.instant('action.no'))
+                .ok($translate.instant('action.yes'));
+            $mdDialog.show(confirm).then(function () {
+                var tasks = [];
+                for (var i=0;i<vm.selectedAlarms.length;i++) {
+                    var alarm = vm.selectedAlarms[i];
+                    if (alarm.id) {
+                        tasks.push(alarmService.clearAlarm(alarm.id.id));
+                    }
+                }
+                if (tasks.length) {
+                    $q.all(tasks).then(function () {
+                        vm.selectedAlarms = [];
+                        vm.subscription.update();
+                    });
+                }
+
+            });
+        }
+    }
+
+
+    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;
+
+        if (vm.displayPagination) {
+            var startIndex = vm.query.limit * (vm.query.page - 1);
+            vm.alarms = result.slice(startIndex, startIndex + vm.query.limit);
+        } else {
+            vm.alarms = result;
+        }
+
+        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);
+            }
+        }
+        if (!style.width) {
+            var columnWidth = vm.columnWidth[key.label];
+            style.width = columnWidth;
+        }
+        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 value;
+            }
+        } 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 {};
+            }
+        } 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() {
+
+        vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle);
+
+        vm.stylesInfo = {};
+        vm.contentsInfo = {};
+        vm.columnWidth = {};
+
+        for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
+            var dataKey = vm.alarmSource.dataKeys[d];
+
+            var translationId = types.translate.customTranslationsPrefix + dataKey.label;
+            var translation = $translate.instant(translationId);
+            if (translation != translationId) {
+                dataKey.title = translation + '';
+            } else {
+                dataKey.title = dataKey.label;
+            }
+
+            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
+            };
+
+            var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
+            vm.columnWidth[dataKey.label] = columnWidth;
+        }
+    }
+
+}
\ 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..600be46
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.scss
@@ -0,0 +1,58 @@
+/**
+ * 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-has-timewindow {
+  .tb-alarms-table {
+    md-toolbar {
+      min-height: 60px;
+      max-height: 60px;
+      &.md-table-toolbar {
+        .md-toolbar-tools {
+          max-height: 60px;
+        }
+      }
+    }
+  }
+}
+
+.tb-alarms-table {
+
+  md-toolbar {
+    min-height: 39px;
+    max-height: 39px;
+    &.md-table-toolbar {
+      .md-toolbar-tools {
+        max-height: 39px;
+      }
+    }
+  }
+
+  &.tb-data-table {
+    table.md-table, table.md-table.md-row-select {
+      tbody {
+        tr {
+          td {
+            &.tb-action-cell {
+              min-width: 36px;
+              max-width: 36px;
+              width: 36px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
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..942b2af
--- /dev/null
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -0,0 +1,103 @@
+<!--
+
+    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">
+                <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 ng-if="vm.allowAcknowledgment" 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 ng-if="vm.allowClear" 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="vm.enableSelection" 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.title }}</span></th>
+                    <th md-column ng-if="vm.displayDetails"><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 flex ng-repeat="key in vm.alarmSource.dataKeys"
+                        ng-style="vm.cellStyle(alarm, key)"
+                        ng-bind-html="vm.cellContent(alarm, key)">
+                    </td>
+                    <td md-cell ng-if="vm.displayDetails" 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 ng-if="vm.displayPagination" 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);
             }
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index ab720c5..c47f177 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -281,7 +281,61 @@ pre.tb-highlight {
     display: flex;
   }
   table.md-table {
+    &.md-row-select td.md-cell,
+    &.md-row-select th.md-column {
+      &:first-child {
+        width: 20px;
+        padding: 0 0 0 12px;
+      }
+
+      &:nth-child(2) {
+        padding: 0 12px;
+      }
+
+      &:nth-child(n+3):nth-last-child(n+2) {
+        padding: 0 28px 0 0;
+      }
+    }
+
+    &:not(.md-row-select) td.md-cell,
+    &:not(.md-row-select) th.md-column {
+      &:first-child {
+        padding: 0 12px;
+      }
+
+      &:nth-child(n+2):nth-last-child(n+2) {
+        padding: 0 28px 0 0;
+      }
+    }
+
+    td.md-cell,
+    th.md-column {
+
+      &:last-child {
+        padding: 0 12px 0 0;
+      }
+
+    }
+  }
+
+  table.md-table, table.md-table.md-row-select {
     tbody {
+      &.md-body {
+        tr {
+          &.md-row:not([disabled]) {
+            outline: none;
+            &:hover {
+              background-color: rgba(221, 221, 221, 0.3) !important;
+            }
+            &.md-selected {
+              background-color: rgba(221, 221, 221, 0.5) !important;
+            }
+            &.tb-current, &.tb-current:hover{
+              background-color: rgba(221, 221, 221, 0.65) !important;
+            }
+          }
+        }
+      }
       tr {
         td {
           &.tb-action-cell {