thingsboard-aplcache

Details

diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 8833ca6..c9e1cd1 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -26,12 +26,12 @@
       "name": "Entities table",
       "descriptor": {
         "type": "latest",
-        "sizeX": 10.5,
-        "sizeY": 6.5,
+        "sizeX": 7.5,
+        "sizeY": 4.5,
         "resources": [],
         "templateHtml": "<tb-entities-table-widget \n    table-id=\"tableId\"\n    ctx=\"ctx\">\n</tb-entities-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('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1  \n    };\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",
+        "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('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        dataKeysOptional: true\n    };\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\": \"EntitiesTableSettings\",\n        \"properties\": {\n            \"entitiesTitle\": {\n                \"title\": \"Entities table title\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"enableSearch\": {\n                \"title\": \"Enable entities search\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"displayEntityName\": {\n                \"title\": \"Display entity name column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"entityNameColumnTitle\": {\n                \"title\": \"Entity name column title\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"displayEntityType\": {\n                \"title\": \"Display entity type column\",\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\": \"entityName\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"entitiesTitle\",\n        \"enableSearch\",\n        \"displayEntityName\",\n        \"entityNameColumnTitle\",\n        \"displayEntityType\",\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, entity, 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,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}"
diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index 9ae8677..24014fc 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -15,7 +15,7 @@
         "resources": [],
         "templateHtml": "<div style=\"height: 100%;\" id=\"device-terminal\"></div>",
         "templateCss": ".cmd .cursor.blink {\n    -webkit-animation-name: terminal-underline;\n       -moz-animation-name: terminal-underline;\n        -ms-animation-name: terminal-underline;\n            animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n    border-bottom-color: #aaa;\n}\n",
-        "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n    var subscription = self.ctx.defaultSubscription;\n    var rpcEnabled = subscription.rpcEnabled;\n    var deviceName = 'Simulated';\n    var prompt;\n    if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n        deviceName = subscription.targetDeviceName;\n    }\n    if (self.ctx.settings.requestTimeout) {\n        requestTimeout = self.ctx.settings.requestTimeout;\n    }\n    var greetings = 'Welcome to ThingsBoard RPC commands terminal.\\n\\n';\n    if (!rpcEnabled) {\n        greetings += 'Target device is not set!\\n\\n';\n        prompt = '';\n    } else {\n        greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n        greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n        prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n    }\n    \n    var terminal = $('#device-terminal', self.ctx.$container).terminal(\n        function(command) {\n            if (command !== '') {\n                try {\n                    var localCommand = angular.copy(command).trim();\n                    if (localCommand == 'help') {\n                        printUsage(this);\n                    } else {\n                        var cmdObj = $.terminal.parse_command(localCommand);\n                        if (cmdObj.args.length > 1) {\n                            this.error(\"Wrong number of arguments!\");\n                            this.echo(' ');\n                        } else {\n                            var params;\n                            if (cmdObj.args.length && cmdObj.args[0]) {\n                                params = JSON.parse(cmdObj.args[0]);\n                            }\n                            performRpc(this, cmdObj.name, params);\n                        }\n                    }\n                } catch(e) {\n                    this.error(new String(e));\n                }\n            } else {\n               this.echo('');\n            }\n        }, {\n            greetings: greetings,\n            prompt: prompt\n    });\n    \n    if (!rpcEnabled) {\n        terminal.error('No RPC target detected!').pause();\n    }\n}\n\n\nfunction printUsage(terminal) {\n    var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n    commandsListText += '   <method> [params body]\\n\\n';\n    commandsListText += '[[b;#fff;]Example 1:]\\n'; \n    commandsListText += '   myRemoteMethod1 myText\\n\\n'; \n    commandsListText += '[[b;#fff;]Example 2:]\\n'; \n    commandsListText += '   myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n    terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n    terminal.pause();\n    self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).then(\n        function success(responseBody) {\n            terminal.echo(JSON.stringify(responseBody));\n            terminal.echo(' ');\n            terminal.resume();\n        },\n        function fail() {\n            var errorText = self.ctx.defaultSubscription.rpcErrorText;\n            terminal.error(errorText);\n            terminal.echo(' ');\n            terminal.resume();\n        }\n    );\n}\n\n  \nself.onDestroy = function() {\n}\n",
+        "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n    var subscription = self.ctx.defaultSubscription;\n    var rpcEnabled = subscription.rpcEnabled;\n    var deviceName = 'Simulated';\n    var prompt;\n    if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n        deviceName = subscription.targetDeviceName;\n    }\n    if (self.ctx.settings.requestTimeout) {\n        requestTimeout = self.ctx.settings.requestTimeout;\n    }\n    var greetings = 'Welcome to ThingsBoard RPC commands terminal.\\n\\n';\n    if (!rpcEnabled) {\n        greetings += 'Target device is not set!\\n\\n';\n        prompt = '';\n    } else {\n        greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n        greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n        prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n    }\n    \n    var terminal = $('#device-terminal', self.ctx.$container).terminal(\n        function(command) {\n            if (command !== '') {\n                try {\n                    var localCommand = angular.copy(command).trim();\n                    if (localCommand == 'help') {\n                        printUsage(this);\n                    } else {\n                        var cmdObj = $.terminal.parse_command(localCommand);\n                        if (cmdObj.args.length > 1) {\n                            this.error(\"Wrong number of arguments!\");\n                            this.echo(' ');\n                        } else {\n                            var params;\n                            if (cmdObj.args.length && cmdObj.args[0]) {\n                                try {\n                                    params = JSON.parse(cmdObj.args[0]);\n                                } catch (e) {\n                                    params = cmdObj.args[0];\n                                }\n                            }\n                            performRpc(this, cmdObj.name, params);\n                        }\n                    }\n                } catch(e) {\n                    this.error(new String(e));\n                }\n            } else {\n               this.echo('');\n            }\n        }, {\n            greetings: greetings,\n            prompt: prompt\n    });\n    \n    if (!rpcEnabled) {\n        terminal.error('No RPC target detected!').pause();\n    }\n}\n\n\nfunction printUsage(terminal) {\n    var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n    commandsListText += '   <method> [params body]\\n\\n';\n    commandsListText += '[[b;#fff;]Example 1:]\\n'; \n    commandsListText += '   myRemoteMethod1 myText\\n\\n'; \n    commandsListText += '[[b;#fff;]Example 2:]\\n'; \n    commandsListText += '   myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n    terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n    terminal.pause();\n    self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).then(\n        function success(responseBody) {\n            terminal.echo(JSON.stringify(responseBody));\n            terminal.echo(' ');\n            terminal.resume();\n        },\n        function fail() {\n            var errorText = self.ctx.defaultSubscription.rpcErrorText;\n            terminal.error(errorText);\n            terminal.echo(' ');\n            terminal.resume();\n        }\n    );\n}\n\n  \nself.onDestroy = function() {\n}\n",
         "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"requestTimeout\": {\n                \"title\": \"RPC request timeout (ms)\",\n                \"type\": \"number\",\n                \"default\": 500\n            }\n        },\n        \"required\": [\"requestTimeout\"]\n    },\n    \"form\": [\n        \"requestTimeout\"\n    ]\n}",
         "dataKeySettingsSchema": "{}\n",
         "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Device terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js
index 9acd5aa..fd970c0 100644
--- a/ui/src/app/api/alias-controller.js
+++ b/ui/src/app/api/alias-controller.js
@@ -53,10 +53,11 @@ export default class AliasController {
     }
 
     dashboardStateChanged() {
-        var newEntityId = this.stateController.getStateParams().entityId;
         var changedAliasIds = [];
         for (var aliasId in this.resolvedAliasesToStateEntities) {
-            var prevEntityId = this.resolvedAliasesToStateEntities[aliasId];
+            var stateEntityInfo = this.resolvedAliasesToStateEntities[aliasId];
+            var newEntityId = this.stateController.getEntityId(stateEntityInfo.entityParamName);
+            var prevEntityId = stateEntityInfo.entityId;
             if (!angular.equals(newEntityId, prevEntityId)) {
                 changedAliasIds.push(aliasId);
                 this.setAliasUnresolved(aliasId);
@@ -93,19 +94,26 @@ export default class AliasController {
                 this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
                     function success(aliasInfo) {
                         aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
+                        delete aliasCtrl.resolvedAliasesPromise[aliasId];
                         if (aliasInfo.stateEntity) {
+                            var stateEntityInfo = {
+                                entityParamName: aliasInfo.entityParamName,
+                                entityId: aliasCtrl.stateController.getEntityId(aliasInfo.entityParamName)
+                            };
                             aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
-                                aliasCtrl.stateController.getStateParams().entityId;
+                                stateEntityInfo;
                         }
                         aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
                         deferred.resolve(aliasInfo);
                     },
                     function fail() {
                         deferred.reject();
+                        delete aliasCtrl.resolvedAliasesPromise[aliasId];
                     }
                 );
             } else {
                 deferred.reject();
+                delete aliasCtrl.resolvedAliasesPromise[aliasId];
             }
             return this.resolvedAliasesPromise[aliasId];
         }
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index dde6228..b20d0b3 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -376,6 +376,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 var aliasInfo = {
                     alias: entityAlias.alias,
                     stateEntity: result.stateEntity,
+                    entityParamName: result.entityParamName,
                     resolveMultiple: filter.resolveMultiple
                 };
                 aliasInfo.resolvedEntities = result.entities;
@@ -392,12 +393,30 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return deferred.promise;
     }
 
+    function getStateEntityId(filter, stateParams) {
+        var entityId = null;
+        if (stateParams) {
+            if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
+                if (stateParams[filter.stateEntityParamName]) {
+                    entityId = stateParams[filter.stateEntityParamName].entityId;
+                }
+            } else {
+                entityId = stateParams.entityId;
+            }
+        }
+        return entityId;
+    }
+
     function resolveAliasFilter(filter, stateParams, maxItems) {
         var deferred = $q.defer();
         var result = {
             entities: [],
             stateEntity: false
         };
+        if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
+            result.entityParamName = filter.stateEntityParamName;
+        }
+        var stateEntityId = getStateEntityId(filter, stateParams);
         switch (filter.type) {
             case types.aliasFilterType.entityList.value:
                 getEntities(filter.entityType, filter.entityList).then(
@@ -431,8 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 break;
             case types.aliasFilterType.stateEntity.value:
                 result.stateEntity = true;
-                if (stateParams && stateParams.entityId) {
-                    getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
+                if (stateEntityId) {
+                    getEntity(stateEntityId.entityType, stateEntityId.id).then(
                         function success(entity) {
                             result.entities = entitiesToEntitiesInfo([entity]);
                             deferred.resolve(result);
@@ -479,9 +498,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 result.stateEntity = filter.rootStateEntity;
                 var rootEntityType;
                 var rootEntityId;
-                if (result.stateEntity && stateParams && stateParams.entityId) {
-                    rootEntityType = stateParams.entityId.entityType;
-                    rootEntityId = stateParams.entityId.id;
+                if (result.stateEntity && stateEntityId) {
+                    rootEntityType = stateEntityId.entityType;
+                    rootEntityId = stateEntityId.id;
                 } else if (!result.stateEntity) {
                     rootEntityType = filter.rootEntity.entityType;
                     rootEntityId = filter.rootEntity.id;
@@ -520,9 +539,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.aliasFilterType.assetSearchQuery.value:
             case types.aliasFilterType.deviceSearchQuery.value:
                 result.stateEntity = filter.rootStateEntity;
-                if (result.stateEntity && stateParams && stateParams.entityId) {
-                    rootEntityType = stateParams.entityId.entityType;
-                    rootEntityId = stateParams.entityId.id;
+                if (result.stateEntity && stateEntityId) {
+                    rootEntityType = stateEntityId.entityType;
+                    rootEntityId = stateEntityId.id;
                 } else if (!result.stateEntity) {
                     rootEntityType = filter.rootEntity.entityType;
                     rootEntityId = filter.rootEntity.id;
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index dd93626..8678b9b 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -730,12 +730,15 @@ export default class Subscription {
                 index += datasource.dataKeys.length;
 
                 this.datasourceListeners.push(listener);
-                this.ctx.datasourceService.subscribeToDatasource(listener);
-                if (datasource.unresolvedStateEntity) {
+
+                if (datasource.dataKeys.length) {
+                    this.ctx.datasourceService.subscribeToDatasource(listener);
+                }
+
+                if (datasource.unresolvedStateEntity || !datasource.dataKeys.length) {
                     this.notifyDataLoaded();
                     this.onDataUpdated();
                 }
-
             }
         }
     }
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index a301925..8580b80 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -556,8 +556,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
          '    self.typeParameters = function() {\n\n' +
                     return {
                                 useCustomDatasources: false,
-                                maxDatasources: -1 //unlimited
-                                maxDataKeys: -1 //unlimited
+                                maxDatasources: -1, //unlimited
+                                maxDataKeys: -1, //unlimited
+                                dataKeysOptional: false
                            };
          '    }\n\n' +
 
@@ -625,6 +626,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
             if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
                 result.typeParameters.maxDataKeys = -1;
             }
+            if (angular.isUndefined(result.typeParameters.dataKeysOptional)) {
+                result.typeParameters.dataKeysOptional = false;
+            }
             if (angular.isFunction(widgetTypeInstance.actionSources)) {
                 result.actionSources = widgetTypeInstance.actionSources();
             } else {
diff --git a/ui/src/app/components/datasource.directive.js b/ui/src/app/components/datasource.directive.js
index 42011b0..6d81553 100644
--- a/ui/src/app/components/datasource.directive.js
+++ b/ui/src/app/components/datasource.directive.js
@@ -83,6 +83,7 @@ function Datasource($compile, $templateCache, utils, types) {
         scope: {
             aliasController: '=',
             maxDataKeys: '=',
+            optDataKeys: '=',
             widgetType: '=',
             functionsOnly: '=',
             datakeySettingsSchema: '=',
diff --git a/ui/src/app/components/datasource.tpl.html b/ui/src/app/components/datasource.tpl.html
index 41b18db..df0b150 100644
--- a/ui/src/app/components/datasource.tpl.html
+++ b/ui/src/app/components/datasource.tpl.html
@@ -28,6 +28,7 @@
                             ng-switch-default
                             ng-model="model"
                             max-data-keys="maxDataKeys"
+                            opt-data-keys="optDataKeys"
                             datakey-settings-schema="datakeySettingsSchema"
                             ng-required="model.type === types.datasourceType.function"
                             widget-type="widgetType"
@@ -36,6 +37,7 @@
         <tb-datasource-entity flex
                               ng-model="model"
                               max-data-keys="maxDataKeys"
+                              opt-data-keys="optDataKeys"
                               datakey-settings-schema="datakeySettingsSchema"
                               ng-switch-when="entity"
                               ng-required="model.type === types.datasourceType.entity"
diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js
index 46c8b63..2cc5d2c 100644
--- a/ui/src/app/components/datasource-entity.directive.js
+++ b/ui/src/app/components/datasource-entity.directive.js
@@ -68,10 +68,14 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
                     ngModelCtrl.$setValidity('entityAlias',
                         angular.isDefined(value.entityAliasId) &&
                         value.entityAliasId != null);
-                    ngModelCtrl.$setValidity('entityKeys',
-                        angular.isDefined(value.dataKeys) &&
-                        value.dataKeys != null &&
-                        value.dataKeys.length > 0);
+                    if (scope.optDataKeys) {
+                        ngModelCtrl.$setValidity('entityKeys', true);
+                    } else {
+                        ngModelCtrl.$setValidity('entityKeys',
+                            angular.isDefined(value.dataKeys) &&
+                            value.dataKeys != null &&
+                            value.dataKeys.length > 0);
+                    }
                 }
             }
         };
@@ -281,6 +285,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
         scope: {
             widgetType: '=',
             maxDataKeys: '=',
+            optDataKeys: '=',
             aliasController: '=',
             datakeySettingsSchema: '=',
             generateDataKey: '&',
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index 96e0387..5ab1dcd 100644
--- a/ui/src/app/components/datasource-func.directive.js
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -63,10 +63,14 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
                 var dataValid = angular.isDefined(value) && value != null;
                 ngModelCtrl.$setValidity('deviceData', dataValid);
                 if (dataValid) {
-                    ngModelCtrl.$setValidity('datasourceKeys',
-                        angular.isDefined(value.dataKeys) &&
-                        value.dataKeys != null &&
-                        value.dataKeys.length > 0);
+                    if (scope.optDataKeys) {
+                        ngModelCtrl.$setValidity('datasourceKeys', true);
+                    } else {
+                        ngModelCtrl.$setValidity('datasourceKeys',
+                            angular.isDefined(value.dataKeys) &&
+                            value.dataKeys != null &&
+                            value.dataKeys.length > 0);
+                    }
                 }
             }
         };
@@ -222,6 +226,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
         scope: {
             widgetType: '=',
             maxDataKeys: '=',
+            optDataKeys: '=',
             generateDataKey: '&',
             datakeySettingsSchema: '='
         },
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.controller.js b/ui/src/app/components/widget/action/widget-action-dialog.controller.js
index 99a2f0b..63aa128 100644
--- a/ui/src/app/components/widget/action/widget-action-dialog.controller.js
+++ b/ui/src/app/components/widget/action/widget-action-dialog.controller.js
@@ -145,11 +145,13 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
                 result.targetDashboardStateId = action.targetDashboardStateId;
                 result.openRightLayout = action.openRightLayout;
                 result.setEntityId = action.setEntityId;
+                result.stateEntityParamName = action.stateEntityParamName;
                 break;
             case vm.types.widgetActionTypes.openDashboard.value:
                 result.targetDashboardId = action.targetDashboardId;
                 result.targetDashboardStateId = action.targetDashboardStateId;
                 result.setEntityId = action.setEntityId;
+                result.stateEntityParamName = action.stateEntityParamName;
                 break;
             case vm.types.widgetActionTypes.custom.value:
                 result.customFunction = action.customFunction;
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.tpl.html b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
index 7a20e74..ded4c96 100644
--- a/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
+++ b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
@@ -104,12 +104,20 @@
                                      flex aria-label="{{ 'widget-action.open-right-layout' | translate }}"
                                      ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }}
                         </md-checkbox>
-                        <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
-                                            vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
-                                            vm.action.type == vm.types.widgetActionTypes.openDashboard.value"
-                                     flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"
-                                     ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}
-                        </md-checkbox>
+                        <div flex layout="column" ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
+                                                vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
+                                                vm.action.type == vm.types.widgetActionTypes.openDashboard.value">
+                            <md-checkbox flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"
+                                         ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}
+                            </md-checkbox>
+                            <md-input-container ng-if="vm.action.setEntityId" class="md-block">
+                                <label translate>alias.state-entity-parameter-name</label>
+                                <input name="stateEntityParamName"
+                                       placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
+                                       ng-model="vm.action.stateEntityParamName"
+                                       aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
+                            </md-input-container>
+                        </div>
                         <tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value"
                                     ng-model="vm.action.customFunction"
                                     function-args="{{ ['$event', 'widgetContext', 'entityId'] }}"
diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js
index 113399a..4e85718 100644
--- a/ui/src/app/components/widget/widget.controller.js
+++ b/ui/src/app/components/widget/widget.controller.js
@@ -421,23 +421,41 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
         return result;
     }
 
+    function updateEntityParams(params, targetEntityParamName, targetEntityId, entityName) {
+        if (targetEntityId) {
+            var targetEntityParams;
+            if (targetEntityParamName && targetEntityParamName.length) {
+                targetEntityParams = params[targetEntityParamName];
+                if (!targetEntityParams) {
+                    targetEntityParams = {};
+                    params[targetEntityParamName] = targetEntityParams;
+                }
+            } else {
+                targetEntityParams = params;
+            }
+            targetEntityParams.entityId = targetEntityId;
+            if (entityName) {
+                targetEntityParams.entityName = entityName;
+            }
+        }
+    }
+
     function handleWidgetAction($event, descriptor, entityId, entityName) {
         var type = descriptor.type;
+        var targetEntityParamName = descriptor.stateEntityParamName;
+        var targetEntityId;
+        if (descriptor.setEntityId) {
+            targetEntityId = entityId;
+        }
         switch (type) {
             case types.widgetActionTypes.openDashboardState.value:
             case types.widgetActionTypes.updateDashboardState.value:
                 var targetDashboardStateId = descriptor.targetDashboardStateId;
-                var targetEntityId;
-                if (descriptor.setEntityId) {
-                    targetEntityId = entityId;
-                }
-                var params = {};
-                if (targetEntityId) {
-                    params.entityId = targetEntityId;
-                    if (entityName) {
-                        params.entityName = entityName;
-                    }
+                var params = angular.copy(widgetContext.stateController.getStateParams());
+                if (!params) {
+                    params = {};
                 }
+                updateEntityParams(params, targetEntityParamName, targetEntityId, entityName);
                 if (type == types.widgetActionTypes.openDashboardState.value) {
                     widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
                 } else {
@@ -447,18 +465,9 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
             case types.widgetActionTypes.openDashboard.value:
                 var targetDashboardId = descriptor.targetDashboardId;
                 targetDashboardStateId = descriptor.targetDashboardStateId;
-                targetEntityId;
-                if (descriptor.setEntityId) {
-                    targetEntityId = entityId;
-                }
                 var stateObject = {};
                 stateObject.params = {};
-                if (targetEntityId) {
-                    stateObject.params.entityId = targetEntityId;
-                    if (entityName) {
-                        stateObject.params.entityName = entityName;
-                    }
-                }
+                updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName);
                 if (targetDashboardStateId) {
                     stateObject.id = targetDashboardStateId;
                 }
diff --git a/ui/src/app/components/widget/widget-config.tpl.html b/ui/src/app/components/widget/widget-config.tpl.html
index 6851ee8..d5337ef 100644
--- a/ui/src/app/components/widget/widget-config.tpl.html
+++ b/ui/src/app/components/widget/widget-config.tpl.html
@@ -96,6 +96,7 @@
                                         <tb-datasource flex ng-model="datasource.value"
                                                        widget-type="widgetType"
                                                        max-data-keys="typeParameters.maxDataKeys"
+                                                       opt-data-keys="typeParameters.dataKeysOptional"
                                                        alias-controller="aliasController"
                                                        functions-only="functionsOnly"
                                                        datakey-settings-schema="datakeySettingsSchema"
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index 749b7d6..c69c6ca 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -25,6 +25,8 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
     vm.imageAdded = imageAdded;
     vm.clearImage = clearImage;
 
+    vm.stateControllerIdChanged = stateControllerIdChanged;
+
     vm.settings = settings;
     vm.gridSettings = gridSettings;
     vm.stateControllers = statesControllerService.getStateControllers();
@@ -98,6 +100,12 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
         vm.gridSettings.backgroundImageUrl = null;
     }
 
+    function stateControllerIdChanged() {
+        if (vm.settings.stateControllerId != 'default') {
+            vm.settings.toolbarAlwaysOpen = true;
+        }
+    }
+
     function save() {
         $scope.theForm.$setPristine();
         if (vm.gridSettings) {
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index 0d69287..38e2af3 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -34,7 +34,8 @@
                     <div ng-show="vm.settings">
                        <md-input-container class="md-block">
                             <label translate>dashboard.state-controller</label>
-                            <md-select aria-label="{{ 'dashboard.state-controller' | translate }}" ng-model="vm.settings.stateControllerId">
+                            <md-select aria-label="{{ 'dashboard.state-controller' | translate }}"
+                                       ng-model="vm.settings.stateControllerId" ng-change="vm.stateControllerIdChanged()">
                                 <md-option ng-repeat="(stateControllerId, stateController) in vm.stateControllers" ng-value="stateControllerId">
                                     {{stateControllerId}}
                                 </md-option>
diff --git a/ui/src/app/dashboard/states/default-state-controller.js b/ui/src/app/dashboard/states/default-state-controller.js
index d39478f..ae92d37 100644
--- a/ui/src/app/dashboard/states/default-state-controller.js
+++ b/ui/src/app/dashboard/states/default-state-controller.js
@@ -27,6 +27,7 @@ export default function DefaultStateController($scope, $location, $state, $state
     vm.getStateId = getStateId;
     vm.getStateParams = getStateParams;
     vm.getStateParamsByStateId = getStateParamsByStateId;
+    vm.getEntityId = getEntityId;
 
     vm.getStateName = getStateName;
 
@@ -103,6 +104,10 @@ export default function DefaultStateController($scope, $location, $state, $state
         }
     }
 
+    function getEntityId() {
+        return null;
+    }
+
     function getStateObjById(id) {
         for (var i=0; i < vm.stateObject.length; i++) {
             if (vm.stateObject[i].id === id) {
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
index 4510449..cbe91fa 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.js
+++ b/ui/src/app/dashboard/states/entity-state-controller.js
@@ -29,6 +29,7 @@ export default function EntityStateController($scope, $location, $state, $stateP
     vm.getStateId = getStateId;
     vm.getStateParams = getStateParams;
     vm.getStateParamsByStateId = getStateParamsByStateId;
+    vm.getEntityId = getEntityId;
 
     vm.getStateName = getStateName;
 
@@ -111,6 +112,16 @@ export default function EntityStateController($scope, $location, $state, $stateP
         }
     }
 
+    function getEntityId(entityParamName) {
+        var stateParams = getStateParams();
+        if (!entityParamName || !entityParamName.length) {
+            return stateParams.entityId;
+        } else if (stateParams[entityParamName]) {
+            return stateParams[entityParamName].entityId;
+        }
+        return null;
+    }
+
     function getStateObjById(id) {
         for (var i=0; i < vm.stateObject.length; i++) {
             if (vm.stateObject[i].id === id) {
@@ -135,15 +146,22 @@ export default function EntityStateController($scope, $location, $state, $stateP
     function resolveEntity(params) {
         var deferred = $q.defer();
         if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
-            entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(
-                function success(entity) {
-                    var entityName = entity.name;
-                    deferred.resolve(entityName);
-                },
-                function fail() {
-                    deferred.reject();
-                }
-            );
+            if (params.entityName && params.entityName.length) {
+                deferred.resolve(params.entityName);
+            } else {
+                entityService.getEntity(params.entityId.entityType, params.entityId.id, {
+                    ignoreLoading: true,
+                    ignoreErrors: true
+                }).then(
+                    function success(entity) {
+                        var entityName = entity.name;
+                        deferred.resolve(entityName);
+                    },
+                    function fail() {
+                        deferred.reject();
+                    }
+                );
+            }
         } else {
             deferred.reject();
         }
diff --git a/ui/src/app/dashboard/states/states-component.directive.js b/ui/src/app/dashboard/states/states-component.directive.js
index 538c3e9..9af5122 100644
--- a/ui/src/app/dashboard/states/states-component.directive.js
+++ b/ui/src/app/dashboard/states/states-component.directive.js
@@ -70,6 +70,15 @@ export default function StatesComponent($compile, $templateCache, $controller, s
                     return null;
                 }
             }
+
+            stateController.getEntityId = function(entityParamName) {
+                if (scope.statesController) {
+                    return scope.statesController.getEntityId(entityParamName);
+                } else {
+                    return null;
+                }
+            }
+
         }
 
         scope.$on('$destroy', function callOnDestroyHook() {
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index b81f369..db7a744 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -54,6 +54,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                     filter.entityNameFilter = '';
                     break;
                 case types.aliasFilterType.stateEntity.value:
+                    filter.stateEntityParamName = null;
                     break;
                 case types.aliasFilterType.assetType.value:
                     filter.assetType = null;
@@ -67,6 +68,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                 case types.aliasFilterType.assetSearchQuery.value:
                 case types.aliasFilterType.deviceSearchQuery.value:
                     filter.rootStateEntity = false;
+                    filter.stateEntityParamName = 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 4f5af00..dae2817 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -59,6 +59,13 @@
         </md-input-container>
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
+        <md-input-container 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>
     </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
         <tb-entity-subtype-autocomplete
@@ -97,12 +104,21 @@
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <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>
+            <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>
         <div flex layout="row">
             <md-input-container class="md-block" style="min-width: 100px;">
@@ -139,12 +155,21 @@
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <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>
+            <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>
         <div flex layout="row">
             <md-input-container class="md-block" style="min-width: 100px;">
@@ -189,12 +214,21 @@
                               ng-disabled="filter.rootStateEntity"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
-            <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>
+            <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>
         <div flex layout="row">
             <md-input-container class="md-block" style="min-width: 100px;">
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index f1f8543..2a33d96 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -187,6 +187,8 @@ export default angular.module('thingsboard.locale', [])
                     "no-entity-filter-specified": "No entity filter specified",
                     "root-state-entity": "Use dashboard state entity as root",
                     "root-entity": "Root entity",
+                    "state-entity-parameter-name": "State entity parameter name",
+                    "default-entity-parameter-name": "By default",
                     "max-relation-level": "Max relation level",
                     "unlimited-level": "Unlimited level",
                     "state-entity": "Dashboard state entity",
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index 2f80037..f0b5835 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -479,6 +479,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
         for (var i=0;i<vm.datasources.length;i++) {
             datasource = vm.datasources[i];
+            if (datasource.type == types.datasourceType.entity && !datasource.entityId) {
+                continue;
+            }
             var entity = {
                 id: {}
             };