Commit 81ccdf7bfafd486ebd00cf619cdb6b5c4818cead

Authored by Igor Kulikov
1 parent 983ba89a

TB-70: Improve dashboards states management.

@@ -26,12 +26,12 @@ @@ -26,12 +26,12 @@
26 "name": "Entities table", 26 "name": "Entities table",
27 "descriptor": { 27 "descriptor": {
28 "type": "latest", 28 "type": "latest",
29 - "sizeX": 10.5,  
30 - "sizeY": 6.5, 29 + "sizeX": 7.5,
  30 + "sizeY": 4.5,
31 "resources": [], 31 "resources": [],
32 "templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>", 32 "templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>",
33 "templateCss": "", 33 "templateCss": "",
34 - "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", 34 + "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",
35 "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}", 35 "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}",
36 "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}", 36 "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}",
37 "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;\"}]}]}" 37 "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;\"}]}]}"
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 "resources": [], 15 "resources": [],
16 "templateHtml": "<div style=\"height: 100%;\" id=\"device-terminal\"></div>", 16 "templateHtml": "<div style=\"height: 100%;\" id=\"device-terminal\"></div>",
17 "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", 17 "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",
18 - "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", 18 + "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",
19 "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}", 19 "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}",
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "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\":{}}" 21 "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\":{}}"
@@ -53,10 +53,11 @@ export default class AliasController { @@ -53,10 +53,11 @@ export default class AliasController {
53 } 53 }
54 54
55 dashboardStateChanged() { 55 dashboardStateChanged() {
56 - var newEntityId = this.stateController.getStateParams().entityId;  
57 var changedAliasIds = []; 56 var changedAliasIds = [];
58 for (var aliasId in this.resolvedAliasesToStateEntities) { 57 for (var aliasId in this.resolvedAliasesToStateEntities) {
59 - var prevEntityId = this.resolvedAliasesToStateEntities[aliasId]; 58 + var stateEntityInfo = this.resolvedAliasesToStateEntities[aliasId];
  59 + var newEntityId = this.stateController.getEntityId(stateEntityInfo.entityParamName);
  60 + var prevEntityId = stateEntityInfo.entityId;
60 if (!angular.equals(newEntityId, prevEntityId)) { 61 if (!angular.equals(newEntityId, prevEntityId)) {
61 changedAliasIds.push(aliasId); 62 changedAliasIds.push(aliasId);
62 this.setAliasUnresolved(aliasId); 63 this.setAliasUnresolved(aliasId);
@@ -93,19 +94,26 @@ export default class AliasController { @@ -93,19 +94,26 @@ export default class AliasController {
93 this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then( 94 this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
94 function success(aliasInfo) { 95 function success(aliasInfo) {
95 aliasCtrl.resolvedAliases[aliasId] = aliasInfo; 96 aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
  97 + delete aliasCtrl.resolvedAliasesPromise[aliasId];
96 if (aliasInfo.stateEntity) { 98 if (aliasInfo.stateEntity) {
  99 + var stateEntityInfo = {
  100 + entityParamName: aliasInfo.entityParamName,
  101 + entityId: aliasCtrl.stateController.getEntityId(aliasInfo.entityParamName)
  102 + };
97 aliasCtrl.resolvedAliasesToStateEntities[aliasId] = 103 aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
98 - aliasCtrl.stateController.getStateParams().entityId; 104 + stateEntityInfo;
99 } 105 }
100 aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId); 106 aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
101 deferred.resolve(aliasInfo); 107 deferred.resolve(aliasInfo);
102 }, 108 },
103 function fail() { 109 function fail() {
104 deferred.reject(); 110 deferred.reject();
  111 + delete aliasCtrl.resolvedAliasesPromise[aliasId];
105 } 112 }
106 ); 113 );
107 } else { 114 } else {
108 deferred.reject(); 115 deferred.reject();
  116 + delete aliasCtrl.resolvedAliasesPromise[aliasId];
109 } 117 }
110 return this.resolvedAliasesPromise[aliasId]; 118 return this.resolvedAliasesPromise[aliasId];
111 } 119 }
@@ -376,6 +376,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -376,6 +376,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
376 var aliasInfo = { 376 var aliasInfo = {
377 alias: entityAlias.alias, 377 alias: entityAlias.alias,
378 stateEntity: result.stateEntity, 378 stateEntity: result.stateEntity,
  379 + entityParamName: result.entityParamName,
379 resolveMultiple: filter.resolveMultiple 380 resolveMultiple: filter.resolveMultiple
380 }; 381 };
381 aliasInfo.resolvedEntities = result.entities; 382 aliasInfo.resolvedEntities = result.entities;
@@ -392,12 +393,30 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -392,12 +393,30 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
392 return deferred.promise; 393 return deferred.promise;
393 } 394 }
394 395
  396 + function getStateEntityId(filter, stateParams) {
  397 + var entityId = null;
  398 + if (stateParams) {
  399 + if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
  400 + if (stateParams[filter.stateEntityParamName]) {
  401 + entityId = stateParams[filter.stateEntityParamName].entityId;
  402 + }
  403 + } else {
  404 + entityId = stateParams.entityId;
  405 + }
  406 + }
  407 + return entityId;
  408 + }
  409 +
395 function resolveAliasFilter(filter, stateParams, maxItems) { 410 function resolveAliasFilter(filter, stateParams, maxItems) {
396 var deferred = $q.defer(); 411 var deferred = $q.defer();
397 var result = { 412 var result = {
398 entities: [], 413 entities: [],
399 stateEntity: false 414 stateEntity: false
400 }; 415 };
  416 + if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
  417 + result.entityParamName = filter.stateEntityParamName;
  418 + }
  419 + var stateEntityId = getStateEntityId(filter, stateParams);
401 switch (filter.type) { 420 switch (filter.type) {
402 case types.aliasFilterType.entityList.value: 421 case types.aliasFilterType.entityList.value:
403 getEntities(filter.entityType, filter.entityList).then( 422 getEntities(filter.entityType, filter.entityList).then(
@@ -431,8 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -431,8 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
431 break; 450 break;
432 case types.aliasFilterType.stateEntity.value: 451 case types.aliasFilterType.stateEntity.value:
433 result.stateEntity = true; 452 result.stateEntity = true;
434 - if (stateParams && stateParams.entityId) {  
435 - getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( 453 + if (stateEntityId) {
  454 + getEntity(stateEntityId.entityType, stateEntityId.id).then(
436 function success(entity) { 455 function success(entity) {
437 result.entities = entitiesToEntitiesInfo([entity]); 456 result.entities = entitiesToEntitiesInfo([entity]);
438 deferred.resolve(result); 457 deferred.resolve(result);
@@ -479,9 +498,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -479,9 +498,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
479 result.stateEntity = filter.rootStateEntity; 498 result.stateEntity = filter.rootStateEntity;
480 var rootEntityType; 499 var rootEntityType;
481 var rootEntityId; 500 var rootEntityId;
482 - if (result.stateEntity && stateParams && stateParams.entityId) {  
483 - rootEntityType = stateParams.entityId.entityType;  
484 - rootEntityId = stateParams.entityId.id; 501 + if (result.stateEntity && stateEntityId) {
  502 + rootEntityType = stateEntityId.entityType;
  503 + rootEntityId = stateEntityId.id;
485 } else if (!result.stateEntity) { 504 } else if (!result.stateEntity) {
486 rootEntityType = filter.rootEntity.entityType; 505 rootEntityType = filter.rootEntity.entityType;
487 rootEntityId = filter.rootEntity.id; 506 rootEntityId = filter.rootEntity.id;
@@ -520,9 +539,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -520,9 +539,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
520 case types.aliasFilterType.assetSearchQuery.value: 539 case types.aliasFilterType.assetSearchQuery.value:
521 case types.aliasFilterType.deviceSearchQuery.value: 540 case types.aliasFilterType.deviceSearchQuery.value:
522 result.stateEntity = filter.rootStateEntity; 541 result.stateEntity = filter.rootStateEntity;
523 - if (result.stateEntity && stateParams && stateParams.entityId) {  
524 - rootEntityType = stateParams.entityId.entityType;  
525 - rootEntityId = stateParams.entityId.id; 542 + if (result.stateEntity && stateEntityId) {
  543 + rootEntityType = stateEntityId.entityType;
  544 + rootEntityId = stateEntityId.id;
526 } else if (!result.stateEntity) { 545 } else if (!result.stateEntity) {
527 rootEntityType = filter.rootEntity.entityType; 546 rootEntityType = filter.rootEntity.entityType;
528 rootEntityId = filter.rootEntity.id; 547 rootEntityId = filter.rootEntity.id;
@@ -730,12 +730,15 @@ export default class Subscription { @@ -730,12 +730,15 @@ export default class Subscription {
730 index += datasource.dataKeys.length; 730 index += datasource.dataKeys.length;
731 731
732 this.datasourceListeners.push(listener); 732 this.datasourceListeners.push(listener);
733 - this.ctx.datasourceService.subscribeToDatasource(listener);  
734 - if (datasource.unresolvedStateEntity) { 733 +
  734 + if (datasource.dataKeys.length) {
  735 + this.ctx.datasourceService.subscribeToDatasource(listener);
  736 + }
  737 +
  738 + if (datasource.unresolvedStateEntity || !datasource.dataKeys.length) {
735 this.notifyDataLoaded(); 739 this.notifyDataLoaded();
736 this.onDataUpdated(); 740 this.onDataUpdated();
737 } 741 }
738 -  
739 } 742 }
740 } 743 }
741 } 744 }
@@ -556,8 +556,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr @@ -556,8 +556,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
556 ' self.typeParameters = function() {\n\n' + 556 ' self.typeParameters = function() {\n\n' +
557 return { 557 return {
558 useCustomDatasources: false, 558 useCustomDatasources: false,
559 - maxDatasources: -1 //unlimited  
560 - maxDataKeys: -1 //unlimited 559 + maxDatasources: -1, //unlimited
  560 + maxDataKeys: -1, //unlimited
  561 + dataKeysOptional: false
561 }; 562 };
562 ' }\n\n' + 563 ' }\n\n' +
563 564
@@ -625,6 +626,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr @@ -625,6 +626,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
625 if (angular.isUndefined(result.typeParameters.maxDataKeys)) { 626 if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
626 result.typeParameters.maxDataKeys = -1; 627 result.typeParameters.maxDataKeys = -1;
627 } 628 }
  629 + if (angular.isUndefined(result.typeParameters.dataKeysOptional)) {
  630 + result.typeParameters.dataKeysOptional = false;
  631 + }
628 if (angular.isFunction(widgetTypeInstance.actionSources)) { 632 if (angular.isFunction(widgetTypeInstance.actionSources)) {
629 result.actionSources = widgetTypeInstance.actionSources(); 633 result.actionSources = widgetTypeInstance.actionSources();
630 } else { 634 } else {
@@ -68,10 +68,14 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -68,10 +68,14 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
68 ngModelCtrl.$setValidity('entityAlias', 68 ngModelCtrl.$setValidity('entityAlias',
69 angular.isDefined(value.entityAliasId) && 69 angular.isDefined(value.entityAliasId) &&
70 value.entityAliasId != null); 70 value.entityAliasId != null);
71 - ngModelCtrl.$setValidity('entityKeys',  
72 - angular.isDefined(value.dataKeys) &&  
73 - value.dataKeys != null &&  
74 - value.dataKeys.length > 0); 71 + if (scope.optDataKeys) {
  72 + ngModelCtrl.$setValidity('entityKeys', true);
  73 + } else {
  74 + ngModelCtrl.$setValidity('entityKeys',
  75 + angular.isDefined(value.dataKeys) &&
  76 + value.dataKeys != null &&
  77 + value.dataKeys.length > 0);
  78 + }
75 } 79 }
76 } 80 }
77 }; 81 };
@@ -281,6 +285,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -281,6 +285,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
281 scope: { 285 scope: {
282 widgetType: '=', 286 widgetType: '=',
283 maxDataKeys: '=', 287 maxDataKeys: '=',
  288 + optDataKeys: '=',
284 aliasController: '=', 289 aliasController: '=',
285 datakeySettingsSchema: '=', 290 datakeySettingsSchema: '=',
286 generateDataKey: '&', 291 generateDataKey: '&',
@@ -63,10 +63,14 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -63,10 +63,14 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
63 var dataValid = angular.isDefined(value) && value != null; 63 var dataValid = angular.isDefined(value) && value != null;
64 ngModelCtrl.$setValidity('deviceData', dataValid); 64 ngModelCtrl.$setValidity('deviceData', dataValid);
65 if (dataValid) { 65 if (dataValid) {
66 - ngModelCtrl.$setValidity('datasourceKeys',  
67 - angular.isDefined(value.dataKeys) &&  
68 - value.dataKeys != null &&  
69 - value.dataKeys.length > 0); 66 + if (scope.optDataKeys) {
  67 + ngModelCtrl.$setValidity('datasourceKeys', true);
  68 + } else {
  69 + ngModelCtrl.$setValidity('datasourceKeys',
  70 + angular.isDefined(value.dataKeys) &&
  71 + value.dataKeys != null &&
  72 + value.dataKeys.length > 0);
  73 + }
70 } 74 }
71 } 75 }
72 }; 76 };
@@ -222,6 +226,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -222,6 +226,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
222 scope: { 226 scope: {
223 widgetType: '=', 227 widgetType: '=',
224 maxDataKeys: '=', 228 maxDataKeys: '=',
  229 + optDataKeys: '=',
225 generateDataKey: '&', 230 generateDataKey: '&',
226 datakeySettingsSchema: '=' 231 datakeySettingsSchema: '='
227 }, 232 },
@@ -83,6 +83,7 @@ function Datasource($compile, $templateCache, utils, types) { @@ -83,6 +83,7 @@ function Datasource($compile, $templateCache, utils, types) {
83 scope: { 83 scope: {
84 aliasController: '=', 84 aliasController: '=',
85 maxDataKeys: '=', 85 maxDataKeys: '=',
  86 + optDataKeys: '=',
86 widgetType: '=', 87 widgetType: '=',
87 functionsOnly: '=', 88 functionsOnly: '=',
88 datakeySettingsSchema: '=', 89 datakeySettingsSchema: '=',
@@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
28 ng-switch-default 28 ng-switch-default
29 ng-model="model" 29 ng-model="model"
30 max-data-keys="maxDataKeys" 30 max-data-keys="maxDataKeys"
  31 + opt-data-keys="optDataKeys"
31 datakey-settings-schema="datakeySettingsSchema" 32 datakey-settings-schema="datakeySettingsSchema"
32 ng-required="model.type === types.datasourceType.function" 33 ng-required="model.type === types.datasourceType.function"
33 widget-type="widgetType" 34 widget-type="widgetType"
@@ -36,6 +37,7 @@ @@ -36,6 +37,7 @@
36 <tb-datasource-entity flex 37 <tb-datasource-entity flex
37 ng-model="model" 38 ng-model="model"
38 max-data-keys="maxDataKeys" 39 max-data-keys="maxDataKeys"
  40 + opt-data-keys="optDataKeys"
39 datakey-settings-schema="datakeySettingsSchema" 41 datakey-settings-schema="datakeySettingsSchema"
40 ng-switch-when="entity" 42 ng-switch-when="entity"
41 ng-required="model.type === types.datasourceType.entity" 43 ng-required="model.type === types.datasourceType.entity"
@@ -145,11 +145,13 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter, @@ -145,11 +145,13 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
145 result.targetDashboardStateId = action.targetDashboardStateId; 145 result.targetDashboardStateId = action.targetDashboardStateId;
146 result.openRightLayout = action.openRightLayout; 146 result.openRightLayout = action.openRightLayout;
147 result.setEntityId = action.setEntityId; 147 result.setEntityId = action.setEntityId;
  148 + result.stateEntityParamName = action.stateEntityParamName;
148 break; 149 break;
149 case vm.types.widgetActionTypes.openDashboard.value: 150 case vm.types.widgetActionTypes.openDashboard.value:
150 result.targetDashboardId = action.targetDashboardId; 151 result.targetDashboardId = action.targetDashboardId;
151 result.targetDashboardStateId = action.targetDashboardStateId; 152 result.targetDashboardStateId = action.targetDashboardStateId;
152 result.setEntityId = action.setEntityId; 153 result.setEntityId = action.setEntityId;
  154 + result.stateEntityParamName = action.stateEntityParamName;
153 break; 155 break;
154 case vm.types.widgetActionTypes.custom.value: 156 case vm.types.widgetActionTypes.custom.value:
155 result.customFunction = action.customFunction; 157 result.customFunction = action.customFunction;
@@ -104,12 +104,20 @@ @@ -104,12 +104,20 @@
104 flex aria-label="{{ 'widget-action.open-right-layout' | translate }}" 104 flex aria-label="{{ 'widget-action.open-right-layout' | translate }}"
105 ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }} 105 ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }}
106 </md-checkbox> 106 </md-checkbox>
107 - <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||  
108 - vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||  
109 - vm.action.type == vm.types.widgetActionTypes.openDashboard.value"  
110 - flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"  
111 - ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}  
112 - </md-checkbox> 107 + <div flex layout="column" ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
  108 + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
  109 + vm.action.type == vm.types.widgetActionTypes.openDashboard.value">
  110 + <md-checkbox flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"
  111 + ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}
  112 + </md-checkbox>
  113 + <md-input-container ng-if="vm.action.setEntityId" class="md-block">
  114 + <label translate>alias.state-entity-parameter-name</label>
  115 + <input name="stateEntityParamName"
  116 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  117 + ng-model="vm.action.stateEntityParamName"
  118 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  119 + </md-input-container>
  120 + </div>
113 <tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value" 121 <tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value"
114 ng-model="vm.action.customFunction" 122 ng-model="vm.action.customFunction"
115 function-args="{{ ['$event', 'widgetContext', 'entityId'] }}" 123 function-args="{{ ['$event', 'widgetContext', 'entityId'] }}"
@@ -96,6 +96,7 @@ @@ -96,6 +96,7 @@
96 <tb-datasource flex ng-model="datasource.value" 96 <tb-datasource flex ng-model="datasource.value"
97 widget-type="widgetType" 97 widget-type="widgetType"
98 max-data-keys="typeParameters.maxDataKeys" 98 max-data-keys="typeParameters.maxDataKeys"
  99 + opt-data-keys="typeParameters.dataKeysOptional"
99 alias-controller="aliasController" 100 alias-controller="aliasController"
100 functions-only="functionsOnly" 101 functions-only="functionsOnly"
101 datakey-settings-schema="datakeySettingsSchema" 102 datakey-settings-schema="datakeySettingsSchema"
@@ -421,23 +421,41 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele @@ -421,23 +421,41 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
421 return result; 421 return result;
422 } 422 }
423 423
  424 + function updateEntityParams(params, targetEntityParamName, targetEntityId, entityName) {
  425 + if (targetEntityId) {
  426 + var targetEntityParams;
  427 + if (targetEntityParamName && targetEntityParamName.length) {
  428 + targetEntityParams = params[targetEntityParamName];
  429 + if (!targetEntityParams) {
  430 + targetEntityParams = {};
  431 + params[targetEntityParamName] = targetEntityParams;
  432 + }
  433 + } else {
  434 + targetEntityParams = params;
  435 + }
  436 + targetEntityParams.entityId = targetEntityId;
  437 + if (entityName) {
  438 + targetEntityParams.entityName = entityName;
  439 + }
  440 + }
  441 + }
  442 +
424 function handleWidgetAction($event, descriptor, entityId, entityName) { 443 function handleWidgetAction($event, descriptor, entityId, entityName) {
425 var type = descriptor.type; 444 var type = descriptor.type;
  445 + var targetEntityParamName = descriptor.stateEntityParamName;
  446 + var targetEntityId;
  447 + if (descriptor.setEntityId) {
  448 + targetEntityId = entityId;
  449 + }
426 switch (type) { 450 switch (type) {
427 case types.widgetActionTypes.openDashboardState.value: 451 case types.widgetActionTypes.openDashboardState.value:
428 case types.widgetActionTypes.updateDashboardState.value: 452 case types.widgetActionTypes.updateDashboardState.value:
429 var targetDashboardStateId = descriptor.targetDashboardStateId; 453 var targetDashboardStateId = descriptor.targetDashboardStateId;
430 - var targetEntityId;  
431 - if (descriptor.setEntityId) {  
432 - targetEntityId = entityId;  
433 - }  
434 - var params = {};  
435 - if (targetEntityId) {  
436 - params.entityId = targetEntityId;  
437 - if (entityName) {  
438 - params.entityName = entityName;  
439 - } 454 + var params = angular.copy(widgetContext.stateController.getStateParams());
  455 + if (!params) {
  456 + params = {};
440 } 457 }
  458 + updateEntityParams(params, targetEntityParamName, targetEntityId, entityName);
441 if (type == types.widgetActionTypes.openDashboardState.value) { 459 if (type == types.widgetActionTypes.openDashboardState.value) {
442 widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); 460 widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
443 } else { 461 } else {
@@ -447,18 +465,9 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele @@ -447,18 +465,9 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
447 case types.widgetActionTypes.openDashboard.value: 465 case types.widgetActionTypes.openDashboard.value:
448 var targetDashboardId = descriptor.targetDashboardId; 466 var targetDashboardId = descriptor.targetDashboardId;
449 targetDashboardStateId = descriptor.targetDashboardStateId; 467 targetDashboardStateId = descriptor.targetDashboardStateId;
450 - targetEntityId;  
451 - if (descriptor.setEntityId) {  
452 - targetEntityId = entityId;  
453 - }  
454 var stateObject = {}; 468 var stateObject = {};
455 stateObject.params = {}; 469 stateObject.params = {};
456 - if (targetEntityId) {  
457 - stateObject.params.entityId = targetEntityId;  
458 - if (entityName) {  
459 - stateObject.params.entityName = entityName;  
460 - }  
461 - } 470 + updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName);
462 if (targetDashboardStateId) { 471 if (targetDashboardStateId) {
463 stateObject.id = targetDashboardStateId; 472 stateObject.id = targetDashboardStateId;
464 } 473 }
@@ -25,6 +25,8 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon @@ -25,6 +25,8 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
25 vm.imageAdded = imageAdded; 25 vm.imageAdded = imageAdded;
26 vm.clearImage = clearImage; 26 vm.clearImage = clearImage;
27 27
  28 + vm.stateControllerIdChanged = stateControllerIdChanged;
  29 +
28 vm.settings = settings; 30 vm.settings = settings;
29 vm.gridSettings = gridSettings; 31 vm.gridSettings = gridSettings;
30 vm.stateControllers = statesControllerService.getStateControllers(); 32 vm.stateControllers = statesControllerService.getStateControllers();
@@ -98,6 +100,12 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon @@ -98,6 +100,12 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
98 vm.gridSettings.backgroundImageUrl = null; 100 vm.gridSettings.backgroundImageUrl = null;
99 } 101 }
100 102
  103 + function stateControllerIdChanged() {
  104 + if (vm.settings.stateControllerId != 'default') {
  105 + vm.settings.toolbarAlwaysOpen = true;
  106 + }
  107 + }
  108 +
101 function save() { 109 function save() {
102 $scope.theForm.$setPristine(); 110 $scope.theForm.$setPristine();
103 if (vm.gridSettings) { 111 if (vm.gridSettings) {
@@ -34,7 +34,8 @@ @@ -34,7 +34,8 @@
34 <div ng-show="vm.settings"> 34 <div ng-show="vm.settings">
35 <md-input-container class="md-block"> 35 <md-input-container class="md-block">
36 <label translate>dashboard.state-controller</label> 36 <label translate>dashboard.state-controller</label>
37 - <md-select aria-label="{{ 'dashboard.state-controller' | translate }}" ng-model="vm.settings.stateControllerId"> 37 + <md-select aria-label="{{ 'dashboard.state-controller' | translate }}"
  38 + ng-model="vm.settings.stateControllerId" ng-change="vm.stateControllerIdChanged()">
38 <md-option ng-repeat="(stateControllerId, stateController) in vm.stateControllers" ng-value="stateControllerId"> 39 <md-option ng-repeat="(stateControllerId, stateController) in vm.stateControllers" ng-value="stateControllerId">
39 {{stateControllerId}} 40 {{stateControllerId}}
40 </md-option> 41 </md-option>
@@ -27,6 +27,7 @@ export default function DefaultStateController($scope, $location, $state, $state @@ -27,6 +27,7 @@ export default function DefaultStateController($scope, $location, $state, $state
27 vm.getStateId = getStateId; 27 vm.getStateId = getStateId;
28 vm.getStateParams = getStateParams; 28 vm.getStateParams = getStateParams;
29 vm.getStateParamsByStateId = getStateParamsByStateId; 29 vm.getStateParamsByStateId = getStateParamsByStateId;
  30 + vm.getEntityId = getEntityId;
30 31
31 vm.getStateName = getStateName; 32 vm.getStateName = getStateName;
32 33
@@ -103,6 +104,10 @@ export default function DefaultStateController($scope, $location, $state, $state @@ -103,6 +104,10 @@ export default function DefaultStateController($scope, $location, $state, $state
103 } 104 }
104 } 105 }
105 106
  107 + function getEntityId() {
  108 + return null;
  109 + }
  110 +
106 function getStateObjById(id) { 111 function getStateObjById(id) {
107 for (var i=0; i < vm.stateObject.length; i++) { 112 for (var i=0; i < vm.stateObject.length; i++) {
108 if (vm.stateObject[i].id === id) { 113 if (vm.stateObject[i].id === id) {
@@ -29,6 +29,7 @@ export default function EntityStateController($scope, $location, $state, $stateP @@ -29,6 +29,7 @@ export default function EntityStateController($scope, $location, $state, $stateP
29 vm.getStateId = getStateId; 29 vm.getStateId = getStateId;
30 vm.getStateParams = getStateParams; 30 vm.getStateParams = getStateParams;
31 vm.getStateParamsByStateId = getStateParamsByStateId; 31 vm.getStateParamsByStateId = getStateParamsByStateId;
  32 + vm.getEntityId = getEntityId;
32 33
33 vm.getStateName = getStateName; 34 vm.getStateName = getStateName;
34 35
@@ -111,6 +112,16 @@ export default function EntityStateController($scope, $location, $state, $stateP @@ -111,6 +112,16 @@ export default function EntityStateController($scope, $location, $state, $stateP
111 } 112 }
112 } 113 }
113 114
  115 + function getEntityId(entityParamName) {
  116 + var stateParams = getStateParams();
  117 + if (!entityParamName || !entityParamName.length) {
  118 + return stateParams.entityId;
  119 + } else if (stateParams[entityParamName]) {
  120 + return stateParams[entityParamName].entityId;
  121 + }
  122 + return null;
  123 + }
  124 +
114 function getStateObjById(id) { 125 function getStateObjById(id) {
115 for (var i=0; i < vm.stateObject.length; i++) { 126 for (var i=0; i < vm.stateObject.length; i++) {
116 if (vm.stateObject[i].id === id) { 127 if (vm.stateObject[i].id === id) {
@@ -135,15 +146,22 @@ export default function EntityStateController($scope, $location, $state, $stateP @@ -135,15 +146,22 @@ export default function EntityStateController($scope, $location, $state, $stateP
135 function resolveEntity(params) { 146 function resolveEntity(params) {
136 var deferred = $q.defer(); 147 var deferred = $q.defer();
137 if (params && params.entityId && params.entityId.id && params.entityId.entityType) { 148 if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
138 - entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(  
139 - function success(entity) {  
140 - var entityName = entity.name;  
141 - deferred.resolve(entityName);  
142 - },  
143 - function fail() {  
144 - deferred.reject();  
145 - }  
146 - ); 149 + if (params.entityName && params.entityName.length) {
  150 + deferred.resolve(params.entityName);
  151 + } else {
  152 + entityService.getEntity(params.entityId.entityType, params.entityId.id, {
  153 + ignoreLoading: true,
  154 + ignoreErrors: true
  155 + }).then(
  156 + function success(entity) {
  157 + var entityName = entity.name;
  158 + deferred.resolve(entityName);
  159 + },
  160 + function fail() {
  161 + deferred.reject();
  162 + }
  163 + );
  164 + }
147 } else { 165 } else {
148 deferred.reject(); 166 deferred.reject();
149 } 167 }
@@ -70,6 +70,15 @@ export default function StatesComponent($compile, $templateCache, $controller, s @@ -70,6 +70,15 @@ export default function StatesComponent($compile, $templateCache, $controller, s
70 return null; 70 return null;
71 } 71 }
72 } 72 }
  73 +
  74 + stateController.getEntityId = function(entityParamName) {
  75 + if (scope.statesController) {
  76 + return scope.statesController.getEntityId(entityParamName);
  77 + } else {
  78 + return null;
  79 + }
  80 + }
  81 +
73 } 82 }
74 83
75 scope.$on('$destroy', function callOnDestroyHook() { 84 scope.$on('$destroy', function callOnDestroyHook() {
@@ -54,6 +54,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc @@ -54,6 +54,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
54 filter.entityNameFilter = ''; 54 filter.entityNameFilter = '';
55 break; 55 break;
56 case types.aliasFilterType.stateEntity.value: 56 case types.aliasFilterType.stateEntity.value:
  57 + filter.stateEntityParamName = null;
57 break; 58 break;
58 case types.aliasFilterType.assetType.value: 59 case types.aliasFilterType.assetType.value:
59 filter.assetType = null; 60 filter.assetType = null;
@@ -67,6 +68,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc @@ -67,6 +68,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
67 case types.aliasFilterType.assetSearchQuery.value: 68 case types.aliasFilterType.assetSearchQuery.value:
68 case types.aliasFilterType.deviceSearchQuery.value: 69 case types.aliasFilterType.deviceSearchQuery.value:
69 filter.rootStateEntity = false; 70 filter.rootStateEntity = false;
  71 + filter.stateEntityParamName = null;
70 filter.rootEntity = null; 72 filter.rootEntity = null;
71 filter.direction = types.entitySearchDirection.from; 73 filter.direction = types.entitySearchDirection.from;
72 filter.maxLevel = 1; 74 filter.maxLevel = 1;
@@ -59,6 +59,13 @@ @@ -59,6 +59,13 @@
59 </md-input-container> 59 </md-input-container>
60 </section> 60 </section>
61 <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter"> 61 <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
  62 + <md-input-container class="md-block">
  63 + <label translate>alias.state-entity-parameter-name</label>
  64 + <input name="stateEntityParamName"
  65 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  66 + ng-model="filter.stateEntityParamName"
  67 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  68 + </md-input-container>
62 </section> 69 </section>
63 <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter"> 70 <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
64 <tb-entity-subtype-autocomplete 71 <tb-entity-subtype-autocomplete
@@ -97,12 +104,21 @@ @@ -97,12 +104,21 @@
97 ng-disabled="filter.rootStateEntity" 104 ng-disabled="filter.rootStateEntity"
98 ng-model="filter.rootEntity"> 105 ng-model="filter.rootEntity">
99 </tb-entity-select> 106 </tb-entity-select>
100 - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">  
101 - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>  
102 - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"  
103 - aria-label="{{ 'alias.root-state-entity' | translate }}">  
104 - </md-switch>  
105 - </section> 107 + <div layout="column">
  108 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  109 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  110 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  111 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  112 + </md-switch>
  113 + </section>
  114 + <md-input-container ng-if="filter.rootStateEntity" class="md-block">
  115 + <label translate>alias.state-entity-parameter-name</label>
  116 + <input name="stateEntityParamName"
  117 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  118 + ng-model="filter.stateEntityParamName"
  119 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  120 + </md-input-container>
  121 + </div>
106 </div> 122 </div>
107 <div flex layout="row"> 123 <div flex layout="row">
108 <md-input-container class="md-block" style="min-width: 100px;"> 124 <md-input-container class="md-block" style="min-width: 100px;">
@@ -139,12 +155,21 @@ @@ -139,12 +155,21 @@
139 ng-disabled="filter.rootStateEntity" 155 ng-disabled="filter.rootStateEntity"
140 ng-model="filter.rootEntity"> 156 ng-model="filter.rootEntity">
141 </tb-entity-select> 157 </tb-entity-select>
142 - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">  
143 - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>  
144 - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"  
145 - aria-label="{{ 'alias.root-state-entity' | translate }}">  
146 - </md-switch>  
147 - </section> 158 + <div layout="column">
  159 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  160 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  161 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  162 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  163 + </md-switch>
  164 + </section>
  165 + <md-input-container ng-if="filter.rootStateEntity" class="md-block">
  166 + <label translate>alias.state-entity-parameter-name</label>
  167 + <input name="stateEntityParamName"
  168 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  169 + ng-model="filter.stateEntityParamName"
  170 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  171 + </md-input-container>
  172 + </div>
148 </div> 173 </div>
149 <div flex layout="row"> 174 <div flex layout="row">
150 <md-input-container class="md-block" style="min-width: 100px;"> 175 <md-input-container class="md-block" style="min-width: 100px;">
@@ -189,12 +214,21 @@ @@ -189,12 +214,21 @@
189 ng-disabled="filter.rootStateEntity" 214 ng-disabled="filter.rootStateEntity"
190 ng-model="filter.rootEntity"> 215 ng-model="filter.rootEntity">
191 </tb-entity-select> 216 </tb-entity-select>
192 - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">  
193 - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>  
194 - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"  
195 - aria-label="{{ 'alias.root-state-entity' | translate }}">  
196 - </md-switch>  
197 - </section> 217 + <div layout="column">
  218 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  219 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  220 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  221 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  222 + </md-switch>
  223 + </section>
  224 + <md-input-container ng-if="filter.rootStateEntity" class="md-block">
  225 + <label translate>alias.state-entity-parameter-name</label>
  226 + <input name="stateEntityParamName"
  227 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  228 + ng-model="filter.stateEntityParamName"
  229 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  230 + </md-input-container>
  231 + </div>
198 </div> 232 </div>
199 <div flex layout="row"> 233 <div flex layout="row">
200 <md-input-container class="md-block" style="min-width: 100px;"> 234 <md-input-container class="md-block" style="min-width: 100px;">
@@ -187,6 +187,8 @@ export default angular.module('thingsboard.locale', []) @@ -187,6 +187,8 @@ export default angular.module('thingsboard.locale', [])
187 "no-entity-filter-specified": "No entity filter specified", 187 "no-entity-filter-specified": "No entity filter specified",
188 "root-state-entity": "Use dashboard state entity as root", 188 "root-state-entity": "Use dashboard state entity as root",
189 "root-entity": "Root entity", 189 "root-entity": "Root entity",
  190 + "state-entity-parameter-name": "State entity parameter name",
  191 + "default-entity-parameter-name": "By default",
190 "max-relation-level": "Max relation level", 192 "max-relation-level": "Max relation level",
191 "unlimited-level": "Unlimited level", 193 "unlimited-level": "Unlimited level",
192 "state-entity": "Dashboard state entity", 194 "state-entity": "Dashboard state entity",
@@ -479,6 +479,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra @@ -479,6 +479,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
479 479
480 for (var i=0;i<vm.datasources.length;i++) { 480 for (var i=0;i<vm.datasources.length;i++) {
481 datasource = vm.datasources[i]; 481 datasource = vm.datasources[i];
  482 + if (datasource.type == types.datasourceType.entity && !datasource.entityId) {
  483 + continue;
  484 + }
482 var entity = { 485 var entity = {
483 id: {} 486 id: {}
484 }; 487 };