thingsboard-aplcache
Changes
ui/src/app/api/alias-controller.js 14(+11 -3)
ui/src/app/api/entity.service.js 35(+27 -8)
ui/src/app/api/subscription.js 9(+6 -3)
ui/src/app/api/widget.service.js 8(+6 -2)
ui/src/app/entity/entity-filter.tpl.html 70(+52 -18)
ui/src/app/locale/locale.constant.js 2(+2 -0)
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\":{}}"
ui/src/app/api/alias-controller.js 14(+11 -3)
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];
}
ui/src/app/api/entity.service.js 35(+27 -8)
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;
ui/src/app/api/subscription.js 9(+6 -3)
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();
}
-
}
}
}
ui/src/app/api/widget.service.js 8(+6 -2)
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;
ui/src/app/entity/entity-filter.tpl.html 70(+52 -18)
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;">
ui/src/app/locale/locale.constant.js 2(+2 -0)
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: {}
};