From 81ccdf7bfafd486ebd00cf619cdb6b5c4818cead Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 12 Jul 2017 19:28:53 +0300 Subject: [PATCH] TB-70: Improve dashboards states management. --- .../json/system/widget_bundles/cards.json | 6 +- .../widget_bundles/control_widgets.json | 2 +- ui/src/app/api/alias-controller.js | 14 +++- ui/src/app/api/entity.service.js | 35 +++++++--- ui/src/app/api/subscription.js | 9 ++- ui/src/app/api/widget.service.js | 8 ++- .../components/datasource-entity.directive.js | 13 ++-- .../components/datasource-func.directive.js | 13 ++-- ui/src/app/components/datasource.directive.js | 1 + ui/src/app/components/datasource.tpl.html | 2 + .../action/widget-action-dialog.controller.js | 2 + .../action/widget-action-dialog.tpl.html | 20 ++++-- .../components/widget/widget-config.tpl.html | 1 + .../components/widget/widget.controller.js | 49 +++++++------ .../dashboard-settings.controller.js | 8 +++ .../app/dashboard/dashboard-settings.tpl.html | 3 +- .../states/default-state-controller.js | 5 ++ .../states/entity-state-controller.js | 36 +++++++--- .../states/states-component.directive.js | 9 +++ ui/src/app/entity/entity-filter.directive.js | 2 + ui/src/app/entity/entity-filter.tpl.html | 70 ++++++++++++++----- ui/src/app/locale/locale.constant.js | 2 + .../app/widget/lib/entities-table-widget.js | 3 + 23 files changed, 231 insertions(+), 82 deletions(-) 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 8833ca67b9..c9e1cd1e6d 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": "\n", "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 9ae8677d5f..24014fc4ba 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": "
", "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 += ' [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 += ' [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 9acd5aaa8b..fd970c0529 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 dde622871a..b20d0b375d 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 dd93626ed9..8678b9b429 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 a3019259f2..8580b800bd 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-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js index 46c8b6344f..2cc5d2c09d 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 96e03871c1..5ab1dcd987 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/datasource.directive.js b/ui/src/app/components/datasource.directive.js index 42011b03a4..6d81553d89 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 41b18dbb51..df0b1500a5 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 @@ {{ 'widget-action.open-right-layout' | translate }} - {{ 'widget-action.set-entity-from-widget' | translate }} - +
+ {{ 'widget-action.set-entity-from-widget' | translate }} + + + + + +
- + {{stateControllerId}} diff --git a/ui/src/app/dashboard/states/default-state-controller.js b/ui/src/app/dashboard/states/default-state-controller.js index d39478f14c..ae92d37518 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 4510449e28..cbe91faaee 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 538c3e9620..9af5122a0f 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 b81f36941d..db7a744a60 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 4f5af00bf2..dae28172af 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -59,6 +59,13 @@
+ + + +
-
- - - -
+
+
+ + + +
+ + + + +
@@ -139,12 +155,21 @@ ng-disabled="filter.rootStateEntity" ng-model="filter.rootEntity"> -
- - - -
+
+
+ + + +
+ + + + +
@@ -189,12 +214,21 @@ ng-disabled="filter.rootStateEntity" ng-model="filter.rootEntity"> -
- - - -
+
+
+ + + +
+ + + + +
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index f1f8543267..2a33d96b2c 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 2f8003756e..f0b5835fbe 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