thingsboard-aplcache

Merge pull request #224 from thingsboard/feature/TB-70 TB-70:

7/26/2017 2:28:09 PM

Details

diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
index aec4dd0..164366d 100644
--- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
@@ -15,7 +15,7 @@
         "resources": [],
         "templateHtml": "<tb-alarms-table-widget \n    table-id=\"tableId\"\n    ctx=\"ctx\">\n</tb-alarms-table-widget>",
         "templateCss": "",
-        "controllerScript": "self.onInit = function() {\n    var scope = self.ctx.$scope;\n    var id = self.ctx.$scope.$injector.get('utils').guid();\n    scope.tableId = \"table-\"+id;\n    scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n    self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}\n",
+        "controllerScript": "self.onInit = function() {\n    var scope = self.ctx.$scope;\n    var id = self.ctx.$scope.$injector.get('utils').guid();\n    scope.tableId = \"table-\"+id;\n    scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n    self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n    return {\n        'actionCellButton': {\n            name: 'widget-action.action-cell-button',\n            multiple: true\n        },\n        'rowClick': {\n            name: 'widget-action.row-click',\n            multiple: false\n        }\n    };\n}\n\nself.onDestroy = function() {\n}\n",
         "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"AlarmTableSettings\",\n        \"properties\": {\n            \"alarmsTitle\": {\n                \"title\": \"Alarms table title\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"enableSelection\": {\n                \"title\": \"Enable alarms selection\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"enableSearch\": {\n                \"title\": \"Enable alarms search\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"displayDetails\": {\n                \"title\": \"Display alarm details\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"allowAcknowledgment\": {\n                \"title\": \"Allow alarms acknowledgment\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"allowClear\": {\n                \"title\": \"Allow alarms clear\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"displayPagination\": {\n                \"title\": \"Display pagination\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"defaultPageSize\": {\n                \"title\": \"Default page size\",\n                \"type\": \"number\",\n                \"default\": 10\n            },\n            \"defaultSortOrder\": {\n                \"title\": \"Default sort order\",\n                \"type\": \"string\",\n                \"default\": \"-createdTime\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"alarmsTitle\",\n        \"enableSelection\",\n        \"enableSearch\",\n        \"displayDetails\",\n        \"allowAcknowledgment\",\n        \"allowClear\",\n        \"displayPagination\",\n        \"defaultPageSize\",\n        \"defaultSortOrder\"\n    ]\n}",
         "dataKeySettingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"columnWidth\": {\n                \"title\": \"Column width (px or %)\",\n                \"type\": \"string\",\n                \"default\": \"0px\"\n            },\n            \"useCellStyleFunction\": {\n                \"title\": \"Use cell style function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellStyleFunction\": {\n                \"title\": \"Cell style function: f(value)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"useCellContentFunction\": {\n                \"title\": \"Use cell content function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellContentFunction\": {\n                \"title\": \"Cell content function: f(value, alarm, filter)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"columnWidth\",\n        \"useCellStyleFunction\",\n        {\n            \"key\": \"cellStyleFunction\",\n            \"type\": \"javascript\"\n        },\n        \"useCellContentFunction\",\n        {\n            \"key\": \"cellContentFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}",
         "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}"
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index b20d0b3..344edf5 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -353,16 +353,20 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
 
     function entitiesToEntitiesInfo(entities) {
         var entitiesInfo = [];
-        for (var d = 0; d < entities.length; d++) {
-            entitiesInfo.push(entityToEntityInfo(entities[d]));
+        if (entities) {
+            for (var d = 0; d < entities.length; d++) {
+                entitiesInfo.push(entityToEntityInfo(entities[d]));
+            }
         }
         return entitiesInfo;
     }
 
     function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
         var entitiesInfo = [];
-        for (var d = 0; d < entityRelations.length; d++) {
-            entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
+        if (entityRelations) {
+            for (var d = 0; d < entityRelations.length; d++) {
+                entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
+            }
         }
         return entitiesInfo;
     }
@@ -371,7 +375,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
     function resolveAlias(entityAlias, stateParams) {
         var deferred = $q.defer();
         var filter = entityAlias.filter;
-        resolveAliasFilter(filter, stateParams, -1).then(
+        resolveAliasFilter(filter, stateParams, -1, false).then(
             function (result) {
                 var aliasInfo = {
                     alias: entityAlias.alias,
@@ -404,10 +408,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 entityId = stateParams.entityId;
             }
         }
+        if (!entityId) {
+            entityId = filter.defaultStateEntity;
+        }
         return entityId;
     }
 
-    function resolveAliasFilter(filter, stateParams, maxItems) {
+    function resolveAliasFilter(filter, stateParams, maxItems, failOnEmpty) {
         var deferred = $q.defer();
         var result = {
             entities: [],
@@ -421,7 +428,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.aliasFilterType.entityList.value:
                 getEntities(filter.entityType, filter.entityList).then(
                     function success(entities) {
-                        if (entities && entities.length) {
+                        if (entities && entities.length || !failOnEmpty) {
                             result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
@@ -436,7 +443,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.aliasFilterType.entityName.value:
                 getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
                     function success(entities) {
-                        if (entities && entities.length) {
+                        if (entities && entities.length || !failOnEmpty) {
                             result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
@@ -457,7 +464,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                             deferred.resolve(result);
                         },
                         function fail() {
-                            deferred.reject();
+                            deferred.resolve(result);
                         }
                     );
                 } else {
@@ -467,7 +474,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.aliasFilterType.assetType.value:
                 getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
                     function success(entities) {
-                        if (entities && entities.length) {
+                        if (entities && entities.length || !failOnEmpty) {
                             result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
@@ -482,7 +489,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.aliasFilterType.deviceType.value:
                 getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
                     function success(entities) {
-                        if (entities && entities.length) {
+                        if (entities && entities.length || !failOnEmpty) {
                             result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
@@ -517,8 +524,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
                     entityRelationService.findInfoByQuery(searchQuery).then(
                         function success(allRelations) {
-                            if (allRelations && allRelations.length) {
-                                if (angular.isDefined(maxItems) && maxItems > 0) {
+                            if (allRelations && allRelations.length || !failOnEmpty) {
+                                if (angular.isDefined(maxItems) && maxItems > 0 && allRelations) {
                                     var limit = Math.min(allRelations.length, maxItems);
                                     allRelations.length = limit;
                                 }
@@ -566,8 +573,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     }
                     findByQueryPromise.then(
                         function success(entities) {
-                            if (entities && entities.length) {
-                                if (angular.isDefined(maxItems) && maxItems > 0) {
+                            if (entities && entities.length || !failOnEmpty) {
+                                if (angular.isDefined(maxItems) && maxItems > 0 && entities) {
                                     var limit = Math.min(entities.length, maxItems);
                                     entities.length = limit;
                                 }
@@ -720,7 +727,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
 
     function checkEntityAlias(entityAlias) {
         var deferred = $q.defer();
-        resolveAliasFilter(entityAlias.filter, null, 1).then(
+        resolveAliasFilter(entityAlias.filter, null, 1, true).then(
             function success(result) {
                 if (result.stateEntity) {
                     deferred.resolve(true);
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 4a6f743..fd26629 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -737,7 +737,15 @@ export default class Subscription {
                     this.ctx.datasourceService.subscribeToDatasource(listener);
                 }
 
-                if (datasource.unresolvedStateEntity || !datasource.dataKeys.length) {
+                var forceUpdate = false;
+                if (datasource.unresolvedStateEntity ||
+                    !datasource.dataKeys.length ||
+                    (datasource.type === this.ctx.types.datasourceType.entity && !datasource.entityId)
+                ) {
+                    forceUpdate = true;
+                }
+
+                if (forceUpdate) {
                     this.notifyDataLoaded();
                     this.onDataUpdated();
                 }
@@ -767,7 +775,14 @@ export default class Subscription {
 
         this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener);
 
-        if (this.alarmSource.unresolvedStateEntity) {
+        var forceUpdate = false;
+        if (this.alarmSource.unresolvedStateEntity ||
+            (this.alarmSource.type === this.ctx.types.datasourceType.entity && !this.alarmSource.entityId)
+        ) {
+            forceUpdate = true;
+        }
+
+        if (forceUpdate) {
             this.notifyDataLoaded();
             this.onDataUpdated();
         }
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
index f0512d7..1e2c2ad 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.js
+++ b/ui/src/app/dashboard/states/entity-state-controller.js
@@ -282,10 +282,30 @@ export default function EntityStateController($scope, $location, $state, $stateP
 
     function updateLocation() {
         if (vm.stateObject[vm.stateObject.length-1].id) {
-            $location.search({state : utils.objToBase64(vm.stateObject)});
+            if (isDefaultState()) {
+                $location.search({state : null});
+            } else {
+                $location.search({state : utils.objToBase64(vm.stateObject)});
+            }
         }
     }
 
+    function isDefaultState() {
+        if (vm.stateObject.length == 1) {
+            var state = vm.stateObject[0];
+            var rootStateId = dashboardUtils.getRootStateId(vm.states);
+            if (state.id == rootStateId && (!state.params || isEmpty(state.params))) {
+                return true;
+            }
+        }
+        return false;
+    }
 
+    function isEmpty(map) {
+        for(var key in map) {
+            return !map.hasOwnProperty(key);
+        }
+        return true;
+    }
 
 }
diff --git a/ui/src/app/entity/alias/entity-alias-dialog.controller.js b/ui/src/app/entity/alias/entity-alias-dialog.controller.js
index a5b221a..d1f353d 100644
--- a/ui/src/app/entity/alias/entity-alias-dialog.controller.js
+++ b/ui/src/app/entity/alias/entity-alias-dialog.controller.js
@@ -71,7 +71,7 @@ export default function EntityAliasDialogController($scope, $mdDialog, $q, $filt
             entity: null,
             stateEntity: false
         }
-        entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
+        entityService.resolveAliasFilter(vm.alias.filter, null, 1, true).then(
             function success(result) {
                 validationResult.stateEntity = result.stateEntity;
                 var entities = result.entities;
diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js
index 8f8153d..4610426 100644
--- a/ui/src/app/entity/entity-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-autocomplete.directive.js
@@ -95,12 +95,16 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
             }
         });
 
-        scope.$watch('entity', function () {
-            scope.updateView();
+        scope.$watch('entity', function (newVal, prevVal) {
+            if (!angular.equals(newVal, prevVal)) {
+                scope.updateView();
+            }
         });
 
-        scope.$watch('disabled', function () {
-            scope.updateView();
+        scope.$watch('disabled', function (newVal, prevVal) {
+            if (!angular.equals(newVal, prevVal)) {
+                scope.updateView();
+            }
         });
 
 
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index db7a744..c68458d 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -55,6 +55,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                     break;
                 case types.aliasFilterType.stateEntity.value:
                     filter.stateEntityParamName = null;
+                    filter.defaultStateEntity = null;
                     break;
                 case types.aliasFilterType.assetType.value:
                     filter.assetType = null;
@@ -69,6 +70,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                 case types.aliasFilterType.deviceSearchQuery.value:
                     filter.rootStateEntity = false;
                     filter.stateEntityParamName = null;
+                    filter.defaultStateEntity = null;
                     filter.rootEntity = null;
                     filter.direction = types.entitySearchDirection.from;
                     filter.maxLevel = 1;
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index dae2817..132a15e 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -66,6 +66,14 @@
                    ng-model="filter.stateEntityParamName"
                    aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
         </md-input-container>
+        <div layout="column">
+            <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
+            <tb-entity-select flex
+                              the-form="theForm"
+                              tb-required="false"
+                              ng-model="filter.defaultStateEntity">
+            </tb-entity-select>
+        </div>
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
         <tb-entity-subtype-autocomplete
@@ -97,27 +105,35 @@
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
         <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
-        <div flex layout="row">
+        <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
+            <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                       aria-label="{{ 'alias.root-state-entity' | translate }}">
+            </md-switch>
+            <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+        </section>
+        <div flex layout="row" ng-if="!filter.rootStateEntity">
             <tb-entity-select flex
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <div layout="column">
-                <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
-                    <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
-                    <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
-                               aria-label="{{ 'alias.root-state-entity' | translate }}">
-                    </md-switch>
-                </section>
-                <md-input-container ng-if="filter.rootStateEntity" class="md-block">
-                    <label translate>alias.state-entity-parameter-name</label>
-                    <input name="stateEntityParamName"
-                           placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
-                           ng-model="filter.stateEntityParamName"
-                           aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
-                </md-input-container>
+        </div>
+        <div flex layout="row" ng-if="filter.rootStateEntity">
+            <md-input-container class="md-block" style="margin-top: 32px;">
+                <label translate>alias.state-entity-parameter-name</label>
+                <input name="stateEntityParamName"
+                       placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
+                       ng-model="filter.stateEntityParamName"
+                       aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
+            </md-input-container>
+            <div flex layout="column">
+                <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
+                <tb-entity-select flex
+                                  the-form="theForm"
+                                  tb-required="false"
+                                  ng-model="filter.defaultStateEntity">
+                </tb-entity-select>
             </div>
         </div>
         <div flex layout="row">
@@ -148,27 +164,35 @@
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
         <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
-        <div flex layout="row">
+        <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
+            <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                       aria-label="{{ 'alias.root-state-entity' | translate }}">
+            </md-switch>
+            <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+        </section>
+        <div flex layout="row" ng-if="!filter.rootStateEntity">
             <tb-entity-select flex
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <div layout="column">
-                <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
-                    <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
-                    <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
-                               aria-label="{{ 'alias.root-state-entity' | translate }}">
-                    </md-switch>
-                </section>
-                <md-input-container ng-if="filter.rootStateEntity" class="md-block">
-                    <label translate>alias.state-entity-parameter-name</label>
-                    <input name="stateEntityParamName"
-                           placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
-                           ng-model="filter.stateEntityParamName"
-                           aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
-                </md-input-container>
+        </div>
+        <div flex layout="row" ng-if="filter.rootStateEntity">
+            <md-input-container class="md-block" style="margin-top: 32px;">
+                <label translate>alias.state-entity-parameter-name</label>
+                <input name="stateEntityParamName"
+                       placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
+                       ng-model="filter.stateEntityParamName"
+                       aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
+            </md-input-container>
+            <div flex layout="column">
+                <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
+                <tb-entity-select flex
+                                  the-form="theForm"
+                                  tb-required="false"
+                                  ng-model="filter.defaultStateEntity">
+                </tb-entity-select>
             </div>
         </div>
         <div flex layout="row">
@@ -207,27 +231,35 @@
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
         <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
-        <div flex layout="row">
+        <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
+            <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                       aria-label="{{ 'alias.root-state-entity' | translate }}">
+            </md-switch>
+            <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+        </section>
+        <div flex layout="row" ng-if="!filter.rootStateEntity">
             <tb-entity-select flex
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <div layout="column">
-                <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
-                    <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
-                    <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
-                               aria-label="{{ 'alias.root-state-entity' | translate }}">
-                    </md-switch>
-                </section>
-                <md-input-container ng-if="filter.rootStateEntity" class="md-block">
-                    <label translate>alias.state-entity-parameter-name</label>
-                    <input name="stateEntityParamName"
-                           placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
-                           ng-model="filter.stateEntityParamName"
-                           aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
-                </md-input-container>
+        </div>
+        <div flex layout="row" ng-if="filter.rootStateEntity">
+            <md-input-container class="md-block" style="margin-top: 32px;">
+                <label translate>alias.state-entity-parameter-name</label>
+                <input name="stateEntityParamName"
+                       placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
+                       ng-model="filter.stateEntityParamName"
+                       aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
+            </md-input-container>
+            <div flex layout="column">
+                <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
+                <tb-entity-select flex
+                                  the-form="theForm"
+                                  tb-required="false"
+                                  ng-model="filter.defaultStateEntity">
+                </tb-entity-select>
             </div>
         </div>
         <div flex layout="row">
diff --git a/ui/src/app/entity/entity-select.directive.js b/ui/src/app/entity/entity-select.directive.js
index 705c6b8..2778a79 100644
--- a/ui/src/app/entity/entity-select.directive.js
+++ b/ui/src/app/entity/entity-select.directive.js
@@ -48,6 +48,7 @@ export default function EntitySelect($compile, $templateCache) {
         }
 
         ngModelCtrl.$render = function () {
+            destroyWatchers();
             if (ngModelCtrl.$viewValue) {
                 var value = ngModelCtrl.$viewValue;
                 scope.model.entityType = value.entityType;
@@ -56,19 +57,43 @@ export default function EntitySelect($compile, $templateCache) {
                 scope.model.entityType = null;
                 scope.model.entityId = null;
             }
+            initWatchers();
         }
 
-        scope.$watch('model.entityType', function () {
-            scope.updateView();
-        });
+        function initWatchers() {
+            scope.entityTypeDeregistration = scope.$watch('model.entityType', function (newVal, prevVal) {
+                if (!angular.equals(newVal, prevVal)) {
+                    scope.updateView();
+                }
+            });
+
+            scope.entityIdDeregistration = scope.$watch('model.entityId', function (newVal, prevVal) {
+                if (!angular.equals(newVal, prevVal)) {
+                    scope.updateView();
+                }
+            });
 
-        scope.$watch('model.entityId', function () {
-            scope.updateView();
-        });
+            scope.disabledDeregistration = scope.$watch('disabled', function (newVal, prevVal) {
+                if (!angular.equals(newVal, prevVal)) {
+                    scope.updateView();
+                }
+            });
+        }
 
-        scope.$watch('disabled', function () {
-            scope.updateView();
-        });
+        function destroyWatchers() {
+            if (scope.entityTypeDeregistration) {
+                scope.entityTypeDeregistration();
+                scope.entityTypeDeregistration = null;
+            }
+            if (scope.entityIdDeregistration) {
+                scope.entityIdDeregistration();
+                scope.entityIdDeregistration = null;
+            }
+            if (scope.disabledDeregistration) {
+                scope.disabledDeregistration();
+                scope.disabledDeregistration = null;
+            }
+        }
 
         $compile(element.contents())(scope);
     }
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 6513ce8..f16fe9a 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -188,6 +188,7 @@ export default angular.module('thingsboard.locale', [])
                     "root-state-entity": "Use dashboard state entity as root",
                     "root-entity": "Root entity",
                     "state-entity-parameter-name": "State entity parameter name",
+                    "default-state-entity": "Default state entity",
                     "default-entity-parameter-name": "By default",
                     "max-relation-level": "Max relation level",
                     "unlimited-level": "Unlimited level",
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 7618e6a..a91c2a0 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -69,6 +69,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.displayDetails = true;
     vm.allowAcknowledgment = true;
     vm.allowClear = true;
+    vm.actionCellDescriptors = [];
     vm.displayPagination = true;
     vm.defaultPageSize = 10;
     vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value;
@@ -94,6 +95,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.onReorder = onReorder;
     vm.onPaginate = onPaginate;
     vm.onRowClick = onRowClick;
+    vm.onActionButtonClick = onActionButtonClick;
     vm.isCurrent = isCurrent;
     vm.openAlarmDetails = openAlarmDetails;
     vm.ackAlarms = ackAlarms;
@@ -157,6 +159,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
 
         vm.ctx.widgetActions = [ vm.searchAction ];
 
+        vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
+
         if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
             vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
         } else {
@@ -280,9 +284,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     }
 
     function onRowClick($event, alarm) {
+        if ($event) {
+            $event.stopPropagation();
+        }
         if (vm.currentAlarm != alarm) {
             vm.currentAlarm = alarm;
         }
+        var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
+        if (descriptors.length) {
+            var entityId;
+            var entityName;
+            if (vm.currentAlarm && vm.currentAlarm.originator) {
+                entityId = vm.currentAlarm.originator;
+                entityName = vm.currentAlarm.originatorName;
+            }
+            vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
+        }
+    }
+
+    function onActionButtonClick($event, alarm, actionDescriptor) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var entityId;
+        var entityName;
+        if (alarm && alarm.originator) {
+            entityId = alarm.originator;
+            entityName = alarm.originatorName;
+        }
+        vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName);
     }
 
     function isCurrent(alarm) {
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
index 942b2af..b7416e9 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -64,6 +64,7 @@
                 <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>
+                    <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
                 </tr>
                 </thead>
                 <tbody md-body>
@@ -83,6 +84,19 @@
                             </md-tooltip>
                         </md-button>
                     </td>
+                    <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+                        ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
+                                   maxWidth: vm.actionCellDescriptors.length*36+'px',
+                                   width: vm.actionCellDescriptors.length*36+'px'}">
+                        <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
+                                   aria-label="{{ actionDescriptor.displayName }}"
+                                   ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)">
+                            <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
+                            <md-tooltip md-direction="top">
+                                {{ actionDescriptor.displayName }}
+                            </md-tooltip>
+                        </md-button>
+                    </td>
                 </tr>
                 </tbody>
             </table>
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index f0b5835..7d97fe4 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -107,6 +107,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
             vm.datasources = vm.subscription.datasources;
             initializeConfig();
             updateDatasources();
+            updateEntities();
         }
     });
 
@@ -276,16 +277,16 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
         }
         if (vm.currentEntity != entity) {
             vm.currentEntity = entity;
-            var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
-            if (descriptors.length) {
-                var entityId;
-                var entityName;
-                if (vm.currentEntity) {
-                    entityId = vm.currentEntity.id;
-                    entityName = vm.currentEntity.entityName;
-                }
-                vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
+        }
+        var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
+        if (descriptors.length) {
+            var entityId;
+            var entityName;
+            if (vm.currentEntity) {
+                entityId = vm.currentEntity.id;
+                entityName = vm.currentEntity.entityName;
             }
+            vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
         }
     }