thingsboard-memoizeit

TB-63: Improve alarms widget. Find highest alarm severity

6/15/2017 2:15:34 PM

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 58f7316..06a9b34 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
@@ -27,6 +27,7 @@ import org.springframework.util.StringUtils;
 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;
@@ -46,6 +47,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;
@@ -242,6 +244,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/api/alarm.service.js b/ui/src/app/api/alarm.service.js
index db4cead..3b7a735 100644
--- a/ui/src/app/api/alarm.service.js
+++ b/ui/src/app/api/alarm.service.js
@@ -48,6 +48,7 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
         ackAlarm: ackAlarm,
         clearAlarm: clearAlarm,
         getAlarms: getAlarms,
+        getHighestAlarmSeverity: getHighestAlarmSeverity,
         pollAlarms: pollAlarms,
         cancelPollAlarms: cancelPollAlarms,
         subscribeForAlarms: subscribeForAlarms,
@@ -165,6 +166,23 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
         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,
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 ad09c82..81ce0fa 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -394,7 +394,6 @@ export default angular.module('thingsboard.types', [])
                 cards: "cards"
             },
             translate: {
-                dashboardStatePrefix: "dashboardState.state.",
                 customTranslationsPrefix: "custom."
             }
         }
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index ebded08..a3433c2 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -135,7 +135,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
         isLocalUrl: isLocalUrl,
         validateDatasources: validateDatasources,
         createKey: createKey,
-        createLabelFromDatasource: createLabelFromDatasource
+        createLabelFromDatasource: createLabelFromDatasource,
+        insertVariable: insertVariable
     }
 
     return service;
@@ -407,4 +408,18 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
         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 f21df59..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();
     }
@@ -178,6 +181,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     vm.widgetPadding = widgetPadding;
     vm.showWidgetTitle = showWidgetTitle;
     vm.showWidgetTitlePanel = showWidgetTitlePanel;
+    vm.showWidgetActions = showWidgetActions;
     vm.widgetTitleStyle = widgetTitleStyle;
     vm.widgetTitle = widgetTitle;
     vm.widgetActions = widgetActions;
@@ -271,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;
@@ -287,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() {
@@ -297,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();
     });
@@ -333,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;
@@ -345,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 () {
@@ -360,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]) {
@@ -650,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;
@@ -673,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 {
@@ -759,6 +825,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
         }
     }
 
+    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;
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index da069b0..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">
@@ -50,7 +50,7 @@
 									<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" ng-show="vm.showWidgetTitlePanel(widget)" 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"
@@ -103,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/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/widget.controller.js b/ui/src/app/components/widget.controller.js
index 63ad0ed..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, alarmService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
+                                         datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow,
                                          dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
 
     var vm = this;
@@ -50,7 +50,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         height: 0,
         hideTitlePanel: false,
         isEdit: isEdit,
-        isMobile: false,
+        isMobile: isMobile,
         widgetConfig: widget.config,
         settings: widget.config.settings,
         units: widget.config.units || '',
@@ -618,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/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/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/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 6cb8592..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;
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/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index f35434c..b213746 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -431,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",
@@ -472,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}}'?",
@@ -1169,9 +1168,6 @@ export default angular.module('thingsboard.locale', [])
                     "es_ES": "Spanish"
                 },
                 "custom": {
-                    "alarms": {
-                        "title": "Super ${entityName}"
-                    }
                 }
             }
         }
diff --git a/ui/src/app/locale/translate-handler.js b/ui/src/app/locale/translate-handler.js
index 20af258..7410062 100644
--- a/ui/src/app/locale/translate-handler.js
+++ b/ui/src/app/locale/translate-handler.js
@@ -18,8 +18,7 @@
 export default function ThingsboardMissingTranslateHandler($log, types) {
 
     return function (translationId) {
-        if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix) &&
-                             !translationId.startsWith(types.translate.customTranslationsPrefix)) {
+        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
index ebe6b03..5a65af2 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -210,6 +210,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         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'+
@@ -220,6 +223,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
             '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'+
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 5b5f929..c47f177 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -324,8 +324,14 @@ pre.tb-highlight {
         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: #dddddd !important;
+              background-color: rgba(221, 221, 221, 0.65) !important;
             }
           }
         }