From 5ee16ef5af9bcf6a32e20b0d581b474e5c51e4ec Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 8 Jan 2021 10:24:45 +0200 Subject: [PATCH 01/23] Hide sensitive edge info for Customers --- ui/src/app/edge/edge-fieldset.tpl.html | 15 +++++++++------ ui/src/app/edge/edges.tpl.html | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index 7058d44000..d805eda10a 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -58,6 +58,7 @@ edge.copy-edge-key @@ -65,12 +66,14 @@ edge.copy-edge-secret edge.sync @@ -102,16 +105,16 @@ ng-model="edge.type" entity-type="types.entityType.edge"> -
edge.license-key-hint
- +
edge.license-key-hint
+
edge.edge-license-key-required
-
edge.cloud-endpoint-hint
- +
edge.cloud-endpoint-hint
+
@@ -119,7 +122,7 @@
-
+
@@ -134,7 +137,7 @@
-
+
diff --git a/ui/src/app/edge/edges.tpl.html b/ui/src/app/edge/edges.tpl.html index ea40c80b06..d7724238f3 100644 --- a/ui/src/app/edge/edges.tpl.html +++ b/ui/src/app/edge/edges.tpl.html @@ -65,7 +65,7 @@ default-event-type="{{vm.types.eventType.error.value}}"> - + From 21823588d4408c29fbf7b51e7cbc2eae843eea0b Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 8 Jan 2021 13:38:18 +0200 Subject: [PATCH 02/23] Hide sensitive edge info for Customers update --- ui/src/app/edge/edge-fieldset.tpl.html | 18 +++++++++--------- ui/src/app/edge/edge.directive.js | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index d805eda10a..bf0f518793 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -58,7 +58,7 @@ edge.copy-edge-key @@ -66,14 +66,14 @@ edge.copy-edge-secret edge.sync @@ -105,16 +105,16 @@ ng-model="edge.type" entity-type="types.entityType.edge"> -
edge.license-key-hint
- +
edge.license-key-hint
+
edge.edge-license-key-required
-
edge.cloud-endpoint-hint
- +
edge.cloud-endpoint-hint
+
@@ -122,7 +122,7 @@
-
+
@@ -137,7 +137,7 @@
-
+
diff --git a/ui/src/app/edge/edge.directive.js b/ui/src/app/edge/edge.directive.js index 7f8e6c3436..99fd7e6dc3 100644 --- a/ui/src/app/edge/edge.directive.js +++ b/ui/src/app/edge/edge.directive.js @@ -20,7 +20,7 @@ import edgeFieldsetTemplate from './edge-fieldset.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, utils, toast, types, customerService, edgeService) { +export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, utils, toast, types, customerService, edgeService, userService) { var linker = function (scope, element) { var template = $templateCache.get(edgeFieldsetTemplate); element.html(template); @@ -29,6 +29,7 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD scope.isAssignedToCustomer = false; scope.isPublic = false; scope.assignedCustomer = null; + scope.isTenantAdmin = userService.getCurrentUser().authority === 'TENANT_ADMIN'; scope.$watch('edge', function(newVal) { if (newVal) { From 9c1a93141b362799c01489dc83a9a7bee8840c6c Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 8 Jan 2021 13:47:56 +0200 Subject: [PATCH 03/23] Hide sensitive edge info for Customers --- ui/src/app/edge/edge-fieldset.tpl.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index bf0f518793..c5760a939c 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -57,7 +57,8 @@ @@ -65,14 +66,15 @@ edge.copy-edge-secret From 52218bf875fea4623bd3907055729c9fe3f3f427 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 8 Jan 2021 14:46:58 +0200 Subject: [PATCH 04/23] Edge widgets initial implementation --- .../system/widget_bundles/edge_widgets.json | 25 + .../install/ThingsboardInstallService.java | 1 + ui/src/app/api/widget.service.js | 3 +- ui/src/app/components/nav-tree.scss | 12 + .../app/widget/lib/edges-overview-widget.js | 483 ++++++++++++++++++ .../app/widget/lib/edges-overview-widget.scss | 109 ++++ .../widget/lib/edges-overview-widget.tpl.html | 26 + 7 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 application/src/main/data/json/system/widget_bundles/edge_widgets.json create mode 100644 ui/src/app/widget/lib/edges-overview-widget.js create mode 100644 ui/src/app/widget/lib/edges-overview-widget.scss create mode 100644 ui/src/app/widget/lib/edges-overview-widget.tpl.html diff --git a/application/src/main/data/json/system/widget_bundles/edge_widgets.json b/application/src/main/data/json/system/widget_bundles/edge_widgets.json new file mode 100644 index 0000000000..32047f20a4 --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/edge_widgets.json @@ -0,0 +1,25 @@ +{ + "widgetsBundle": { + "alias": "edge_widgets", + "title": "Edge widgets", + "image": null + }, + "widgetTypes": [ + { + "alias": "edges_overview", + "name": "Edges Quick Overview", + "descriptor": { + "type": "latest", + "sizeX": 7.5, + "sizeY": 5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n};\n\nself.onDestroy = function() {\n};\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}\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\":{},\"title\":\"Edges Quick Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" + } + } + ] +} diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index c9f6b94922..91a3912fbe 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -176,6 +176,7 @@ public class ThingsboardInstallService { systemDataLoaderService.deleteSystemWidgetBundle("input_widgets"); systemDataLoaderService.deleteSystemWidgetBundle("date"); systemDataLoaderService.deleteSystemWidgetBundle("entity_admin_widgets"); + systemDataLoaderService.deleteSystemWidgetBundle("edge_widgets"); systemDataLoaderService.loadSystemWidgets(); break; diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index 4b706e827d..742f102d28 100644 --- a/ui/src/app/api/widget.service.js +++ b/ui/src/app/api/widget.service.js @@ -22,6 +22,7 @@ import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-wid import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy-widget'; +import thingsboardEdgesOverviewWidget from '../widget/lib/edges-overview-widget' import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator'; import thingsboardMultipleInputWidget from '../widget/lib/multiple-input-widget'; @@ -52,7 +53,7 @@ import thingsboardUtils from '../common/utils.service'; export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, - thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, + thingsboardEntitiesHierarchyWidget, thingsboardEdgesOverviewWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils, thingsboardJsonToString, TripAnimationWidget]) .factory('widgetService', WidgetService) diff --git a/ui/src/app/components/nav-tree.scss b/ui/src/app/components/nav-tree.scss index df903e747b..dc21431479 100644 --- a/ui/src/app/components/nav-tree.scss +++ b/ui/src/app/components/nav-tree.scss @@ -131,6 +131,18 @@ content: "supervisor_account"; } } + + &.tb-edge-group { + &::before { + content: "router"; + } + } + + &.tb-rule-chain-group { + &::before { + content: "settings_ethernet"; + } + } } } } diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js new file mode 100644 index 0000000000..278ccb52fb --- /dev/null +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -0,0 +1,483 @@ +/* + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import './edges-overview-widget.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import edgesOverviewWidgetTemplate from './edges-overview-widget.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.widgets.edgesOverviewWidget', []) + .directive('tbEdgesOverviewWidget', EdgesOverviewWidget) + .name; +/* eslint-disable no-unused-vars, no-undef */ +/*@ngInject*/ +function EdgesOverviewWidget() { + return { + restrict: "E", + scope: true, + bindToController: { + hierarchyId: '=', + ctx: '=' + }, + controller: EdgesOverviewWidgetController, + controllerAs: 'vm', + templateUrl: edgesOverviewWidgetTemplate + }; +} + +/*@ngInject*/ +function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, types, entityService, userService, entityRelationService, + assetService, deviceService, entityViewService, dashboardService, ruleChainService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) { + var vm = this; + + vm.showData = true; + + vm.nodeEditCallbacks = {}; + + vm.nodeIdCounter = 0; + + vm.nodesMap = {}; + vm.entityGroupsNodesMap = {}; + vm.pendingUpdateNodeTasks = {}; + + vm.query = { + search: null + }; + + vm.edgeGroupsTypes = [ + types.entityType.asset, + types.entityType.device, + types.entityType.entityView, + types.entityType.dashboard, + types.entityType.rulechain, + ] + + vm.onNodesInserted = onNodesInserted; + vm.onNodeSelected = onNodeSelected; + + $scope.$watch('vm.ctx', function() { + if (vm.ctx && vm.ctx.defaultSubscription) { + vm.settings = vm.ctx.settings; + vm.widgetConfig = vm.ctx.widgetConfig; + vm.subscription = vm.ctx.defaultSubscription; + vm.datasources = vm.subscription.datasources; + updateDatasources(); + } + }); + + $scope.$on('edges-overview-data-updated', function(event, hierarchyId) { + if (vm.hierarchyId == hierarchyId) { + if (vm.subscription) { + updateNodeData(vm.subscription.data); + } + } + }); + + function updateDatasources() { + vm.loadNodes = loadNodes; + } + + function onNodesInserted(nodes/*, parent*/) { + if (nodes) { + nodes.forEach((nodeId) => { + var task = vm.pendingUpdateNodeTasks[nodeId]; + if (task) { + task(); + delete vm.pendingUpdateNodeTasks[nodeId]; + } + }); + } + } + + function onNodeSelected(node, event) { + var nodeId; + if (!node) { + nodeId = -1; + } else { + nodeId = node.id; + } + if (nodeId !== -1) { + var selectedNode = vm.nodesMap[nodeId]; + if (selectedNode) { + var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected'); + if (descriptors.length) { + var entity = selectedNode.data.nodeCtx.entity; + vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); + } + } + } + } + + function updateNodeData(subscriptionData) { + var affectedNodes = []; + if (subscriptionData) { + for (var i=0;i { + var node = vm.nodeEditCallbacks.getNode(nodeId); + if (node) { + updateNodeStyle(vm.nodesMap[nodeId]); + } else { + vm.pendingUpdateNodeTasks[nodeId] = () => { + updateNodeStyle(vm.nodesMap[nodeId]); + }; + } + }); + } + + function updateNodeStyle(node) { + var newText = prepareNodeText(node); + if (!angular.equals(node.text, newText)) { + node.text = newText; + vm.nodeEditCallbacks.updateNode(node.id, node.text); + } + var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx); + if (!angular.equals(node.state.disabled, newDisabled)) { + node.state.disabled = newDisabled; + if (node.state.disabled) { + vm.nodeEditCallbacks.disableNode(node.id); + } else { + vm.nodeEditCallbacks.enableNode(node.id); + } + } + var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx); + if (!angular.equals(node.children, newHasChildren)) { + node.children = newHasChildren; + vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children); + } + } + + function prepareNodeText(node) { + var nodeIcon = prepareNodeIcon(node.data.nodeCtx); + var nodeText = vm.nodeTextFunction(node.data.nodeCtx); + node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; + return nodeIcon + nodeText; + } + + function prepareEgeGroupText(node) { + var nodeIcon = prepareNodeIcon(node.data.nodeCtx); + var nodeText = "Edge rule chains"; + node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; + return nodeIcon + nodeText; + } + + function loadNodes(node, cb) { + var datasource = vm.datasources[0]; + if (node.id === '#' && datasource) { + if (datasource.type === 'entity' && datasource.entity.id.entityType === types.entityType.edge) { + var selectedEdge = datasource.entity; + vm.ctx.widgetTitle = selectedEdge.name; + cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)) + } + } else { + cb([]); + } + } + + function loadNodesForEdge(parentNodeId, entity) { + var nodes = []; + var nodesMap = {}; + vm.entityGroupsNodesMap[parentNodeId] = nodesMap; + var user = userService.getCurrentUser(); + var allowedGroupTypes = vm.edgeGroupsTypes; + if (user.authority === 'CUSTOMER_USER') { + allowedGroupTypes = vm.edgeGroupsTypes.filter(type => type !== types.entityType.rulechain) + } + allowedGroupTypes.forEach((groupType) => { + var node = { + id: ++vm.nodeIdCounter, + icon: 'material-icons ' + iconForGroupType(groupType), + text: textForGroupType(groupType), + children: true, + data: { + groupType, + entity, + internalId: entity.id.id + '_' + groupType + } + }; + nodes.push(node); + nodesMap[groupType] = node.id; + }); + return nodes; + } + + function iconForGroupType(groupType) { + switch (groupType) { + case vm.types.entityType.asset: + return 'tb-asset-group'; + case vm.types.entityType.device: + return 'tb-device-group'; + case vm.types.entityType.entityView: + return 'tb-entity-view-group'; + case vm.types.entityType.dashboard: + return 'tb-dashboard-group'; + case vm.types.entityType.rulechain: + return 'tb-rule-chain-group'; + } + return ''; + } + + function textForGroupType(groupType) { + switch (groupType) { + case vm.types.entityType.asset: + return $translate.instant('asset.assets'); + case vm.types.entityType.device: + return $translate.instant('device.devices'); + case vm.types.entityType.entityView: + return $translate.instant('entity-view.entity-views'); + case vm.types.entityType.rulechain: + return $translate.instant('rulechain.rulechains'); + case vm.types.entityType.dashboard: + return $translate.instant('dashboard.dashboards'); + } + return ''; + } + + function edgeGroupsNodeText(entityType) { + + } + + function prepareNodes(nodes) { + nodes = nodes.filter((node) => node !== null); + nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); + return nodes; + } + + function datasourceToNode(datasource, parentNodeCtx) { + var deferred = $q.defer(); + resolveEntity(datasource).then( + (entity) => { + if (entity != null) { + var node = { + id: ++vm.nodeIdCounter + }; + vm.nodesMap[node.id] = node; + datasource.nodeId = node.id; + node.icon = false; + var nodeCtx = { + parentNodeCtx: parentNodeCtx, + entity: entity, + data: {} + }; + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; + node.data = { + datasource: datasource, + nodeCtx: nodeCtx + }; + node.state = { + disabled: vm.nodeDisabledFunction(node.data.nodeCtx), + opened: vm.nodeOpenedFunction(node.data.nodeCtx) + }; + node.text = prepareNodeText(node); + node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); + deferred.resolve(node); + } else { + deferred.resolve(null); + } + } + ); + return deferred.promise; + } + + function edgeGroupToNode(entityType, parentDatasource, parentNodeCtx) { + var deferred = $q.defer(); + deferred.resolve(getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx)); + return deferred.promise; + } + + function getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx) { + var datasource = { + dataKeys: parentDatasource.dataKeys, + entityId: parentDatasource.entity.id.id + '_' + entityType, + type: "edgeGroup", + entityType: entityType + }; + + var node = { + id: ++vm.nodeIdCounter + }; + vm.nodesMap[node.id] = node; + datasource.nodeId = node.id; + node.icon = false; + var nodeCtx = { + parentNodeCtx: 3, + data: {}, + entity: { + id: { + id: parentDatasource.entity.id.id + '_' + entityType, + entityType: 'group' + }, + name: "Edge Group RC" + } + }; + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; + node.data = { + datasource: datasource, + nodeCtx: nodeCtx + }; + node.state = { + disabled: vm.nodeDisabledFunction(node.data.nodeCtx), + opened: vm.nodeOpenedFunction(node.data.nodeCtx) + }; + node.text = prepareEgeGroupText(node); + node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); + return node; + } + + function entityIdToEdgeGroupNode(entityType, entityId, parentDatasource, parentNodeCtx) { + var deferred = $q.defer(); + var datasource = { + dataKeys: parentDatasource.dataKeys, + type: types.datasourceType.entity, + entityType: entityType, + entityId: entityId + }; + datasourceToNode(datasource, parentNodeCtx).then( + (node) => { + if (node != null) { + var subscriptionOptions = { + type: types.widgetType.latest.value, + datasources: [datasource], + callbacks: { + onDataUpdated: (subscription) => { + updateNodeData(subscription.data); + } + } + }; + vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then( + (/*subscription*/) => { + deferred.resolve(node); + } + ); + } else { + deferred.resolve(node); + } + } + ); + return deferred.promise; + } + + function resolveEntity(datasource) { + var deferred = $q.defer(); + if (datasource.type === types.datasourceType.function) { + var entity = { + id: { + entityType: "function" + }, + name: datasource.name + } + deferred.resolve(entity); + } else { + entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then( + (entity) => { + deferred.resolve(entity); + }, + () => { + deferred.resolve(null); + } + ); + } + return deferred.promise; + } + + function prepareNodeIcon(nodeCtx) { + var iconInfo = vm.nodeIconFunction(nodeCtx); + if (iconInfo && iconInfo === 'default') { + iconInfo = defaultNodeIconFunction(nodeCtx); + } + if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) { + if (iconInfo.materialIcon) { + return materialIconHtml(iconInfo.materialIcon); + } else { + return iconUrlHtml(iconInfo.iconUrl); + } + } else { + return ""; + } + } + + function materialIconHtml(materialIcon) { + return ''+materialIcon+''; + } + + function iconUrlHtml(iconUrl) { + return '
 
'; + } + + function defaultNodeIconFunction(nodeCtx) { + var materialIcon = 'insert_drive_file'; + var entity = nodeCtx.entity; + if (entity && entity.id && entity.id.entityType) { + switch (entity.id.entityType) { + case 'function': + materialIcon = 'functions'; + break; + case types.entityType.device: + materialIcon = 'devices_other'; + break; + case types.entityType.asset: + materialIcon = 'domain'; + break; + case types.entityType.tenant: + materialIcon = 'supervisor_account'; + break; + case types.entityType.customer: + materialIcon = 'supervisor_account'; + break; + case types.entityType.user: + materialIcon = 'account_circle'; + break; + case types.entityType.dashboard: + materialIcon = 'dashboards'; + break; + case types.entityType.alarm: + materialIcon = 'notifications_active'; + break; + case types.entityType.entityView: + materialIcon = 'view_quilt'; + break; + case types.entityType.edge: + materialIcon = 'router'; + break; + case types.entityType.rulechain: + materialIcon = 'settings_ethernet'; + break; + } + } + return { + materialIcon: materialIcon + }; + } + +} diff --git a/ui/src/app/widget/lib/edges-overview-widget.scss b/ui/src/app/widget/lib/edges-overview-widget.scss new file mode 100644 index 0000000000..cd2d419ef2 --- /dev/null +++ b/ui/src/app/widget/lib/edges-overview-widget.scss @@ -0,0 +1,109 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-has-timewindow { + .tb-edges-overview { + md-toolbar { + min-height: 60px; + max-height: 60px; + } + } +} + +.tb-edges-overview { + md-toolbar { + min-height: 39px; + max-height: 39px; + } + + .tb-entities-nav-tree-panel { + overflow-x: auto; + overflow-y: auto; + + .tb-nav-tree-container { + &.jstree-proton { + .jstree-anchor { + div.node-icon { + display: inline-block; + width: 22px; + height: 22px; + margin-right: 2px; + margin-bottom: 2px; + background-color: transparent; + background-repeat: no-repeat; + background-attachment: scroll; + background-position: center center; + background-size: 18px 18px; + } + + md-icon.node-icon { + width: 22px; + min-width: 22px; + height: 22px; + min-height: 22px; + margin-right: 2px; + margin-bottom: 2px; + color: inherit; + + &.material-icons { /* stylelint-disable-line selector-max-class */ + font-size: 18px; + line-height: 22px; + text-align: center; + } + } + + &.jstree-hovered:not(.jstree-clicked), + &.jstree-disabled { + div.node-icon { /* stylelint-disable-line selector-max-class */ + opacity: .5; + } + } + } + } + } + } +} + +@media (max-width: 768px) { + .tb-edges-overview { + .tb-entities-nav-tree-panel { + .tb-nav-tree-container { + &.jstree-proton-responsive { + .jstree-anchor { + div.node-icon { + width: 40px; + height: 40px; + margin: 0; + background-size: 24px 24px; + } + + md-icon.node-icon { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + margin: 0; + + &.material-icons { /* stylelint-disable-line selector-max-class */ + font-size: 24px; + line-height: 40px; + } + } + } + } + } + } + } +} diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html new file mode 100644 index 0000000000..feeefd1013 --- /dev/null +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -0,0 +1,26 @@ + +
+
+
+ +
+
+
From 68d56019de99e354cdeec5e7236fce56683b6590 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Mon, 11 Jan 2021 06:05:32 +0200 Subject: [PATCH 05/23] Edges overview widget initial implementation --- .../system/widget_bundles/edge_widgets.json | 10 +- ui/src/app/api/entity.service.js | 25 +- ui/src/app/locale/locale.constant-en_US.json | 3 +- .../widget/lib/edges-overview-widget-copy.js | 483 ++++++++++++++++++ .../lib/edges-overview-widget-copy.scss | 109 ++++ .../app/widget/lib/edges-overview-widget.js | 348 +++++++++---- .../app/widget/lib/edges-overview-widget.scss | 7 + .../widget/lib/edges-overview-widget.tpl.html | 26 + 8 files changed, 908 insertions(+), 103 deletions(-) create mode 100644 ui/src/app/widget/lib/edges-overview-widget-copy.js create mode 100644 ui/src/app/widget/lib/edges-overview-widget-copy.scss diff --git a/application/src/main/data/json/system/widget_bundles/edge_widgets.json b/application/src/main/data/json/system/widget_bundles/edge_widgets.json index 32047f20a4..47cf3c94aa 100644 --- a/application/src/main/data/json/system/widget_bundles/edge_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/edge_widgets.json @@ -13,12 +13,12 @@ "sizeX": 7.5, "sizeY": 5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n};\n\nself.onDestroy = function() {\n};\n", - "settingsSchema": "{}", - "dataKeySettingsSchema": "{}\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\":{},\"title\":\"Edges Quick Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.hierarchyId = \"hierarchy-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('edges-overview-data-updated', self.ctx.$scope.hierarchyId);\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EdgesOverviewSettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\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\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" \\\"+ data['temperature'] +\\\" °C\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Edges Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" } } ] diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 56e4378eba..091c1287ba 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -42,7 +42,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getRelatedEntity: getRelatedEntity, deleteRelatedEntity: deleteRelatedEntity, moveEntity: moveEntity, - copyEntity: copyEntity + copyEntity: copyEntity, + getAssignedToEdgeEntitiesByType: getAssignedToEdgeEntitiesByType }; return service; @@ -1684,4 +1685,26 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } } + function getAssignedToEdgeEntitiesByType(edgeId, entityType, pageLink) { + var promise; + switch (entityType) { + case types.entityType.device: + promise = deviceService.getEdgeDevices(edgeId, pageLink, null); + break; + case types.entityType.asset: + promise = assetService.getEdgeAssets(edgeId, pageLink, null); + break; + case types.entityType.entityView: + promise = entityViewService.getEdgeEntityViews(edgeId, pageLink, null); + break; + case types.entityType.dashboard: + promise = dashboardService.getEdgeDashboards(edgeId, pageLink, null); + break; + case types.entityType.rulechain: + promise = ruleChainService.getEdgeRuleChains(edgeId, pageLink, null); + break; + } + return promise; + } + } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index c9522365b6..e4aedb319f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -856,7 +856,8 @@ "missing-related-rule-chains-title": "Edge has missing related rule chain(s)", "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}", "downlinks": "Downlinks", - "no-downlinks-prompt": "No downlinks found" + "no-downlinks-prompt": "No downlinks found", + "assigned-to-customer-widget": "Assigned to: {{customerTitle}}" }, "edge-event": { "type-dashboard": "Dashboard", diff --git a/ui/src/app/widget/lib/edges-overview-widget-copy.js b/ui/src/app/widget/lib/edges-overview-widget-copy.js new file mode 100644 index 0000000000..56921b264c --- /dev/null +++ b/ui/src/app/widget/lib/edges-overview-widget-copy.js @@ -0,0 +1,483 @@ +// /* +// * Copyright © 2016-2020 The Thingsboard Authors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// import './edges-overview-widget-copy.scss'; +// +// /* eslint-disable import/no-unresolved, import/default */ +// +// import edgesOverviewWidgetTemplate from './edges-overview-widget.tpl.html'; +// +// /* eslint-enable import/no-unresolved, import/default */ +// +// export default angular.module('thingsboard.widgets.edgesOverviewWidget2', []) +// .directive('tbEdgesOverviewWidget', EdgesOverviewWidget) +// .name; +// /* eslint-disable no-unused-vars, no-undef */ +// /*@ngInject*/ +// function EdgesOverviewWidget() { +// return { +// restrict: "E", +// scope: true, +// bindToController: { +// hierarchyId: '=', +// ctx: '=' +// }, +// controller: EdgesOverviewWidgetController, +// controllerAs: 'vm', +// templateUrl: edgesOverviewWidgetTemplate +// }; +// } +// +// /*@ngInject*/ +// function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, types, entityService, userService, entityRelationService, +// assetService, deviceService, entityViewService, dashboardService, ruleChainService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) { +// var vm = this; +// +// vm.showData = true; +// +// vm.nodeEditCallbacks = {}; +// +// vm.nodeIdCounter = 0; +// +// vm.nodesMap = {}; +// vm.entityGroupsNodesMap = {}; +// vm.pendingUpdateNodeTasks = {}; +// +// vm.query = { +// search: null +// }; +// +// vm.edgeGroupsTypes = [ +// types.entityType.asset, +// types.entityType.device, +// types.entityType.entityView, +// types.entityType.dashboard, +// types.entityType.rulechain, +// ] +// +// vm.onNodesInserted = onNodesInserted; +// vm.onNodeSelected = onNodeSelected; +// +// $scope.$watch('vm.ctx', function() { +// if (vm.ctx && vm.ctx.defaultSubscription) { +// vm.settings = vm.ctx.settings; +// vm.widgetConfig = vm.ctx.widgetConfig; +// vm.subscription = vm.ctx.defaultSubscription; +// vm.datasources = vm.subscription.datasources; +// updateDatasources(); +// } +// }); +// +// $scope.$on('edges-overview-data-updated', function(event, hierarchyId) { +// if (vm.hierarchyId == hierarchyId) { +// if (vm.subscription) { +// updateNodeData(vm.subscription.data); +// } +// } +// }); +// +// function updateDatasources() { +// vm.loadNodes = loadNodes; +// } +// +// function onNodesInserted(nodes/*, parent*/) { +// if (nodes) { +// nodes.forEach((nodeId) => { +// var task = vm.pendingUpdateNodeTasks[nodeId]; +// if (task) { +// task(); +// delete vm.pendingUpdateNodeTasks[nodeId]; +// } +// }); +// } +// } +// +// function onNodeSelected(node, event) { +// var nodeId; +// if (!node) { +// nodeId = -1; +// } else { +// nodeId = node.id; +// } +// if (nodeId !== -1) { +// var selectedNode = vm.nodesMap[nodeId]; +// if (selectedNode) { +// var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected'); +// if (descriptors.length) { +// var entity = selectedNode.data.nodeCtx.entity; +// vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); +// } +// } +// } +// } +// +// function updateNodeData(subscriptionData) { +// var affectedNodes = []; +// if (subscriptionData) { +// for (var i=0;i { +// var node = vm.nodeEditCallbacks.getNode(nodeId); +// if (node) { +// updateNodeStyle(vm.nodesMap[nodeId]); +// } else { +// vm.pendingUpdateNodeTasks[nodeId] = () => { +// updateNodeStyle(vm.nodesMap[nodeId]); +// }; +// } +// }); +// } +// +// function updateNodeStyle(node) { +// var newText = prepareNodeText(node); +// if (!angular.equals(node.text, newText)) { +// node.text = newText; +// vm.nodeEditCallbacks.updateNode(node.id, node.text); +// } +// var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx); +// if (!angular.equals(node.state.disabled, newDisabled)) { +// node.state.disabled = newDisabled; +// if (node.state.disabled) { +// vm.nodeEditCallbacks.disableNode(node.id); +// } else { +// vm.nodeEditCallbacks.enableNode(node.id); +// } +// } +// var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx); +// if (!angular.equals(node.children, newHasChildren)) { +// node.children = newHasChildren; +// vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children); +// } +// } +// +// function prepareNodeText(node) { +// var nodeIcon = prepareNodeIcon(node.data.nodeCtx); +// var nodeText = vm.nodeTextFunction(node.data.nodeCtx); +// node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; +// return nodeIcon + nodeText; +// } +// +// function prepareEgeGroupText(node) { +// var nodeIcon = prepareNodeIcon(node.data.nodeCtx); +// var nodeText = "Edge rule chains"; +// node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; +// return nodeIcon + nodeText; +// } +// +// function loadNodes(node, cb) { +// var datasource = vm.datasources[0]; +// if (node.id === '#' && datasource) { +// if (datasource.type === 'entity' && datasource.entity.id.entityType === types.entityType.edge) { +// var selectedEdge = datasource.entity; +// vm.ctx.widgetTitle = selectedEdge.name; +// cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)) +// } +// } else { +// cb([]); +// } +// } +// +// function loadNodesForEdge(parentNodeId, entity) { +// var nodes = []; +// var nodesMap = {}; +// vm.entityGroupsNodesMap[parentNodeId] = nodesMap; +// var user = userService.getCurrentUser(); +// var allowedGroupTypes = vm.edgeGroupsTypes; +// if (user.authority === 'CUSTOMER_USER') { +// allowedGroupTypes = vm.edgeGroupsTypes.filter(type => type !== types.entityType.rulechain) +// } +// allowedGroupTypes.forEach((groupType) => { +// var node = { +// id: ++vm.nodeIdCounter, +// icon: 'material-icons ' + iconForGroupType(groupType), +// text: textForGroupType(groupType), +// children: true, +// data: { +// groupType, +// entity, +// internalId: entity.id.id + '_' + groupType +// } +// }; +// nodes.push(node); +// nodesMap[groupType] = node.id; +// }); +// return nodes; +// } +// +// function iconForGroupType(groupType) { +// switch (groupType) { +// case vm.types.entityType.asset: +// return 'tb-asset-group'; +// case vm.types.entityType.device: +// return 'tb-device-group'; +// case vm.types.entityType.entityView: +// return 'tb-entity-view-group'; +// case vm.types.entityType.dashboard: +// return 'tb-dashboard-group'; +// case vm.types.entityType.rulechain: +// return 'tb-rule-chain-group'; +// } +// return ''; +// } +// +// function textForGroupType(groupType) { +// switch (groupType) { +// case vm.types.entityType.asset: +// return $translate.instant('asset.assets'); +// case vm.types.entityType.device: +// return $translate.instant('device.devices'); +// case vm.types.entityType.entityView: +// return $translate.instant('entity-view.entity-views'); +// case vm.types.entityType.rulechain: +// return $translate.instant('rulechain.rulechains'); +// case vm.types.entityType.dashboard: +// return $translate.instant('dashboard.dashboards'); +// } +// return ''; +// } +// +// function edgeGroupsNodeText(entityType) { +// +// } +// +// function prepareNodes(nodes) { +// nodes = nodes.filter((node) => node !== null); +// nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); +// return nodes; +// } +// +// function datasourceToNode(datasource, parentNodeCtx) { +// var deferred = $q.defer(); +// resolveEntity(datasource).then( +// (entity) => { +// if (entity != null) { +// var node = { +// id: ++vm.nodeIdCounter +// }; +// vm.nodesMap[node.id] = node; +// datasource.nodeId = node.id; +// node.icon = false; +// var nodeCtx = { +// parentNodeCtx: parentNodeCtx, +// entity: entity, +// data: {} +// }; +// nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; +// node.data = { +// datasource: datasource, +// nodeCtx: nodeCtx +// }; +// node.state = { +// disabled: vm.nodeDisabledFunction(node.data.nodeCtx), +// opened: vm.nodeOpenedFunction(node.data.nodeCtx) +// }; +// node.text = prepareNodeText(node); +// node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); +// deferred.resolve(node); +// } else { +// deferred.resolve(null); +// } +// } +// ); +// return deferred.promise; +// } +// +// function edgeGroupToNode(entityType, parentDatasource, parentNodeCtx) { +// var deferred = $q.defer(); +// deferred.resolve(getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx)); +// return deferred.promise; +// } +// +// function getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx) { +// var datasource = { +// dataKeys: parentDatasource.dataKeys, +// entityId: parentDatasource.entity.id.id + '_' + entityType, +// type: "edgeGroup", +// entityType: entityType +// }; +// +// var node = { +// id: ++vm.nodeIdCounter +// }; +// vm.nodesMap[node.id] = node; +// datasource.nodeId = node.id; +// node.icon = false; +// var nodeCtx = { +// parentNodeCtx: 3, +// data: {}, +// entity: { +// id: { +// id: parentDatasource.entity.id.id + '_' + entityType, +// entityType: 'group' +// }, +// name: "Edge Group RC" +// } +// }; +// nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; +// node.data = { +// datasource: datasource, +// nodeCtx: nodeCtx +// }; +// node.state = { +// disabled: vm.nodeDisabledFunction(node.data.nodeCtx), +// opened: vm.nodeOpenedFunction(node.data.nodeCtx) +// }; +// node.text = prepareEgeGroupText(node); +// node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); +// return node; +// } +// +// function entityIdToEdgeGroupNode(entityType, entityId, parentDatasource, parentNodeCtx) { +// var deferred = $q.defer(); +// var datasource = { +// dataKeys: parentDatasource.dataKeys, +// type: types.datasourceType.entity, +// entityType: entityType, +// entityId: entityId +// }; +// datasourceToNode(datasource, parentNodeCtx).then( +// (node) => { +// if (node != null) { +// var subscriptionOptions = { +// type: types.widgetType.latest.value, +// datasources: [datasource], +// callbacks: { +// onDataUpdated: (subscription) => { +// updateNodeData(subscription.data); +// } +// } +// }; +// vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then( +// (/*subscription*/) => { +// deferred.resolve(node); +// } +// ); +// } else { +// deferred.resolve(node); +// } +// } +// ); +// return deferred.promise; +// } +// +// function resolveEntity(datasource) { +// var deferred = $q.defer(); +// if (datasource.type === types.datasourceType.function) { +// var entity = { +// id: { +// entityType: "function" +// }, +// name: datasource.name +// } +// deferred.resolve(entity); +// } else { +// entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then( +// (entity) => { +// deferred.resolve(entity); +// }, +// () => { +// deferred.resolve(null); +// } +// ); +// } +// return deferred.promise; +// } +// +// function prepareNodeIcon(nodeCtx) { +// var iconInfo = vm.nodeIconFunction(nodeCtx); +// if (iconInfo && iconInfo === 'default') { +// iconInfo = defaultNodeIconFunction(nodeCtx); +// } +// if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) { +// if (iconInfo.materialIcon) { +// return materialIconHtml(iconInfo.materialIcon); +// } else { +// return iconUrlHtml(iconInfo.iconUrl); +// } +// } else { +// return ""; +// } +// } +// +// function materialIconHtml(materialIcon) { +// return ''+materialIcon+''; +// } +// +// function iconUrlHtml(iconUrl) { +// return '
 
'; +// } +// +// function defaultNodeIconFunction(nodeCtx) { +// var materialIcon = 'insert_drive_file'; +// var entity = nodeCtx.entity; +// if (entity && entity.id && entity.id.entityType) { +// switch (entity.id.entityType) { +// case 'function': +// materialIcon = 'functions'; +// break; +// case types.entityType.device: +// materialIcon = 'devices_other'; +// break; +// case types.entityType.asset: +// materialIcon = 'domain'; +// break; +// case types.entityType.tenant: +// materialIcon = 'supervisor_account'; +// break; +// case types.entityType.customer: +// materialIcon = 'supervisor_account'; +// break; +// case types.entityType.user: +// materialIcon = 'account_circle'; +// break; +// case types.entityType.dashboard: +// materialIcon = 'dashboards'; +// break; +// case types.entityType.alarm: +// materialIcon = 'notifications_active'; +// break; +// case types.entityType.entityView: +// materialIcon = 'view_quilt'; +// break; +// case types.entityType.edge: +// materialIcon = 'router'; +// break; +// case types.entityType.rulechain: +// materialIcon = 'settings_ethernet'; +// break; +// } +// } +// return { +// materialIcon: materialIcon +// }; +// } +// +// } diff --git a/ui/src/app/widget/lib/edges-overview-widget-copy.scss b/ui/src/app/widget/lib/edges-overview-widget-copy.scss new file mode 100644 index 0000000000..59d335b2fa --- /dev/null +++ b/ui/src/app/widget/lib/edges-overview-widget-copy.scss @@ -0,0 +1,109 @@ +///** +// * Copyright © 2016-2020 The Thingsboard Authors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//.tb-has-timewindow { +// .tb-edges-overview { +// md-toolbar { +// min-height: 60px; +// max-height: 60px; +// } +// } +//} +// +//.tb-edges-overview { +// md-toolbar { +// min-height: 39px; +// max-height: 39px; +// } +// +// .tb-entities-nav-tree-panel { +// overflow-x: auto; +// overflow-y: auto; +// +// .tb-nav-tree-container { +// &.jstree-proton { +// .jstree-anchor { +// div.node-icon { +// display: inline-block; +// width: 22px; +// height: 22px; +// margin-right: 2px; +// margin-bottom: 2px; +// background-color: transparent; +// background-repeat: no-repeat; +// background-attachment: scroll; +// background-position: center center; +// background-size: 18px 18px; +// } +// +// md-icon.node-icon { +// width: 22px; +// min-width: 22px; +// height: 22px; +// min-height: 22px; +// margin-right: 2px; +// margin-bottom: 2px; +// color: inherit; +// +// &.material-icons { /* stylelint-disable-line selector-max-class */ +// font-size: 18px; +// line-height: 22px; +// text-align: center; +// } +// } +// +// &.jstree-hovered:not(.jstree-clicked), +// &.jstree-disabled { +// div.node-icon { /* stylelint-disable-line selector-max-class */ +// opacity: .5; +// } +// } +// } +// } +// } +// } +//} +// +//@media (max-width: 768px) { +// .tb-edges-overview { +// .tb-entities-nav-tree-panel { +// .tb-nav-tree-container { +// &.jstree-proton-responsive { +// .jstree-anchor { +// div.node-icon { +// width: 40px; +// height: 40px; +// margin: 0; +// background-size: 24px 24px; +// } +// +// md-icon.node-icon { +// width: 40px; +// min-width: 40px; +// height: 40px; +// min-height: 40px; +// margin: 0; +// +// &.material-icons { /* stylelint-disable-line selector-max-class */ +// font-size: 24px; +// line-height: 40px; +// } +// } +// } +// } +// } +// } +// } +//} diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index 278ccb52fb..396bc1a23e 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -16,7 +16,6 @@ import './edges-overview-widget.scss'; /* eslint-disable import/no-unresolved, import/default */ - import edgesOverviewWidgetTemplate from './edges-overview-widget.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -41,8 +40,7 @@ function EdgesOverviewWidget() { } /*@ngInject*/ -function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, types, entityService, userService, entityRelationService, - assetService, deviceService, entityViewService, dashboardService, ruleChainService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) { +function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translate, toast, types, utils, entityService, edgeService, customerService, userService /*$filter, $mdMedia, $mdPanel, $document, $timeout, utils, types*/) { var vm = this; vm.showData = true; @@ -52,6 +50,7 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty vm.nodeIdCounter = 0; vm.nodesMap = {}; + vm.entityNodesMap = {}; vm.entityGroupsNodesMap = {}; vm.pendingUpdateNodeTasks = {}; @@ -59,7 +58,18 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty search: null }; - vm.edgeGroupsTypes = [ + vm.searchAction = { + name: 'action.search', + show: true, + onAction: function() { + vm.enterFilterMode(); + }, + icon: 'search' + }; + + vm.customerTitle = null; + + var edgeGroupsTypes = [ types.entityType.asset, types.entityType.device, types.entityType.entityView, @@ -69,6 +79,9 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty vm.onNodesInserted = onNodesInserted; vm.onNodeSelected = onNodeSelected; + vm.enterFilterMode = enterFilterMode; + vm.exitFilterMode = exitFilterMode; + vm.searchCallback = searchCallback; $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { @@ -76,10 +89,17 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty vm.widgetConfig = vm.ctx.widgetConfig; vm.subscription = vm.ctx.defaultSubscription; vm.datasources = vm.subscription.datasources; + initializeConfig(); updateDatasources(); } }); + $scope.$watch("vm.query.search", function(newVal, prevVal) { + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { + updateSearchNodes(); + } + }); + $scope.$on('edges-overview-data-updated', function(event, hierarchyId) { if (vm.hierarchyId == hierarchyId) { if (vm.subscription) { @@ -88,10 +108,96 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty } }); + function initializeConfig() { + + vm.ctx.widgetActions = [ vm.searchAction ]; + + var testNodeCtx = { + entity: { + id: { + entityType: 'DEVICE', + id: '123' + }, + name: 'TEST DEV1' + }, + data: {}, + level: 2 + }; + var parentNodeCtx = angular.copy(testNodeCtx); + parentNodeCtx.level = 1; + testNodeCtx.parentNodeCtx = parentNodeCtx; + + var nodeRelationQueryFunction = loadNodeCtxFunction(vm.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx); + var nodeIconFunction = loadNodeCtxFunction(vm.settings.nodeIconFunction, 'nodeCtx', testNodeCtx); + var nodeTextFunction = loadNodeCtxFunction(vm.settings.nodeTextFunction, 'nodeCtx', testNodeCtx); + var nodeDisabledFunction = loadNodeCtxFunction(vm.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx); + var nodeOpenedFunction = loadNodeCtxFunction(vm.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx); + var nodeHasChildrenFunction = loadNodeCtxFunction(vm.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx); + + var testNodeCtx2 = angular.copy(testNodeCtx); + testNodeCtx2.entity.name = 'TEST DEV2'; + + var nodesSortFunction = loadNodeCtxFunction(vm.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2); + + vm.nodeRelationQueryFunction = nodeRelationQueryFunction || defaultNodeRelationQueryFunction; + vm.nodeIconFunction = nodeIconFunction || defaultNodeIconFunction; + vm.nodeTextFunction = nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name); + vm.nodeDisabledFunction = nodeDisabledFunction || (() => false); + vm.nodeOpenedFunction = nodeOpenedFunction || defaultNodeOpenedFunction; + vm.nodeHasChildrenFunction = nodeHasChildrenFunction || (() => true); + vm.nodesSortFunction = nodesSortFunction || defaultSortFunction; + } + + function loadNodeCtxFunction(functionBody, argNames, ...args) { + var nodeCtxFunction = null; + if (angular.isDefined(functionBody) && functionBody.length) { + try { + nodeCtxFunction = new Function(argNames, functionBody); + var res = nodeCtxFunction.apply(null, args); + if (angular.isUndefined(res)) { + nodeCtxFunction = null; + } + } catch (e) { + nodeCtxFunction = null; + } + } + return nodeCtxFunction; + } + + function enterFilterMode () { + vm.query.search = ''; + vm.ctx.hideTitlePanel = true; + $timeout(()=>{ + angular.element(vm.ctx.$container).find('.searchInput').focus(); + }) + } + + function exitFilterMode () { + vm.query.search = null; + updateSearchNodes(); + vm.ctx.hideTitlePanel = false; + } + + function searchCallback (searchText, node) { + var theNode = vm.nodesMap[node.id]; + if (theNode && theNode.data.searchText) { + return theNode.data.searchText.includes(searchText.toLowerCase()); + } + return false; + } + function updateDatasources() { vm.loadNodes = loadNodes; } + function updateSearchNodes() { + if (vm.query.search != null) { + vm.nodeEditCallbacks.search(vm.query.search); + } else { + vm.nodeEditCallbacks.clearSearch(); + } + } + function onNodesInserted(nodes/*, parent*/) { if (nodes) { nodes.forEach((nodeId) => { @@ -185,64 +291,109 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty return nodeIcon + nodeText; } - function prepareEgeGroupText(node) { - var nodeIcon = prepareNodeIcon(node.data.nodeCtx); - var nodeText = "Edge rule chains"; - node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; - return nodeIcon + nodeText; - } - function loadNodes(node, cb) { var datasource = vm.datasources[0]; if (node.id === '#' && datasource) { - if (datasource.type === 'entity' && datasource.entity.id.entityType === types.entityType.edge) { + if (datasource.type === types.datasourceType.entity && datasource.entity.id.entityType === types.entityType.edge) { var selectedEdge = datasource.entity; + getCustomerTitle(selectedEdge.id.id); vm.ctx.widgetTitle = selectedEdge.name; - cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)) - } - } else { + cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)); + } else if (datasource.type === types.datasourceType.function) { + cb(loadNodesForEdge(null, null)); + } else { + vm.edgeIsDatasource = false; cb([]); + } + } else if (node.data && node.data.entity && node.data.entity.id.entityType === types.entityType.edge) { + var edgeId = node.data.entity.id.id; + var entityType = node.data.entityType; + var pageLink = {limit: 100}; + entityService.getAssignedToEdgeEntitiesByType(edgeId, entityType, pageLink).then( + (entities) => { + if (entities.data.length > 0) { + cb(entitiesToNodes(node.id, entities.data)); + } else { + cb([]); + } + } + ) + } else { + cb([]); } } + function entitiesToNodes(parentNodeId, entities) { + var nodes = []; + vm.entityNodesMap[parentNodeId] = {}; + if (entities) { + entities.forEach( + (entity) => { + var node = createEntityNode(parentNodeId, entity, entity.id.entityType); + nodes.push(node); + } + ); + } + return nodes; + } + + function createEntityNode(parentNodeId, entity, entityType) { + var nodesMap = vm.entityNodesMap[parentNodeId]; + if (!nodesMap) { + nodesMap = {}; + vm.entityNodesMap[parentNodeId] = nodesMap; + } + var node = { + id: ++vm.nodeIdCounter, + icon: 'material-icons ' + iconForGroupType(entityType), + text: entity.name, + children: false, + data: { + entity, + internalId: entity.id.id + } + }; + nodesMap[entity.id.id] = node.id; + return node; + } + function loadNodesForEdge(parentNodeId, entity) { var nodes = []; - var nodesMap = {}; - vm.entityGroupsNodesMap[parentNodeId] = nodesMap; - var user = userService.getCurrentUser(); - var allowedGroupTypes = vm.edgeGroupsTypes; - if (user.authority === 'CUSTOMER_USER') { - allowedGroupTypes = vm.edgeGroupsTypes.filter(type => type !== types.entityType.rulechain) + vm.entityGroupsNodesMap[parentNodeId] = {}; + var allowedGroupTypes = edgeGroupsTypes; + if (userService.getAuthority() === 'CUSTOMER_USER') { + allowedGroupTypes = edgeGroupsTypes.filter(type => type !== types.entityType.rulechain); } - allowedGroupTypes.forEach((groupType) => { - var node = { - id: ++vm.nodeIdCounter, - icon: 'material-icons ' + iconForGroupType(groupType), - text: textForGroupType(groupType), - children: true, - data: { - groupType, - entity, - internalId: entity.id.id + '_' + groupType - } - }; - nodes.push(node); - nodesMap[groupType] = node.id; - }); + allowedGroupTypes.forEach( + (entityType) => { + var node = { + id: ++vm.nodeIdCounter, + icon: 'material-icons ' + iconForGroupType(entityType), + text: textForGroupType(entityType), + children: true, + data: { + entityType, + entity, + internalId: entity ? entity.id.id + '_' + entityType : utils.guid() + } + }; + nodes.push(node); + } + ) return nodes; } function iconForGroupType(groupType) { switch (groupType) { - case vm.types.entityType.asset: + case types.entityType.asset: return 'tb-asset-group'; - case vm.types.entityType.device: + case types.entityType.device: return 'tb-device-group'; - case vm.types.entityType.entityView: + case types.entityType.entityView: return 'tb-entity-view-group'; - case vm.types.entityType.dashboard: + case types.entityType.dashboard: return 'tb-dashboard-group'; - case vm.types.entityType.rulechain: + case types.entityType.rulechain: return 'tb-rule-chain-group'; } return ''; @@ -250,22 +401,35 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty function textForGroupType(groupType) { switch (groupType) { - case vm.types.entityType.asset: + case types.entityType.asset: return $translate.instant('asset.assets'); - case vm.types.entityType.device: + case types.entityType.device: return $translate.instant('device.devices'); - case vm.types.entityType.entityView: + case types.entityType.entityView: return $translate.instant('entity-view.entity-views'); - case vm.types.entityType.rulechain: + case types.entityType.rulechain: return $translate.instant('rulechain.rulechains'); - case vm.types.entityType.dashboard: + case types.entityType.dashboard: return $translate.instant('dashboard.dashboards'); } return ''; } - function edgeGroupsNodeText(entityType) { + function getCustomerTitle(edgeId) { + edgeService.getEdge(edgeId, true).then( + function success(edge) { + customerService.getShortCustomerInfo(edge.customerId.id).then( + function success(customer) { + vm.customerTitle = $translate.instant('edge.assigned-to-customer-widget', {customerTitle: customer.title}); + } + ); + } + ) + } + function showError(errorText) { + var toastParent = angular.element('.tb-entities-hierarchy', $element); + toast.showError(errorText, toastParent, 'bottom left'); } function prepareNodes(nodes) { @@ -310,52 +474,7 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty return deferred.promise; } - function edgeGroupToNode(entityType, parentDatasource, parentNodeCtx) { - var deferred = $q.defer(); - deferred.resolve(getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx)); - return deferred.promise; - } - - function getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx) { - var datasource = { - dataKeys: parentDatasource.dataKeys, - entityId: parentDatasource.entity.id.id + '_' + entityType, - type: "edgeGroup", - entityType: entityType - }; - - var node = { - id: ++vm.nodeIdCounter - }; - vm.nodesMap[node.id] = node; - datasource.nodeId = node.id; - node.icon = false; - var nodeCtx = { - parentNodeCtx: 3, - data: {}, - entity: { - id: { - id: parentDatasource.entity.id.id + '_' + entityType, - entityType: 'group' - }, - name: "Edge Group RC" - } - }; - nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; - node.data = { - datasource: datasource, - nodeCtx: nodeCtx - }; - node.state = { - disabled: vm.nodeDisabledFunction(node.data.nodeCtx), - opened: vm.nodeOpenedFunction(node.data.nodeCtx) - }; - node.text = prepareEgeGroupText(node); - node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); - return node; - } - - function entityIdToEdgeGroupNode(entityType, entityId, parentDatasource, parentNodeCtx) { + function entityIdToNode(entityType, entityId, parentDatasource, parentNodeCtx) { var deferred = $q.defer(); var datasource = { dataKeys: parentDatasource.dataKeys, @@ -411,6 +530,35 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty return deferred.promise; } + + function prepareNodeRelationQuery(nodeCtx) { + var relationQuery = vm.nodeRelationQueryFunction(nodeCtx); + if (relationQuery && relationQuery === 'default') { + relationQuery = defaultNodeRelationQueryFunction(nodeCtx); + } + return relationQuery; + } + + function defaultNodeRelationQueryFunction(nodeCtx) { + var entity = nodeCtx.entity; + var query = { + parameters: { + rootId: entity.id.id, + rootType: entity.id.entityType, + direction: types.entitySearchDirection.from, + relationTypeGroup: "COMMON", + maxLevel: 1 + }, + filters: [ + { + relationType: "Contains", + entityTypes: [] + } + ] + }; + return query; + } + function prepareNodeIcon(nodeCtx) { var iconInfo = vm.nodeIconFunction(nodeCtx); if (iconInfo && iconInfo === 'default') { @@ -470,9 +618,6 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty case types.entityType.edge: materialIcon = 'router'; break; - case types.entityType.rulechain: - materialIcon = 'settings_ethernet'; - break; } } return { @@ -480,4 +625,15 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, ty }; } + function defaultNodeOpenedFunction(nodeCtx) { + return nodeCtx.level <= 4; + } + + function defaultSortFunction(nodeCtx1, nodeCtx2) { + var result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType); + if (result === 0) { + result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name); + } + return result; + } } diff --git a/ui/src/app/widget/lib/edges-overview-widget.scss b/ui/src/app/widget/lib/edges-overview-widget.scss index cd2d419ef2..fcb508fea1 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.scss +++ b/ui/src/app/widget/lib/edges-overview-widget.scss @@ -19,6 +19,13 @@ min-height: 60px; max-height: 60px; } + + span { + padding: 15px; + font-size: 14px; + font-weight: 400; + opacity: .5; + } } } diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html index feeefd1013..93a7349fbb 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.tpl.html +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -17,9 +17,35 @@ -->
+ {{ customerTitle }} + +
+ + search + + {{'entity.search' | translate}} + + + + + + + + close + + {{ 'action.close' | translate }} + + +
+
From f1d5d18c92d38cc072b62e545fdb8321a59fefc9 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Mon, 11 Jan 2021 06:11:58 +0200 Subject: [PATCH 06/23] Edges overview widget customerTitle --- ui/src/app/widget/lib/edges-overview-widget.scss | 14 +++++++------- .../app/widget/lib/edges-overview-widget.tpl.html | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/src/app/widget/lib/edges-overview-widget.scss b/ui/src/app/widget/lib/edges-overview-widget.scss index fcb508fea1..f5fcc90f8f 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.scss +++ b/ui/src/app/widget/lib/edges-overview-widget.scss @@ -19,13 +19,6 @@ min-height: 60px; max-height: 60px; } - - span { - padding: 15px; - font-size: 14px; - font-weight: 400; - opacity: .5; - } } } @@ -35,6 +28,13 @@ max-height: 39px; } + .mat-subheader { + padding: 15px; + font-size: 14px; + font-weight: 400; + opacity: .5; + } + .tb-entities-nav-tree-panel { overflow-x: auto; overflow-y: auto; diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html index 93a7349fbb..9a107beb6d 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.tpl.html +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -17,7 +17,7 @@ -->
- {{ customerTitle }} + {{ vm.customerTitle }}
From 8648eadea45f0458bb0bcf28e7c27aec91ffb6d6 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Mon, 11 Jan 2021 06:30:59 +0200 Subject: [PATCH 07/23] Edges overview widget CustomerInfo updated --- ui/src/app/locale/locale.constant-en_US.json | 3 ++- ui/src/app/widget/lib/edges-overview-widget.js | 1 + ui/src/app/widget/lib/edges-overview-widget.tpl.html | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index e4aedb319f..c064e6d83f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -857,7 +857,8 @@ "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}", "downlinks": "Downlinks", "no-downlinks-prompt": "No downlinks found", - "assigned-to-customer-widget": "Assigned to: {{customerTitle}}" + "assigned-to-customer-widget": "Assigned to: {{customerTitle}}", + "widget-datasource-error": "This widget supports only EDGE entity datasource" }, "edge-event": { "type-dashboard": "Dashboard", diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index 396bc1a23e..f70539ea11 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -68,6 +68,7 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translat }; vm.customerTitle = null; + vm.edgeIsDatasource = true; var edgeGroupsTypes = [ types.entityType.asset, diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html index 9a107beb6d..b85d22589d 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.tpl.html +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -17,7 +17,8 @@ -->
- {{ vm.customerTitle }} + {{ vm.customerTitle }} + edge.widget-datasource-error
From 743d2440aa319262289099b190d58277a99b3808 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Mon, 11 Jan 2021 06:40:08 +0200 Subject: [PATCH 08/23] Edges overview widget template cleanup --- .../app/widget/lib/edges-overview-widget.js | 2 +- .../widget/lib/edges-overview-widget.tpl.html | 29 ++----------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index f70539ea11..aa549a8b10 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -295,7 +295,7 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translat function loadNodes(node, cb) { var datasource = vm.datasources[0]; if (node.id === '#' && datasource) { - if (datasource.type === types.datasourceType.entity && datasource.entity.id.entityType === types.entityType.edge) { + if (datasource.type === types.datasourceType.entity && datasource.entity && datasource.entity.id.entityType === types.entityType.edge) { var selectedEdge = datasource.entity; getCustomerTitle(selectedEdge.id.id); vm.ctx.widgetTitle = selectedEdge.name; diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html index b85d22589d..be6e75d16d 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.tpl.html +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -17,36 +17,11 @@ -->
- {{ vm.customerTitle }} - edge.widget-datasource-error - -
- - search - - {{'entity.search' | translate}} - - - - - - - - close - - {{ 'action.close' | translate }} - - -
-
+ {{ vm.customerTitle }} + edge.widget-datasource-error
From 17ed82876ae0f8cc3ed5259e8eb18d8038cbc76d Mon Sep 17 00:00:00 2001 From: deaflynx Date: Mon, 11 Jan 2021 10:54:18 +0200 Subject: [PATCH 09/23] Edges overview widget cleanup --- .../widget/lib/edges-overview-widget-copy.js | 483 ------------------ .../lib/edges-overview-widget-copy.scss | 109 ---- .../app/widget/lib/edges-overview-widget.js | 447 +--------------- .../app/widget/lib/edges-overview-widget.scss | 56 -- 4 files changed, 13 insertions(+), 1082 deletions(-) delete mode 100644 ui/src/app/widget/lib/edges-overview-widget-copy.js delete mode 100644 ui/src/app/widget/lib/edges-overview-widget-copy.scss diff --git a/ui/src/app/widget/lib/edges-overview-widget-copy.js b/ui/src/app/widget/lib/edges-overview-widget-copy.js deleted file mode 100644 index 56921b264c..0000000000 --- a/ui/src/app/widget/lib/edges-overview-widget-copy.js +++ /dev/null @@ -1,483 +0,0 @@ -// /* -// * Copyright © 2016-2020 The Thingsboard Authors -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// import './edges-overview-widget-copy.scss'; -// -// /* eslint-disable import/no-unresolved, import/default */ -// -// import edgesOverviewWidgetTemplate from './edges-overview-widget.tpl.html'; -// -// /* eslint-enable import/no-unresolved, import/default */ -// -// export default angular.module('thingsboard.widgets.edgesOverviewWidget2', []) -// .directive('tbEdgesOverviewWidget', EdgesOverviewWidget) -// .name; -// /* eslint-disable no-unused-vars, no-undef */ -// /*@ngInject*/ -// function EdgesOverviewWidget() { -// return { -// restrict: "E", -// scope: true, -// bindToController: { -// hierarchyId: '=', -// ctx: '=' -// }, -// controller: EdgesOverviewWidgetController, -// controllerAs: 'vm', -// templateUrl: edgesOverviewWidgetTemplate -// }; -// } -// -// /*@ngInject*/ -// function EdgesOverviewWidgetController($element, $scope, $q, $timeout, toast, types, entityService, userService, entityRelationService, -// assetService, deviceService, entityViewService, dashboardService, ruleChainService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) { -// var vm = this; -// -// vm.showData = true; -// -// vm.nodeEditCallbacks = {}; -// -// vm.nodeIdCounter = 0; -// -// vm.nodesMap = {}; -// vm.entityGroupsNodesMap = {}; -// vm.pendingUpdateNodeTasks = {}; -// -// vm.query = { -// search: null -// }; -// -// vm.edgeGroupsTypes = [ -// types.entityType.asset, -// types.entityType.device, -// types.entityType.entityView, -// types.entityType.dashboard, -// types.entityType.rulechain, -// ] -// -// vm.onNodesInserted = onNodesInserted; -// vm.onNodeSelected = onNodeSelected; -// -// $scope.$watch('vm.ctx', function() { -// if (vm.ctx && vm.ctx.defaultSubscription) { -// vm.settings = vm.ctx.settings; -// vm.widgetConfig = vm.ctx.widgetConfig; -// vm.subscription = vm.ctx.defaultSubscription; -// vm.datasources = vm.subscription.datasources; -// updateDatasources(); -// } -// }); -// -// $scope.$on('edges-overview-data-updated', function(event, hierarchyId) { -// if (vm.hierarchyId == hierarchyId) { -// if (vm.subscription) { -// updateNodeData(vm.subscription.data); -// } -// } -// }); -// -// function updateDatasources() { -// vm.loadNodes = loadNodes; -// } -// -// function onNodesInserted(nodes/*, parent*/) { -// if (nodes) { -// nodes.forEach((nodeId) => { -// var task = vm.pendingUpdateNodeTasks[nodeId]; -// if (task) { -// task(); -// delete vm.pendingUpdateNodeTasks[nodeId]; -// } -// }); -// } -// } -// -// function onNodeSelected(node, event) { -// var nodeId; -// if (!node) { -// nodeId = -1; -// } else { -// nodeId = node.id; -// } -// if (nodeId !== -1) { -// var selectedNode = vm.nodesMap[nodeId]; -// if (selectedNode) { -// var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected'); -// if (descriptors.length) { -// var entity = selectedNode.data.nodeCtx.entity; -// vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); -// } -// } -// } -// } -// -// function updateNodeData(subscriptionData) { -// var affectedNodes = []; -// if (subscriptionData) { -// for (var i=0;i { -// var node = vm.nodeEditCallbacks.getNode(nodeId); -// if (node) { -// updateNodeStyle(vm.nodesMap[nodeId]); -// } else { -// vm.pendingUpdateNodeTasks[nodeId] = () => { -// updateNodeStyle(vm.nodesMap[nodeId]); -// }; -// } -// }); -// } -// -// function updateNodeStyle(node) { -// var newText = prepareNodeText(node); -// if (!angular.equals(node.text, newText)) { -// node.text = newText; -// vm.nodeEditCallbacks.updateNode(node.id, node.text); -// } -// var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx); -// if (!angular.equals(node.state.disabled, newDisabled)) { -// node.state.disabled = newDisabled; -// if (node.state.disabled) { -// vm.nodeEditCallbacks.disableNode(node.id); -// } else { -// vm.nodeEditCallbacks.enableNode(node.id); -// } -// } -// var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx); -// if (!angular.equals(node.children, newHasChildren)) { -// node.children = newHasChildren; -// vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children); -// } -// } -// -// function prepareNodeText(node) { -// var nodeIcon = prepareNodeIcon(node.data.nodeCtx); -// var nodeText = vm.nodeTextFunction(node.data.nodeCtx); -// node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; -// return nodeIcon + nodeText; -// } -// -// function prepareEgeGroupText(node) { -// var nodeIcon = prepareNodeIcon(node.data.nodeCtx); -// var nodeText = "Edge rule chains"; -// node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; -// return nodeIcon + nodeText; -// } -// -// function loadNodes(node, cb) { -// var datasource = vm.datasources[0]; -// if (node.id === '#' && datasource) { -// if (datasource.type === 'entity' && datasource.entity.id.entityType === types.entityType.edge) { -// var selectedEdge = datasource.entity; -// vm.ctx.widgetTitle = selectedEdge.name; -// cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)) -// } -// } else { -// cb([]); -// } -// } -// -// function loadNodesForEdge(parentNodeId, entity) { -// var nodes = []; -// var nodesMap = {}; -// vm.entityGroupsNodesMap[parentNodeId] = nodesMap; -// var user = userService.getCurrentUser(); -// var allowedGroupTypes = vm.edgeGroupsTypes; -// if (user.authority === 'CUSTOMER_USER') { -// allowedGroupTypes = vm.edgeGroupsTypes.filter(type => type !== types.entityType.rulechain) -// } -// allowedGroupTypes.forEach((groupType) => { -// var node = { -// id: ++vm.nodeIdCounter, -// icon: 'material-icons ' + iconForGroupType(groupType), -// text: textForGroupType(groupType), -// children: true, -// data: { -// groupType, -// entity, -// internalId: entity.id.id + '_' + groupType -// } -// }; -// nodes.push(node); -// nodesMap[groupType] = node.id; -// }); -// return nodes; -// } -// -// function iconForGroupType(groupType) { -// switch (groupType) { -// case vm.types.entityType.asset: -// return 'tb-asset-group'; -// case vm.types.entityType.device: -// return 'tb-device-group'; -// case vm.types.entityType.entityView: -// return 'tb-entity-view-group'; -// case vm.types.entityType.dashboard: -// return 'tb-dashboard-group'; -// case vm.types.entityType.rulechain: -// return 'tb-rule-chain-group'; -// } -// return ''; -// } -// -// function textForGroupType(groupType) { -// switch (groupType) { -// case vm.types.entityType.asset: -// return $translate.instant('asset.assets'); -// case vm.types.entityType.device: -// return $translate.instant('device.devices'); -// case vm.types.entityType.entityView: -// return $translate.instant('entity-view.entity-views'); -// case vm.types.entityType.rulechain: -// return $translate.instant('rulechain.rulechains'); -// case vm.types.entityType.dashboard: -// return $translate.instant('dashboard.dashboards'); -// } -// return ''; -// } -// -// function edgeGroupsNodeText(entityType) { -// -// } -// -// function prepareNodes(nodes) { -// nodes = nodes.filter((node) => node !== null); -// nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); -// return nodes; -// } -// -// function datasourceToNode(datasource, parentNodeCtx) { -// var deferred = $q.defer(); -// resolveEntity(datasource).then( -// (entity) => { -// if (entity != null) { -// var node = { -// id: ++vm.nodeIdCounter -// }; -// vm.nodesMap[node.id] = node; -// datasource.nodeId = node.id; -// node.icon = false; -// var nodeCtx = { -// parentNodeCtx: parentNodeCtx, -// entity: entity, -// data: {} -// }; -// nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; -// node.data = { -// datasource: datasource, -// nodeCtx: nodeCtx -// }; -// node.state = { -// disabled: vm.nodeDisabledFunction(node.data.nodeCtx), -// opened: vm.nodeOpenedFunction(node.data.nodeCtx) -// }; -// node.text = prepareNodeText(node); -// node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); -// deferred.resolve(node); -// } else { -// deferred.resolve(null); -// } -// } -// ); -// return deferred.promise; -// } -// -// function edgeGroupToNode(entityType, parentDatasource, parentNodeCtx) { -// var deferred = $q.defer(); -// deferred.resolve(getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx)); -// return deferred.promise; -// } -// -// function getEdgeGroupNode(entityType, parentDatasource, parentNodeCtx) { -// var datasource = { -// dataKeys: parentDatasource.dataKeys, -// entityId: parentDatasource.entity.id.id + '_' + entityType, -// type: "edgeGroup", -// entityType: entityType -// }; -// -// var node = { -// id: ++vm.nodeIdCounter -// }; -// vm.nodesMap[node.id] = node; -// datasource.nodeId = node.id; -// node.icon = false; -// var nodeCtx = { -// parentNodeCtx: 3, -// data: {}, -// entity: { -// id: { -// id: parentDatasource.entity.id.id + '_' + entityType, -// entityType: 'group' -// }, -// name: "Edge Group RC" -// } -// }; -// nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; -// node.data = { -// datasource: datasource, -// nodeCtx: nodeCtx -// }; -// node.state = { -// disabled: vm.nodeDisabledFunction(node.data.nodeCtx), -// opened: vm.nodeOpenedFunction(node.data.nodeCtx) -// }; -// node.text = prepareEgeGroupText(node); -// node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); -// return node; -// } -// -// function entityIdToEdgeGroupNode(entityType, entityId, parentDatasource, parentNodeCtx) { -// var deferred = $q.defer(); -// var datasource = { -// dataKeys: parentDatasource.dataKeys, -// type: types.datasourceType.entity, -// entityType: entityType, -// entityId: entityId -// }; -// datasourceToNode(datasource, parentNodeCtx).then( -// (node) => { -// if (node != null) { -// var subscriptionOptions = { -// type: types.widgetType.latest.value, -// datasources: [datasource], -// callbacks: { -// onDataUpdated: (subscription) => { -// updateNodeData(subscription.data); -// } -// } -// }; -// vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then( -// (/*subscription*/) => { -// deferred.resolve(node); -// } -// ); -// } else { -// deferred.resolve(node); -// } -// } -// ); -// return deferred.promise; -// } -// -// function resolveEntity(datasource) { -// var deferred = $q.defer(); -// if (datasource.type === types.datasourceType.function) { -// var entity = { -// id: { -// entityType: "function" -// }, -// name: datasource.name -// } -// deferred.resolve(entity); -// } else { -// entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then( -// (entity) => { -// deferred.resolve(entity); -// }, -// () => { -// deferred.resolve(null); -// } -// ); -// } -// return deferred.promise; -// } -// -// function prepareNodeIcon(nodeCtx) { -// var iconInfo = vm.nodeIconFunction(nodeCtx); -// if (iconInfo && iconInfo === 'default') { -// iconInfo = defaultNodeIconFunction(nodeCtx); -// } -// if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) { -// if (iconInfo.materialIcon) { -// return materialIconHtml(iconInfo.materialIcon); -// } else { -// return iconUrlHtml(iconInfo.iconUrl); -// } -// } else { -// return ""; -// } -// } -// -// function materialIconHtml(materialIcon) { -// return ''+materialIcon+''; -// } -// -// function iconUrlHtml(iconUrl) { -// return '
 
'; -// } -// -// function defaultNodeIconFunction(nodeCtx) { -// var materialIcon = 'insert_drive_file'; -// var entity = nodeCtx.entity; -// if (entity && entity.id && entity.id.entityType) { -// switch (entity.id.entityType) { -// case 'function': -// materialIcon = 'functions'; -// break; -// case types.entityType.device: -// materialIcon = 'devices_other'; -// break; -// case types.entityType.asset: -// materialIcon = 'domain'; -// break; -// case types.entityType.tenant: -// materialIcon = 'supervisor_account'; -// break; -// case types.entityType.customer: -// materialIcon = 'supervisor_account'; -// break; -// case types.entityType.user: -// materialIcon = 'account_circle'; -// break; -// case types.entityType.dashboard: -// materialIcon = 'dashboards'; -// break; -// case types.entityType.alarm: -// materialIcon = 'notifications_active'; -// break; -// case types.entityType.entityView: -// materialIcon = 'view_quilt'; -// break; -// case types.entityType.edge: -// materialIcon = 'router'; -// break; -// case types.entityType.rulechain: -// materialIcon = 'settings_ethernet'; -// break; -// } -// } -// return { -// materialIcon: materialIcon -// }; -// } -// -// } diff --git a/ui/src/app/widget/lib/edges-overview-widget-copy.scss b/ui/src/app/widget/lib/edges-overview-widget-copy.scss deleted file mode 100644 index 59d335b2fa..0000000000 --- a/ui/src/app/widget/lib/edges-overview-widget-copy.scss +++ /dev/null @@ -1,109 +0,0 @@ -///** -// * Copyright © 2016-2020 The Thingsboard Authors -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -//.tb-has-timewindow { -// .tb-edges-overview { -// md-toolbar { -// min-height: 60px; -// max-height: 60px; -// } -// } -//} -// -//.tb-edges-overview { -// md-toolbar { -// min-height: 39px; -// max-height: 39px; -// } -// -// .tb-entities-nav-tree-panel { -// overflow-x: auto; -// overflow-y: auto; -// -// .tb-nav-tree-container { -// &.jstree-proton { -// .jstree-anchor { -// div.node-icon { -// display: inline-block; -// width: 22px; -// height: 22px; -// margin-right: 2px; -// margin-bottom: 2px; -// background-color: transparent; -// background-repeat: no-repeat; -// background-attachment: scroll; -// background-position: center center; -// background-size: 18px 18px; -// } -// -// md-icon.node-icon { -// width: 22px; -// min-width: 22px; -// height: 22px; -// min-height: 22px; -// margin-right: 2px; -// margin-bottom: 2px; -// color: inherit; -// -// &.material-icons { /* stylelint-disable-line selector-max-class */ -// font-size: 18px; -// line-height: 22px; -// text-align: center; -// } -// } -// -// &.jstree-hovered:not(.jstree-clicked), -// &.jstree-disabled { -// div.node-icon { /* stylelint-disable-line selector-max-class */ -// opacity: .5; -// } -// } -// } -// } -// } -// } -//} -// -//@media (max-width: 768px) { -// .tb-edges-overview { -// .tb-entities-nav-tree-panel { -// .tb-nav-tree-container { -// &.jstree-proton-responsive { -// .jstree-anchor { -// div.node-icon { -// width: 40px; -// height: 40px; -// margin: 0; -// background-size: 24px 24px; -// } -// -// md-icon.node-icon { -// width: 40px; -// min-width: 40px; -// height: 40px; -// min-height: 40px; -// margin: 0; -// -// &.material-icons { /* stylelint-disable-line selector-max-class */ -// font-size: 24px; -// line-height: 40px; -// } -// } -// } -// } -// } -// } -// } -//} diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index aa549a8b10..095500e5b5 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -40,33 +40,17 @@ function EdgesOverviewWidget() { } /*@ngInject*/ -function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translate, toast, types, utils, entityService, edgeService, customerService, userService /*$filter, $mdMedia, $mdPanel, $document, $timeout, utils, types*/) { +function EdgesOverviewWidgetController($scope, $translate, types, utils, entityService, edgeService, customerService, userService) { var vm = this; vm.showData = true; - vm.nodeEditCallbacks = {}; - vm.nodeIdCounter = 0; - vm.nodesMap = {}; vm.entityNodesMap = {}; vm.entityGroupsNodesMap = {}; vm.pendingUpdateNodeTasks = {}; - vm.query = { - search: null - }; - - vm.searchAction = { - name: 'action.search', - show: true, - onAction: function() { - vm.enterFilterMode(); - }, - icon: 'search' - }; - vm.customerTitle = null; vm.edgeIsDatasource = true; @@ -78,226 +62,26 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translat types.entityType.rulechain, ] - vm.onNodesInserted = onNodesInserted; - vm.onNodeSelected = onNodeSelected; - vm.enterFilterMode = enterFilterMode; - vm.exitFilterMode = exitFilterMode; - vm.searchCallback = searchCallback; - $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { vm.settings = vm.ctx.settings; vm.widgetConfig = vm.ctx.widgetConfig; vm.subscription = vm.ctx.defaultSubscription; vm.datasources = vm.subscription.datasources; - initializeConfig(); updateDatasources(); } }); - $scope.$watch("vm.query.search", function(newVal, prevVal) { - if (!angular.equals(newVal, prevVal) && vm.query.search != null) { - updateSearchNodes(); - } - }); - - $scope.$on('edges-overview-data-updated', function(event, hierarchyId) { - if (vm.hierarchyId == hierarchyId) { - if (vm.subscription) { - updateNodeData(vm.subscription.data); - } - } - }); - - function initializeConfig() { - - vm.ctx.widgetActions = [ vm.searchAction ]; - - var testNodeCtx = { - entity: { - id: { - entityType: 'DEVICE', - id: '123' - }, - name: 'TEST DEV1' - }, - data: {}, - level: 2 - }; - var parentNodeCtx = angular.copy(testNodeCtx); - parentNodeCtx.level = 1; - testNodeCtx.parentNodeCtx = parentNodeCtx; - - var nodeRelationQueryFunction = loadNodeCtxFunction(vm.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx); - var nodeIconFunction = loadNodeCtxFunction(vm.settings.nodeIconFunction, 'nodeCtx', testNodeCtx); - var nodeTextFunction = loadNodeCtxFunction(vm.settings.nodeTextFunction, 'nodeCtx', testNodeCtx); - var nodeDisabledFunction = loadNodeCtxFunction(vm.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx); - var nodeOpenedFunction = loadNodeCtxFunction(vm.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx); - var nodeHasChildrenFunction = loadNodeCtxFunction(vm.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx); - - var testNodeCtx2 = angular.copy(testNodeCtx); - testNodeCtx2.entity.name = 'TEST DEV2'; - - var nodesSortFunction = loadNodeCtxFunction(vm.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2); - - vm.nodeRelationQueryFunction = nodeRelationQueryFunction || defaultNodeRelationQueryFunction; - vm.nodeIconFunction = nodeIconFunction || defaultNodeIconFunction; - vm.nodeTextFunction = nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name); - vm.nodeDisabledFunction = nodeDisabledFunction || (() => false); - vm.nodeOpenedFunction = nodeOpenedFunction || defaultNodeOpenedFunction; - vm.nodeHasChildrenFunction = nodeHasChildrenFunction || (() => true); - vm.nodesSortFunction = nodesSortFunction || defaultSortFunction; - } - - function loadNodeCtxFunction(functionBody, argNames, ...args) { - var nodeCtxFunction = null; - if (angular.isDefined(functionBody) && functionBody.length) { - try { - nodeCtxFunction = new Function(argNames, functionBody); - var res = nodeCtxFunction.apply(null, args); - if (angular.isUndefined(res)) { - nodeCtxFunction = null; - } - } catch (e) { - nodeCtxFunction = null; - } - } - return nodeCtxFunction; - } - - function enterFilterMode () { - vm.query.search = ''; - vm.ctx.hideTitlePanel = true; - $timeout(()=>{ - angular.element(vm.ctx.$container).find('.searchInput').focus(); - }) - } - - function exitFilterMode () { - vm.query.search = null; - updateSearchNodes(); - vm.ctx.hideTitlePanel = false; - } - - function searchCallback (searchText, node) { - var theNode = vm.nodesMap[node.id]; - if (theNode && theNode.data.searchText) { - return theNode.data.searchText.includes(searchText.toLowerCase()); - } - return false; - } - function updateDatasources() { vm.loadNodes = loadNodes; } - function updateSearchNodes() { - if (vm.query.search != null) { - vm.nodeEditCallbacks.search(vm.query.search); - } else { - vm.nodeEditCallbacks.clearSearch(); - } - } - - function onNodesInserted(nodes/*, parent*/) { - if (nodes) { - nodes.forEach((nodeId) => { - var task = vm.pendingUpdateNodeTasks[nodeId]; - if (task) { - task(); - delete vm.pendingUpdateNodeTasks[nodeId]; - } - }); - } - } - - function onNodeSelected(node, event) { - var nodeId; - if (!node) { - nodeId = -1; - } else { - nodeId = node.id; - } - if (nodeId !== -1) { - var selectedNode = vm.nodesMap[nodeId]; - if (selectedNode) { - var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected'); - if (descriptors.length) { - var entity = selectedNode.data.nodeCtx.entity; - vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); - } - } - } - } - - function updateNodeData(subscriptionData) { - var affectedNodes = []; - if (subscriptionData) { - for (var i=0;i { - var node = vm.nodeEditCallbacks.getNode(nodeId); - if (node) { - updateNodeStyle(vm.nodesMap[nodeId]); - } else { - vm.pendingUpdateNodeTasks[nodeId] = () => { - updateNodeStyle(vm.nodesMap[nodeId]); - }; - } - }); - } - - function updateNodeStyle(node) { - var newText = prepareNodeText(node); - if (!angular.equals(node.text, newText)) { - node.text = newText; - vm.nodeEditCallbacks.updateNode(node.id, node.text); - } - var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx); - if (!angular.equals(node.state.disabled, newDisabled)) { - node.state.disabled = newDisabled; - if (node.state.disabled) { - vm.nodeEditCallbacks.disableNode(node.id); - } else { - vm.nodeEditCallbacks.enableNode(node.id); - } - } - var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx); - if (!angular.equals(node.children, newHasChildren)) { - node.children = newHasChildren; - vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children); - } - } - - function prepareNodeText(node) { - var nodeIcon = prepareNodeIcon(node.data.nodeCtx); - var nodeText = vm.nodeTextFunction(node.data.nodeCtx); - node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ""; - return nodeIcon + nodeText; - } - function loadNodes(node, cb) { var datasource = vm.datasources[0]; if (node.id === '#' && datasource) { if (datasource.type === types.datasourceType.entity && datasource.entity && datasource.entity.id.entityType === types.entityType.edge) { var selectedEdge = datasource.entity; - getCustomerTitle(selectedEdge.id.id); + vm.customerTitle = getCustomerTitle(selectedEdge.id.id); vm.ctx.widgetTitle = selectedEdge.name; cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)); } else if (datasource.type === types.datasourceType.function) { @@ -366,16 +150,16 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translat allowedGroupTypes = edgeGroupsTypes.filter(type => type !== types.entityType.rulechain); } allowedGroupTypes.forEach( - (entityType) => { + (groupType) => { var node = { id: ++vm.nodeIdCounter, - icon: 'material-icons ' + iconForGroupType(entityType), - text: textForGroupType(entityType), + icon: 'material-icons ' + iconForGroupType(groupType), + text: textForGroupType(groupType), children: true, data: { - entityType, + groupType, entity, - internalId: entity ? entity.id.id + '_' + entityType : utils.guid() + internalId: entity ? entity.id.id + '_' + groupType : utils.guid() } }; nodes.push(node); @@ -419,222 +203,17 @@ function EdgesOverviewWidgetController($element, $scope, $q, $timeout, $translat function getCustomerTitle(edgeId) { edgeService.getEdge(edgeId, true).then( function success(edge) { - customerService.getShortCustomerInfo(edge.customerId.id).then( + customerService.getCustomer(edge.customerId.id, { ignoreErrors: true }).then( function success(customer) { - vm.customerTitle = $translate.instant('edge.assigned-to-customer-widget', {customerTitle: customer.title}); + vm.customerTitle = $translate.instant('edge.assigned-to-customer-widget', { customerTitle: customer.title }); + }, + function fail() { } ); + }, + function fail() { } ) } - function showError(errorText) { - var toastParent = angular.element('.tb-entities-hierarchy', $element); - toast.showError(errorText, toastParent, 'bottom left'); - } - - function prepareNodes(nodes) { - nodes = nodes.filter((node) => node !== null); - nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); - return nodes; - } - - function datasourceToNode(datasource, parentNodeCtx) { - var deferred = $q.defer(); - resolveEntity(datasource).then( - (entity) => { - if (entity != null) { - var node = { - id: ++vm.nodeIdCounter - }; - vm.nodesMap[node.id] = node; - datasource.nodeId = node.id; - node.icon = false; - var nodeCtx = { - parentNodeCtx: parentNodeCtx, - entity: entity, - data: {} - }; - nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; - node.data = { - datasource: datasource, - nodeCtx: nodeCtx - }; - node.state = { - disabled: vm.nodeDisabledFunction(node.data.nodeCtx), - opened: vm.nodeOpenedFunction(node.data.nodeCtx) - }; - node.text = prepareNodeText(node); - node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx); - deferred.resolve(node); - } else { - deferred.resolve(null); - } - } - ); - return deferred.promise; - } - - function entityIdToNode(entityType, entityId, parentDatasource, parentNodeCtx) { - var deferred = $q.defer(); - var datasource = { - dataKeys: parentDatasource.dataKeys, - type: types.datasourceType.entity, - entityType: entityType, - entityId: entityId - }; - datasourceToNode(datasource, parentNodeCtx).then( - (node) => { - if (node != null) { - var subscriptionOptions = { - type: types.widgetType.latest.value, - datasources: [datasource], - callbacks: { - onDataUpdated: (subscription) => { - updateNodeData(subscription.data); - } - } - }; - vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then( - (/*subscription*/) => { - deferred.resolve(node); - } - ); - } else { - deferred.resolve(node); - } - } - ); - return deferred.promise; - } - - function resolveEntity(datasource) { - var deferred = $q.defer(); - if (datasource.type === types.datasourceType.function) { - var entity = { - id: { - entityType: "function" - }, - name: datasource.name - } - deferred.resolve(entity); - } else { - entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then( - (entity) => { - deferred.resolve(entity); - }, - () => { - deferred.resolve(null); - } - ); - } - return deferred.promise; - } - - - function prepareNodeRelationQuery(nodeCtx) { - var relationQuery = vm.nodeRelationQueryFunction(nodeCtx); - if (relationQuery && relationQuery === 'default') { - relationQuery = defaultNodeRelationQueryFunction(nodeCtx); - } - return relationQuery; - } - - function defaultNodeRelationQueryFunction(nodeCtx) { - var entity = nodeCtx.entity; - var query = { - parameters: { - rootId: entity.id.id, - rootType: entity.id.entityType, - direction: types.entitySearchDirection.from, - relationTypeGroup: "COMMON", - maxLevel: 1 - }, - filters: [ - { - relationType: "Contains", - entityTypes: [] - } - ] - }; - return query; - } - - function prepareNodeIcon(nodeCtx) { - var iconInfo = vm.nodeIconFunction(nodeCtx); - if (iconInfo && iconInfo === 'default') { - iconInfo = defaultNodeIconFunction(nodeCtx); - } - if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) { - if (iconInfo.materialIcon) { - return materialIconHtml(iconInfo.materialIcon); - } else { - return iconUrlHtml(iconInfo.iconUrl); - } - } else { - return ""; - } - } - - function materialIconHtml(materialIcon) { - return ''+materialIcon+''; - } - - function iconUrlHtml(iconUrl) { - return '
 
'; - } - - function defaultNodeIconFunction(nodeCtx) { - var materialIcon = 'insert_drive_file'; - var entity = nodeCtx.entity; - if (entity && entity.id && entity.id.entityType) { - switch (entity.id.entityType) { - case 'function': - materialIcon = 'functions'; - break; - case types.entityType.device: - materialIcon = 'devices_other'; - break; - case types.entityType.asset: - materialIcon = 'domain'; - break; - case types.entityType.tenant: - materialIcon = 'supervisor_account'; - break; - case types.entityType.customer: - materialIcon = 'supervisor_account'; - break; - case types.entityType.user: - materialIcon = 'account_circle'; - break; - case types.entityType.dashboard: - materialIcon = 'dashboards'; - break; - case types.entityType.alarm: - materialIcon = 'notifications_active'; - break; - case types.entityType.entityView: - materialIcon = 'view_quilt'; - break; - case types.entityType.edge: - materialIcon = 'router'; - break; - } - } - return { - materialIcon: materialIcon - }; - } - - function defaultNodeOpenedFunction(nodeCtx) { - return nodeCtx.level <= 4; - } - - function defaultSortFunction(nodeCtx1, nodeCtx2) { - var result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType); - if (result === 0) { - result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name); - } - return result; - } } diff --git a/ui/src/app/widget/lib/edges-overview-widget.scss b/ui/src/app/widget/lib/edges-overview-widget.scss index f5fcc90f8f..6b7ce3f26e 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.scss +++ b/ui/src/app/widget/lib/edges-overview-widget.scss @@ -13,21 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.tb-has-timewindow { - .tb-edges-overview { - md-toolbar { - min-height: 60px; - max-height: 60px; - } - } -} - .tb-edges-overview { - md-toolbar { - min-height: 39px; - max-height: 39px; - } - .mat-subheader { padding: 15px; font-size: 14px; @@ -38,48 +24,6 @@ .tb-entities-nav-tree-panel { overflow-x: auto; overflow-y: auto; - - .tb-nav-tree-container { - &.jstree-proton { - .jstree-anchor { - div.node-icon { - display: inline-block; - width: 22px; - height: 22px; - margin-right: 2px; - margin-bottom: 2px; - background-color: transparent; - background-repeat: no-repeat; - background-attachment: scroll; - background-position: center center; - background-size: 18px 18px; - } - - md-icon.node-icon { - width: 22px; - min-width: 22px; - height: 22px; - min-height: 22px; - margin-right: 2px; - margin-bottom: 2px; - color: inherit; - - &.material-icons { /* stylelint-disable-line selector-max-class */ - font-size: 18px; - line-height: 22px; - text-align: center; - } - } - - &.jstree-hovered:not(.jstree-clicked), - &.jstree-disabled { - div.node-icon { /* stylelint-disable-line selector-max-class */ - opacity: .5; - } - } - } - } - } } } From bedb1d624508f1e27886613b87ceb79788f70db3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Mon, 11 Jan 2021 14:46:28 +0200 Subject: [PATCH 10/23] Edge overview widget final --- .../system/widget_bundles/edge_widgets.json | 12 +++++------ .../app/widget/lib/edges-overview-widget.js | 20 +++++++++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/edge_widgets.json b/application/src/main/data/json/system/widget_bundles/edge_widgets.json index 47cf3c94aa..e7f3cab71c 100644 --- a/application/src/main/data/json/system/widget_bundles/edge_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/edge_widgets.json @@ -13,13 +13,13 @@ "sizeX": 7.5, "sizeY": 5, "resources": [], - "templateHtml": "\n", + "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.hierarchyId = \"hierarchy-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('edges-overview-data-updated', self.ctx.$scope.hierarchyId);\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EdgesOverviewSettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\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\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" \\\"+ data['temperature'] +\\\" °C\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Edges Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "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\":{},\"title\":\"Edges Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" } } ] -} +} \ No newline at end of file diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index 095500e5b5..c0480afb8d 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -30,7 +30,6 @@ function EdgesOverviewWidget() { restrict: "E", scope: true, bindToController: { - hierarchyId: '=', ctx: '=' }, controller: EdgesOverviewWidgetController, @@ -49,7 +48,6 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS vm.entityNodesMap = {}; vm.entityGroupsNodesMap = {}; - vm.pendingUpdateNodeTasks = {}; vm.customerTitle = null; vm.edgeIsDatasource = true; @@ -150,16 +148,16 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS allowedGroupTypes = edgeGroupsTypes.filter(type => type !== types.entityType.rulechain); } allowedGroupTypes.forEach( - (groupType) => { + (entityType) => { var node = { id: ++vm.nodeIdCounter, - icon: 'material-icons ' + iconForGroupType(groupType), - text: textForGroupType(groupType), + icon: 'material-icons ' + iconForGroupType(entityType), + text: textForGroupType(entityType), children: true, data: { - groupType, + entityType, entity, - internalId: entity ? entity.id.id + '_' + groupType : utils.guid() + internalId: entity ? entity.id.id + '_' + entityType : utils.guid() } }; nodes.push(node); @@ -168,8 +166,8 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS return nodes; } - function iconForGroupType(groupType) { - switch (groupType) { + function iconForGroupType(entityType) { + switch (entityType) { case types.entityType.asset: return 'tb-asset-group'; case types.entityType.device: @@ -184,8 +182,8 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS return ''; } - function textForGroupType(groupType) { - switch (groupType) { + function textForGroupType(entityType) { + switch (entityType) { case types.entityType.asset: return $translate.instant('asset.assets'); case types.entityType.device: From 43570a483ca91bf4cd4df08f3dd5526267957064 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 12 Jan 2021 14:49:20 +0200 Subject: [PATCH 11/23] Fix customerTitle for customer with nullUid --- ui/src/app/widget/lib/edges-overview-widget.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index c0480afb8d..588b763521 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -201,13 +201,15 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS function getCustomerTitle(edgeId) { edgeService.getEdge(edgeId, true).then( function success(edge) { - customerService.getCustomer(edge.customerId.id, { ignoreErrors: true }).then( - function success(customer) { - vm.customerTitle = $translate.instant('edge.assigned-to-customer-widget', { customerTitle: customer.title }); - }, - function fail() { - } - ); + if (edge.customerId.id !== types.id.nullUid) { + customerService.getCustomer(edge.customerId.id, { ignoreErrors: true }).then( + function success(customer) { + vm.customerTitle = $translate.instant('edge.assigned-to-customer-widget', { customerTitle: customer.title }); + }, + function fail() { + } + ); + } }, function fail() { } From 0ba88ccd69e9c10c354e74a12fb13f50cefea513 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 12 Jan 2021 15:26:01 +0200 Subject: [PATCH 12/23] Locales update --- ui/src/app/locale/locale.constant-de_DE.json | 67 ++++++++++++++++++- ui/src/app/locale/locale.constant-en_US.json | 2 - ui/src/app/locale/locale.constant-es_ES.json | 69 ++++++++++++++++++-- ui/src/app/locale/locale.constant-fr_FR.json | 67 ++++++++++++++++++- 4 files changed, 195 insertions(+), 10 deletions(-) diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index 1f495ccdf0..8fdef0c489 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -811,7 +811,66 @@ "event-action": "Ereignisaktion", "load-entity-error": "Entität nicht gefunden. Fehler beim Laden der Informationen", "unassign-edges-text": "Nach der Bestätigung werden alle ausgewählten Kanten nicht zugewiesen und sind für den Kunden nicht zugänglich.", - "unassign-edges-title": "Sind Sie sicher, dass Sie die Zuordnung aufheben möchten { count, plural, 1 {1 Rand} other {# Rand} }?" + "unassign-edges-title": "Sind Sie sicher, dass Sie die Zuordnung aufheben möchten { count, plural, 1 {1 Rand} other {# Rand} }?", + "edge-file": "Edge-Datei", + "name-starts-with": "Der Kantenname beginnt mit", + "rulechain-templates": "Regelkettenvorlagen", + "rulechain-template": "Regelkettenvorlage", + "unassign-edges-action-title": "Heben Sie die Zuordnung von {count, plural, 1 {1 edge} other {# edge}} vom Kunden auf", + "enter-edge-type": "Geben Sie den Kantentyp ein", + "no-edge-types-matching": "Es wurden keine Kantentypen gefunden, die mit '{{entitySubtype}}' übereinstimmen.", + "edge-type-list-empty": "Keine Kantentypen ausgewählt.", + "edge-types": "Kantentypen", + "license-key-hint": "Um Ihre Lizenz zu erhalten, navigieren Sie zur Preisseite und wählen Sie die beste Lizenzoption für Ihre aus Fall.", + "cloud-endpoint-hint": "Edge erfordert HTTP-Zugriff auf die Cloud (ThingsBoard CE / PE), um den Lizenzschlüssel zu überprüfen. Bitte geben Sie die Cloud-URL an, zu der Edge eine Verbindung herstellen kann.", + "missing-related-rule-chains-title": "In Edge fehlen verwandte Regelketten.", + "missing-related-rule-chains-text": "Randregelkette (n) zugewiesen Verwenden Sie Regelknoten, die Nachrichten an Regelkette (n) weiterleiten, die dieser Kante nicht zugeordnet sind.

Liste der fehlenden Regelketten:
{{missingRuleChains}}", + "downlinks": "Downlinks", + "no-downlinks-prompt": "Keine Downlinks gefunden", + "assigned-to-customer-widget": "Zugewiesen an: {{customerTitle}}", + "widget-datasource-error": "Dieses Widget unterstützt nur EDGE-Entitätsdatenquellen" + }, + "edge-event": { + "type-dashboard": "Dashboard", + "type-asset": "Asset", + "type-device": "Device", + "type-device-profile": "Device Profile", + "type-entity-view": "Entity View", + "type-alarm": "Alar", + "type-rule-chain": "Rule Chain", + "type-rule-chain-metadata": "Rule Chain Metadata", + "type-edge": "Edge", + "type-entity-group": "Entity Group", + "type-scheduler-event": "Scheduler Event", + "type-white-labeling": "White Labeling", + "type-login-white-labeling": "White Labeling Login", + "type-user": "User", + "type-tenant": "Tenant", + "type-customer": "Customer", + "type-custom-translation": "Custom Translation", + "type-relation": "Relation", + "type-widgets-bundle": "Widgets Bundle", + "type-widgets-type": "Widgets Type", + "type-admin-settings": "Admin Settings", + "action-type-added": "Added", + "action-type-deleted": "Deleted", + "action-type-updated": "Updated", + "action-type-post-attributes": "Post Attributes", + "action-type-attributes-updated": "Attributes Updated", + "action-type-attributes-deleted": "Attributes Deleted", + "action-type-timeseries-updated": "Timeseries Updated", + "action-type-credentials-updated": "Credentials Updated", + "action-type-assigned-to-customer": "Assigned to Customer", + "action-type-unassigned-from-customer": "Unassigned from Customer", + "action-type-relation-add-or-update": "Relation Add or Update", + "action-type-relation-deleted": "Relation Deleted", + "action-type-rpc-call": "RPC Call", + "action-type-alarm-ack": "Alarm Ack", + "action-type-alarm-clear": "Alarm Clear", + "action-type-assigned-to-edge": "Assigned to Edge", + "action-type-unassigned-from-edge": "Unassigned from Edge", + "action-type-credentials-request": "Credentials Request", + "action-type-entity-merge-request": "Entity Merge Request" }, "error": { "unable-to-connect": "Es konnte keine Verbindung zum Server hergestellt werden! Bitte überprüfen Sie Ihre Internetverbindung.", @@ -1409,7 +1468,11 @@ "unset-auto-assign-to-edge": "Weisen Sie Kanten bei der Erstellung keine Regelkette zu", "unset-auto-assign-to-edge-title": "Sind Sie sicher, dass Sie die Kantenregelkette '{{ruleChainName}}' bei der Erstellung nicht den Kanten zuweisen möchten?", "unset-auto-assign-to-edge-text": "Nach der Bestätigung wird die Kantenregelkette bei der Erstellung nicht mehr automatisch den Kanten zugewiesen.", - "edge-template-root": "Vorlagenstamm" + "edge-template-root": "Vorlagenstamm", + "set-auto-assign-to-edge-card": "Bei der Erstellung den Kanten zuweisen", + "set-default-root-edge": "Machen Sie die Regelkette zum Standardstamm", + "set-default-root-edge-rulechain-title": "Sind Sie sicher, dass Sie die Standardkettenwurzel der Regelkette '{{ruleChainName}}' festlegen möchten?", + "set-default-root-edge-rulechain-text": "Nach der Bestätigung wird die Regelkette zum Standard-Edge-Root und verarbeitet alle eingehenden Transportnachrichten." }, "rulenode": { "details": "Details", diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index c064e6d83f..7cde59ad04 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -769,7 +769,6 @@ "management": "Edge management", "no-edges-matching": "No edges matching '{{entity}}' were found.", "add": "Add Edge", - "view": "View Edge", "no-edges-text": "No edges found", "edge-details": "Edge details", "add-edge-text": "Add new edge", @@ -794,7 +793,6 @@ "id-copied-message": "Edge Id has been copied to clipboard", "sync": "Sync Edge", "sync-message": "Edge has been synchronized", - "permissions": "Permissions", "edge-required": "Edge required", "edge-type": "Edge type", "edge-type-required": "Edge type is required.", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 07acd04e3a..9083bf966b 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -747,7 +747,6 @@ "management": "Gestión de bordes", "no-edges-matching": "No se encontraron bordes que coincidan con '{{entity}}'", "add": "Agregar borde", - "view": "Ver borde", "no-edges-text": "No se encontraron bordes", "edge-details": "Detalles del borde", "add-edge-text": "Agregar nuevo borde", @@ -771,7 +770,6 @@ "id-copied-message": "El ID de borde se ha copiado al portapapeles", "sync": "Sinc Edge", "sync-message": "Edge se ha sincronizado", - "permissions": "Permisos", "edge-required": "Edge required", "edge-type": "Type de la bordure", "edge-type-required": "El tipo de borde es requerido.", @@ -824,8 +822,67 @@ "event-action": "Información de la entidad", "load-entity-error": "Entidad no encontrada. No se pudo cargar la información", "unassign-edges-text": "Después de la confirmación de todos los bordes seleccionados, se anulará la asignación y el cliente no podrá acceder a ellos.", - "unassign-edges-title": "¿Está seguro de que desea anular la asignación de {count, plural, 1 {1 borde} other {# bordes}}?" + "unassign-edges-title": "¿Está seguro de que desea anular la asignación de {count, plural, 1 {1 borde} other {# bordes}}?", + "edge-file": "Archivo de borde", + "name-starts-with": "Edge name starts with", + "rulechain-templates": "Plantillas de cadena de reglas", + "rulechain-template": "Plantilla de cadena de reglas", + "unassign-edges-action-title": "Anular la asignación de {count, plural, 1 {1 borde} other {# bordes}} del cliente", + "enter-edge-type": "Ingrese el tipo de borde", + "no-edge-types-matching": "No se encontraron tipos de aristas que coincidan con '{{entitySubtype}}'.", + "edge-type-list-empty": "No se seleccionó ningún tipo de borde.", + "edge-types": "Tipos de bordes", + "license-key-hint": "Para obtener su licencia, vaya a la página de precios y seleccione la mejor opción de licencia para su caso.", + "cloud-endpoint-hint": "Edge requiere acceso HTTP (s) a la nube (ThingsBoard CE / PE) para verificar la clave de licencia. Especifique la URL de la nube a la que Edge puede conectarse.", + "missing-related-rule-chains-title": "Al borde le faltan cadenas de reglas relacionadas", + "missing-related-rule-chains-text": "Asignado a la (s) cadena (s) de reglas de borde usa nodos de reglas que reenvían mensajes a cadenas de reglas que no están asignadas a este borde.

Lista de cadenas de reglas faltantes:
{{missingRuleChains}}", + "downlinks": "Enlaces descendentes", + "no-downlinks-prompt": "No se encontraron enlaces descendentes", + "assigned-to-customer-widget": "Asignado a: {{customerTitle}}", + "widget-datasource-error": "Este widget solo admite la fuente de datos de la entidad EDGE" }, + "edge-event": { + "type-dashboard": "Dashboard", + "type-asset": "Asset", + "type-device": "Device", + "type-device-profile": "Device Profile", + "type-entity-view": "Entity View", + "type-alarm": "Alar", + "type-rule-chain": "Rule Chain", + "type-rule-chain-metadata": "Rule Chain Metadata", + "type-edge": "Edge", + "type-entity-group": "Entity Group", + "type-scheduler-event": "Scheduler Event", + "type-white-labeling": "White Labeling", + "type-login-white-labeling": "White Labeling Login", + "type-user": "User", + "type-tenant": "Tenant", + "type-customer": "Customer", + "type-custom-translation": "Custom Translation", + "type-relation": "Relation", + "type-widgets-bundle": "Widgets Bundle", + "type-widgets-type": "Widgets Type", + "type-admin-settings": "Admin Settings", + "action-type-added": "Added", + "action-type-deleted": "Deleted", + "action-type-updated": "Updated", + "action-type-post-attributes": "Post Attributes", + "action-type-attributes-updated": "Attributes Updated", + "action-type-attributes-deleted": "Attributes Deleted", + "action-type-timeseries-updated": "Timeseries Updated", + "action-type-credentials-updated": "Credentials Updated", + "action-type-assigned-to-customer": "Assigned to Customer", + "action-type-unassigned-from-customer": "Unassigned from Customer", + "action-type-relation-add-or-update": "Relation Add or Update", + "action-type-relation-deleted": "Relation Deleted", + "action-type-rpc-call": "RPC Call", + "action-type-alarm-ack": "Alarm Ack", + "action-type-alarm-clear": "Alarm Clear", + "action-type-assigned-to-edge": "Assigned to Edge", + "action-type-unassigned-from-edge": "Unassigned from Edge", + "action-type-credentials-request": "Credentials Request", + "action-type-entity-merge-request": "Entity Merge Request" + }, "error": { "unable-to-connect": "¡No se puede conectar al servidor! Por favor, revise su conexión a Internet.", "unhandled-error-code": "Código de error no controlado: {{errorCode}}", @@ -1477,7 +1534,11 @@ "unset-auto-assign-to-edge": "No asigne una cadena de reglas a los bordes en la creación", "unset-auto-assign-to-edge-title": "¿Está seguro de que no desea asignar la cadena de reglas de borde '{{ruleChainName}}' a los bordes en la creación?", "unset-auto-assign-to-edge-text": "Después de la confirmación, la cadena de reglas de borde ya no se asignará automáticamente a los bordes en la creación.", - "edge-template-root": "Raíz de plantilla" + "edge-template-root": "Raíz de plantilla", + "set-auto-assign-to-edge-card": "Asignar a borde (s) en la creación", + "set-default-root-edge": "Hacer raíz predeterminada de la cadena de reglas", + "set-default-root-edge-rulechain-title": "¿Está seguro de que desea que la cadena de reglas '{{ruleChainName}}' sea la raíz del borde predeterminada?", + "set-default-root-edge-rulechain-text": "Después de la confirmación, la cadena de reglas se convertirá en la raíz del borde predeterminada y manejará todos los mensajes de transporte entrantes." }, "rulenode": { "details": "Detalles", diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index a4b92cae37..97b0782534 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -830,8 +830,67 @@ "event-action": "Action d'événement", "load-entity-error": "Entité introuvable. Échec du chargement des informations", "unassign-edges-text": "Après la confirmation, tous les bordures sélectionnés ne seront plus attribués et ne seront pas accessibles par le client.", - "unassign-edges-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 bordure} other {# bordures}}?" + "unassign-edges-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 bordure} other {# bordures}}?", + "edge-file": "Fichier Edge", + "name-starts-with": "Le nom du bord commence par", + "rulechain-templates": "Modèles de chaîne de règles", + "rulechain-template": "Modèle de chaîne de règles", + "unassign-edges-action-title": "Annuler l'attribution de {count, plural, 1 {1 edge} other {# bords}} au client", + "enter-edge-type": "Entrez le type d'arête", + "no-edge-types-matching": "Aucun type d'arête correspondant à '{{entitySubtype}}' n'a été trouvé.", + "edge-type-list-empty": "Aucun type d'arête sélectionné.", + "edge-types": "Types de bords", + "license-key-hint": "Pour obtenir votre licence, accédez à la page de tarification et sélectionnez la meilleure option de licence pour votre Cas.", + "cloud-endpoint-hint": "Edge nécessite un accès HTTP (s) au Cloud (ThingsBoard CE / PE) pour vérifier la clé de licence. Veuillez spécifier l'URL cloud à laquelle Edge peut se connecter.", + "missing-related-rule-chains-title": "Edge n'a pas de chaîne (s) de règles associées", + "missing-related-rule-chains-text": "Les chaînes de règles affectées aux tronçons utilisent des nœuds de règles qui transfèrent les messages aux chaînes de règles non affectées à ce tronçon.

Liste des chaînes de règles manquantes:
{{missingRuleChains}}", + "downlinks": "Liens descendants", + "no-downlinks-prompt": "Aucun lien descendant trouvé", + "assigned-to-customer-widget": "Attribué à: {{customerTitle}}", + "widget-datasource-error": "Ce widget prend en charge uniquement la source de données d'entité EDGE" }, + "edge-event": { + "type-dashboard": "Dashboard", + "type-asset": "Asset", + "type-device": "Device", + "type-device-profile": "Device Profile", + "type-entity-view": "Entity View", + "type-alarm": "Alar", + "type-rule-chain": "Rule Chain", + "type-rule-chain-metadata": "Rule Chain Metadata", + "type-edge": "Edge", + "type-entity-group": "Entity Group", + "type-scheduler-event": "Scheduler Event", + "type-white-labeling": "White Labeling", + "type-login-white-labeling": "White Labeling Login", + "type-user": "User", + "type-tenant": "Tenant", + "type-customer": "Customer", + "type-custom-translation": "Custom Translation", + "type-relation": "Relation", + "type-widgets-bundle": "Widgets Bundle", + "type-widgets-type": "Widgets Type", + "type-admin-settings": "Admin Settings", + "action-type-added": "Added", + "action-type-deleted": "Deleted", + "action-type-updated": "Updated", + "action-type-post-attributes": "Post Attributes", + "action-type-attributes-updated": "Attributes Updated", + "action-type-attributes-deleted": "Attributes Deleted", + "action-type-timeseries-updated": "Timeseries Updated", + "action-type-credentials-updated": "Credentials Updated", + "action-type-assigned-to-customer": "Assigned to Customer", + "action-type-unassigned-from-customer": "Unassigned from Customer", + "action-type-relation-add-or-update": "Relation Add or Update", + "action-type-relation-deleted": "Relation Deleted", + "action-type-rpc-call": "RPC Call", + "action-type-alarm-ack": "Alarm Ack", + "action-type-alarm-clear": "Alarm Clear", + "action-type-assigned-to-edge": "Assigned to Edge", + "action-type-unassigned-from-edge": "Unassigned from Edge", + "action-type-credentials-request": "Credentials Request", + "action-type-entity-merge-request": "Entity Merge Request" + }, "entity": { "add-alias": "Ajouter un alias d'entité", "alarm-name-starts-with": "Les actifs dont le nom commence par '{{prefix}}'", @@ -1455,7 +1514,11 @@ "unset-auto-assign-to-edge": "N'attribuez pas de chaîne de règles aux arêtes lors de la création", "unset-auto-assign-to-edge-title": "Êtes-vous sûr de ne pas vouloir attribuer la chaîne de règles d'arête '{{ruleChainName}}' à l'arête (s) lors de la création?", "unset-auto-assign-to-edge-text": "Après la confirmation, la chaîne de règles d'arêtes ne sera plus automatiquement affectée aux arêtes lors de la création.", - "edge-template-root": "Racine du modèle" + "edge-template-root": "Racine du modèle", + "set-auto-assign-to-edge-card": "Affecter aux arêtes lors de la création", + "set-default-root-edge": "Rendre la chaîne de règles racine par défaut", + "set-default-root-edge-rulechain-title": "Voulez-vous vraiment définir la racine de périphérie par défaut de la chaîne de règles '{{ruleChainName}}'?", + "set-default-root-edge-rulechain-text": "Après la confirmation, la chaîne de règles deviendra la racine périphérique par défaut et gérera tous les messages de transport entrants." }, "rulenode": { "add": "Ajouter un noeud de règle", From 752183f099757478d71607767231cd7f686db0a2 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 12 Jan 2021 15:34:35 +0200 Subject: [PATCH 13/23] Code cleanup --- ui/src/app/components/nav-tree.scss | 6 ------ ui/src/app/edge/edge-fieldset.tpl.html | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/ui/src/app/components/nav-tree.scss b/ui/src/app/components/nav-tree.scss index dc21431479..da50ccea5e 100644 --- a/ui/src/app/components/nav-tree.scss +++ b/ui/src/app/components/nav-tree.scss @@ -132,12 +132,6 @@ } } - &.tb-edge-group { - &::before { - content: "router"; - } - } - &.tb-rule-chain-group { &::before { content: "settings_ethernet"; diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index c5760a939c..68daecac44 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -58,7 +58,7 @@ @@ -67,14 +67,14 @@ edge.copy-edge-secret From 0e00c45e2eeb5ae7174f220b8b987ba532800b11 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Wed, 13 Jan 2021 06:32:13 +0200 Subject: [PATCH 14/23] Hide edge buttons for customer if edges_enabled=false --- .../app/customer/customer-fieldset.tpl.html | 2 +- ui/src/app/customer/customer.controller.js | 44 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/ui/src/app/customer/customer-fieldset.tpl.html b/ui/src/app/customer/customer-fieldset.tpl.html index a67a4391df..f24eb3048b 100644 --- a/ui/src/app/customer/customer-fieldset.tpl.html +++ b/ui/src/app/customer/customer-fieldset.tpl.html @@ -19,7 +19,7 @@ {{ 'customer.manage-assets' | translate }} {{ 'customer.manage-devices' | translate }} {{ 'customer.manage-dashboards' | translate }} -{{ 'customer.manage-edges' | translate }} +{{ 'customer.manage-edges' | translate }} {{ 'customer.delete' | translate }}
diff --git a/ui/src/app/customer/customer.controller.js b/ui/src/app/customer/customer.controller.js index 2d1d2202a8..8c20cddb6b 100644 --- a/ui/src/app/customer/customer.controller.js +++ b/ui/src/app/customer/customer.controller.js @@ -21,7 +21,7 @@ import customerCard from './customer-card.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function CustomerController(customerService, $state, $stateParams, $translate, types) { +export default function CustomerController(customerService, $state, $stateParams, $translate, types, userService) { var customerActionsList = [ { @@ -76,21 +76,29 @@ export default function CustomerController(customerService, $state, $stateParams } }, icon: "dashboard" - }, - { - onAction: function ($event, item) { - openCustomerEdges($event, item); - }, - name: function() { return $translate.instant('edge.edge-instances') }, - details: function(customer) { - if (customer && customer.additionalInfo && customer.additionalInfo.isPublic) { - return $translate.instant('customer.manage-public-edges') - } else { - return $translate.instant('customer.manage-customer-edges') - } - }, - icon: "router" - }, + }]; + + if (userService.isEdgesSupportEnabled()) { + customerActionsList.push( + { + onAction: function ($event, item) { + openCustomerEdges($event, item); + }, + name: function() { return $translate.instant('edge.edge-instances') }, + details: function(customer) { + if (customer && customer.additionalInfo && customer.additionalInfo.isPublic) { + return $translate.instant('customer.manage-public-edges') + } else { + return $translate.instant('customer.manage-customer-edges') + } + }, + icon: "router", + isEnabled: false + } + ) + } + + customerActionsList.push( { onAction: function ($event, item) { vm.grid.deleteItem($event, item); @@ -102,12 +110,14 @@ export default function CustomerController(customerService, $state, $stateParams return customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); } } - ]; + ); var vm = this; vm.types = types; + vm.isEdgesSupportEnabled = userService.isEdgesSupportEnabled(); + vm.customerGridConfig = { refreshParamsFunc: null, From 657c6bfc36e431c244bcebb7fa2fbf384a317fa7 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 20 Jan 2021 14:31:39 +0200 Subject: [PATCH 15/23] Edge widget added config --- .../system/widget_bundles/edge_widgets.json | 4 ++-- .../app/widget/lib/edges-overview-widget.js | 23 ++++++++++++++++++- .../widget/lib/edges-overview-widget.tpl.html | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/edge_widgets.json b/application/src/main/data/json/system/widget_bundles/edge_widgets.json index e7f3cab71c..553189ac6d 100644 --- a/application/src/main/data/json/system/widget_bundles/edge_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/edge_widgets.json @@ -15,10 +15,10 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", - "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\":{},\"title\":\"Edges Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" + "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\":{},\"title\":\"Edges Quick Overview\",\"showTitleIcon\":true,\"titleIcon\":\"router\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" } } ] diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index 588b763521..effc353423 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -60,6 +60,8 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS types.entityType.rulechain, ] + vm.onNodeSelected = onNodeSelected; + $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { vm.settings = vm.ctx.settings; @@ -70,6 +72,25 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS } }); + function onNodeSelected(node, event) { + var nodeId; + if (!node) { + nodeId = -1; + } else { + nodeId = node.id; + } + if (nodeId !== -1) { + var selectedNode = vm.entityNodesMap[nodeId]; + if (selectedNode) { + var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected'); + if (descriptors.length) { + var entity = selectedNode.data.nodeCtx.entity; + vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); + } + } + } + } + function updateDatasources() { vm.loadNodes = loadNodes; } @@ -80,7 +101,7 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS if (datasource.type === types.datasourceType.entity && datasource.entity && datasource.entity.id.entityType === types.entityType.edge) { var selectedEdge = datasource.entity; vm.customerTitle = getCustomerTitle(selectedEdge.id.id); - vm.ctx.widgetTitle = selectedEdge.name; + // vm.ctx.widgetTitle = selectedEdge.name; cb(loadNodesForEdge(selectedEdge.id.id, selectedEdge)); } else if (datasource.type === types.datasourceType.function) { cb(loadNodesForEdge(null, null)); diff --git a/ui/src/app/widget/lib/edges-overview-widget.tpl.html b/ui/src/app/widget/lib/edges-overview-widget.tpl.html index be6e75d16d..884e5360c5 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.tpl.html +++ b/ui/src/app/widget/lib/edges-overview-widget.tpl.html @@ -21,6 +21,7 @@ edge.widget-datasource-error
From 40b364130b59edf1f472e40c31a8bb4cf2ae08be Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 9 Feb 2021 17:29:56 +0200 Subject: [PATCH 16/23] Routes refactored, conflicts resolved --- ui/src/app/edge/edge.routes.js | 59 ------------------- ui/src/app/rulechain/rulechain.controller.js | 2 +- ui/src/app/rulechain/rulechain.routes.js | 6 +- ui/src/app/rulechain/rulechains.controller.js | 4 +- ui/src/app/services/menu.service.js | 4 +- .../app/widget/lib/edges-overview-widget.js | 6 +- 6 files changed, 11 insertions(+), 70 deletions(-) diff --git a/ui/src/app/edge/edge.routes.js b/ui/src/app/edge/edge.routes.js index 5a8a2bbcc7..f89dfdb1b7 100644 --- a/ui/src/app/edge/edge.routes.js +++ b/ui/src/app/edge/edge.routes.js @@ -21,8 +21,6 @@ import devicesTemplate from "../device/devices.tpl.html"; import assetsTemplate from "../asset/assets.tpl.html"; import dashboardsTemplate from "../dashboard/dashboards.tpl.html"; import dashboardTemplate from "../dashboard/dashboard.tpl.html"; -import ruleChainsTemplate from "../rulechain/rulechains.tpl.html"; -import ruleChainTemplate from "../rulechain/rulechain.tpl.html"; /* eslint-enable import/no-unresolved, import/default */ @@ -180,62 +178,5 @@ export default function EdgeRoutes($stateProvider, types) { ncyBreadcrumb: { label: '{"icon": "router", "label": "{{ vm.customerEdgesTitle }}", "translate": "false"}' } - }).state('home.edges.ruleChains', { - url: '/:edgeId/ruleChains', - params: {'topIndex': 0}, - module: 'private', - auth: ['TENANT_ADMIN'], - views: { - "content@home": { - templateUrl: ruleChainsTemplate, - controllerAs: 'vm', - controller: 'RuleChainsController' - } - }, - data: { - searchEnabled: true, - pageTitle: 'edge.rulechain-templates', - ruleChainsType: 'edge' - }, - ncyBreadcrumb: { - label: '{"icon": "settings_ethernet", "label": "edge.rulechain-templates"}' - } - }).state('home.edges.ruleChains.ruleChain', { - url: '/:ruleChainId', - reloadOnSearch: false, - module: 'private', - auth: ['SYS_ADMIN', 'TENANT_ADMIN'], - views: { - "content@home": { - templateUrl: ruleChainTemplate, - controller: 'RuleChainController', - controllerAs: 'vm' - } - }, - resolve: { - ruleChain: - /*@ngInject*/ - function($stateParams, ruleChainService) { - return ruleChainService.getRuleChain($stateParams.ruleChainId); - }, - ruleChainMetaData: - /*@ngInject*/ - function($stateParams, ruleChainService) { - return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); - }, - ruleNodeComponents: - /*@ngInject*/ - function($stateParams, ruleChainService) { - return ruleChainService.getRuleNodeComponents(types.ruleChainType.edge); - } - }, - data: { - import: false, - searchEnabled: false, - pageTitle: 'edge.rulechain-templates' - }, - ncyBreadcrumb: { - label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' - } }); } diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index fee537f309..6cb1ff4308 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -1272,7 +1272,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time if (vm.ruleChain.type === vm.types.ruleChainType.core) { $state.go('home.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); } else { - $state.go('home.edges.edgeRuleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); + $state.go('home.edges.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); } }); } else { diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 9bdb5c2981..cb581f2384 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -125,7 +125,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' } - }).state('home.edges.edgeRuleChains', { + }).state('home.edges.ruleChains', { url: '/ruleChains', params: {'topIndex': 0}, module: 'private', @@ -145,7 +145,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "edge.rulechain-templates"}' } - }).state('home.edges.edgeRuleChains.ruleChain', { + }).state('home.edges.ruleChains.ruleChain', { url: '/:ruleChainId', reloadOnSearch: false, module: 'private', @@ -182,7 +182,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' } - }).state('home.edges.edgeRuleChains.importRuleChain', { + }).state('home.edges.ruleChains.importRuleChain', { url: '/edges/ruleChains/import', reloadOnSearch: false, module: 'private', diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 4ea5d5ffd1..c7c4e7eda5 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -247,7 +247,7 @@ export default function RuleChainsController(ruleChainService, userService, impo onAction: function ($event) { importExport.importRuleChain($event, types.ruleChainType.edge).then( function(ruleChainImport) { - $state.go('home.edges.edgeRuleChains.importRuleChain', {ruleChainImport:ruleChainImport, ruleChainType: types.ruleChainType.edge}); + $state.go('home.edges.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport, ruleChainType: types.ruleChainType.edge}); } ); }, @@ -401,7 +401,7 @@ export default function RuleChainsController(ruleChainService, userService, impo if (vm.ruleChainsScope === 'edge') { $state.go('home.edges.ruleChains.ruleChain', Object.assign(ruleChainParams, edgeId = vm.edge.id.id)); } else if (vm.ruleChainsScope === 'edges') { - $state.go('home.edges.edgeRuleChains.ruleChain', ruleChainParams); + $state.go('home.edges.ruleChains.ruleChain', ruleChainParams); } else { $state.go('home.ruleChains.ruleChain', ruleChainParams); } diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js index 2d01daccdf..e6055654c9 100644 --- a/ui/src/app/services/menu.service.js +++ b/ui/src/app/services/menu.service.js @@ -202,7 +202,7 @@ function Menu(userService, $state, $rootScope) { { name: 'edge.rulechain-templates', type: 'link', - state: 'home.edges.edgeRuleChains', + state: 'home.edges.ruleChains', icon: 'settings_ethernet' } ] @@ -293,7 +293,7 @@ function Menu(userService, $state, $rootScope) { { name: 'edge.rulechain-templates', icon: 'settings_ethernet', - state: 'home.edges.edgeRuleChains' + state: 'home.edges.ruleChains' } ] diff --git a/ui/src/app/widget/lib/edges-overview-widget.js b/ui/src/app/widget/lib/edges-overview-widget.js index effc353423..26c153a5a4 100644 --- a/ui/src/app/widget/lib/edges-overview-widget.js +++ b/ui/src/app/widget/lib/edges-overview-widget.js @@ -57,8 +57,8 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS types.entityType.device, types.entityType.entityView, types.entityType.dashboard, - types.entityType.rulechain, - ] + types.entityType.rulechain + ]; vm.onNodeSelected = onNodeSelected; @@ -121,7 +121,7 @@ function EdgesOverviewWidgetController($scope, $translate, types, utils, entityS cb([]); } } - ) + ); } else { cb([]); } From a60f3630e803267bcde7bbfd3eae52e9af8f57a8 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 10 Feb 2021 10:11:32 +0200 Subject: [PATCH 17/23] Deleted checkTooltip for edge downlinks --- ui/src/app/edge/downlinks/edge-downlinks-row.directive.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js index 16015d12db..bbaf1da30d 100644 --- a/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js +++ b/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js @@ -92,14 +92,6 @@ export default function EdgeDownlinksRowDirective($compile, $templateCache, $mdD type === types.edgeEventType.widgetsBundle ); } - scope.checkTooltip = function($event) { - var el = $event.target; - var $el = angular.element(el); - if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ - $el.attr('title', $el.text()); - } - } - $compile(element.contents())(scope); scope.updateStatus = function(downlinkCreatedTime) { From 280f6aebb39d6e31adf14bea991203d0ec25d591 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 10 Feb 2021 10:19:54 +0200 Subject: [PATCH 18/23] Edge rule chains: refactored edgeId usage, added unAssingFromEdge button --- .../app/rulechain/rulechain-fieldset.tpl.html | 9 ++++---- ui/src/app/rulechain/rulechain.directive.js | 1 + ui/src/app/rulechain/rulechains.controller.js | 23 +++++++++++-------- ui/src/app/rulechain/rulechains.tpl.html | 1 + 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index 6a4841a550..1f95855d96 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -24,8 +24,11 @@ class="md-raised md-primary">{{ 'rulechain.delete' | translate }}
{{ 'rulechain.set-root' | translate }} + {{ 'edge.unassign-from-edge' | translate }} {{ 'rulechain.set-edge-template-root-rulechain' | translate }} @@ -36,9 +39,7 @@ ng-show="!isEdit && !ruleChain.root && ruleChain.isDefault && ruleChainsScope == 'edges'" class="md-raised md-primary">{{ 'rulechain.unset-auto-assign-to-edge' | translate }} - {{ 'rulechain.set-root' | translate }} +
{ vm.edge = edge; @@ -525,7 +528,7 @@ export default function RuleChainsController(ruleChainService, userService, impo }); } - function unassignRuleChainsFromEdge($event, items, edgeId) { + function unassignRuleChainsFromEdge($event, items) { var confirm = $mdDialog.confirm() .targetEvent($event) .title($translate.instant('rulechain.unassign-rulechains-title', {count: items.selectedCount}, 'messageformat')) @@ -567,7 +570,7 @@ export default function RuleChainsController(ruleChainService, userService, impo controller: 'AddRuleChainsToEdgeController', controllerAs: 'vm', templateUrl: addRuleChainsToEdgeTemplate, - locals: {edgeId: edgeId, ruleChains: ruleChains}, + locals: {edgeId, ruleChains}, parent: angular.element($document[0].body), fullscreen: true, targetEvent: $event @@ -604,7 +607,7 @@ export default function RuleChainsController(ruleChainService, userService, impo }); } - function unassignFromEdge($event, ruleChain, edgeId) { + function unassignFromEdge($event, ruleChain) { if ($event) { $event.stopPropagation(); } diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html index 94ca52f3c6..26aa54fd72 100644 --- a/ui/src/app/rulechain/rulechains.tpl.html +++ b/ui/src/app/rulechain/rulechains.tpl.html @@ -30,6 +30,7 @@ edge="vm.edge" on-set-auto-assign-to-edge-rule-chain="vm.setAutoAssignToEdgeRuleChain(event, vm.grid.detailsConfig.currentItem)" on-unset-auto-assign-to-edge-rule-chain="vm.unsetAutoAssignToEdgeRuleChain(event, vm.grid.detailsConfig.currentItem)" + on-unassign-from-edge="vm.unassignFromEdge(event, vm.grid.detailsConfig.currentItem)" on-set-root-rule-chain="vm.setRootRuleChain(event, vm.grid.detailsConfig.currentItem)" on-export-rule-chain="vm.exportRuleChain(event, vm.grid.detailsConfig.currentItem)" on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"> From 8d5957c2a9a3c722d5b73c53fd2cddd403f6c621 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 10 Feb 2021 10:36:05 +0200 Subject: [PATCH 19/23] fetchRuleChains refactored with promise chaining --- ui/src/app/rulechain/rulechains.controller.js | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index a255d84869..41056e4627 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -354,12 +354,11 @@ export default function RuleChainsController(ruleChainService, userService, impo return ruleChain; }); ruleChains.data = data; - deferred.resolve(ruleChains); }, function fail() { deferred.reject(); } - ) + ); return deferred.promise; } @@ -368,19 +367,13 @@ export default function RuleChainsController(ruleChainService, userService, impo return ruleChainService.getRuleChains(pageLink, null, type); } else if (vm.ruleChainsScope === 'edges') { var deferred = $q.defer(); - ruleChainService.getRuleChains(pageLink, null, type).then( - function success(ruleChains) { - mapRuleChainsWithDefaultEdges(ruleChains).then( - function success(response) { - deferred.resolve(response); - }, function fail() { - deferred.reject(); - } - ); - }, function fail() { - deferred.reject(); - } - ); + ruleChainService.getRuleChains(pageLink, null, type) + .then(function (ruleChains) { + return mapRuleChainsWithDefaultEdges(ruleChains); + }) + .then(function (ruleChains) { + deferred.resolve(ruleChains); + }); return deferred.promise; } } From e151b41dafcd298cee1b92808b5b9875187c959e Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 10 Feb 2021 10:46:57 +0200 Subject: [PATCH 20/23] fetchRuleChains promise chaining simplified --- ui/src/app/rulechain/rulechains.controller.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 41056e4627..1b87f05ddf 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -368,12 +368,8 @@ export default function RuleChainsController(ruleChainService, userService, impo } else if (vm.ruleChainsScope === 'edges') { var deferred = $q.defer(); ruleChainService.getRuleChains(pageLink, null, type) - .then(function (ruleChains) { - return mapRuleChainsWithDefaultEdges(ruleChains); - }) - .then(function (ruleChains) { - deferred.resolve(ruleChains); - }); + .then(ruleChains => mapRuleChainsWithDefaultEdges(ruleChains)) + .then(ruleChains => deferred.resolve(ruleChains)); return deferred.promise; } } From 291c048e6e3c3bc6ef4acafd11e713f626e37cbf Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 10 Feb 2021 13:58:41 +0200 Subject: [PATCH 21/23] ComponentDescriptorService added removed methods --- .../app/api/component-descriptor.service.js | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/ui/src/app/api/component-descriptor.service.js b/ui/src/app/api/component-descriptor.service.js index b4cacf4991..b4398cb569 100644 --- a/ui/src/app/api/component-descriptor.service.js +++ b/ui/src/app/api/component-descriptor.service.js @@ -21,38 +21,63 @@ function ComponentDescriptorService($http, $q) { var componentsByType = {}; var componentsByClazz = {}; + var actionsByPlugin = {}; var service = { + getComponentDescriptorsByType: getComponentDescriptorsByType, + getComponentDescriptorByClazz: getComponentDescriptorByClazz, + getPluginActionsByPluginClazz: getPluginActionsByPluginClazz, getComponentDescriptorsByTypes: getComponentDescriptorsByTypes } return service; - function getComponentDescriptorsByTypes(componentTypes, ruleChainType) { + function getComponentDescriptorsByType(componentType) { + var deferred = $q.defer(); + if (componentsByType[componentType]) { + deferred.resolve(componentsByType[componentType]); + } else { + var url = '/api/components/' + componentType; + $http.get(url, null).then(function success(response) { + componentsByType[componentType] = response.data; + for (var i = 0; i < componentsByType[componentType].length; i++) { + var component = componentsByType[componentType][i]; + componentsByClazz[component.clazz] = component; + } + deferred.resolve(componentsByType[componentType]); + }, function fail() { + deferred.reject(); + }); + + } + return deferred.promise; + } + + function getComponentDescriptorsByTypes(componentTypes, type) { var deferred = $q.defer(); var result = []; - if (!componentsByType[ruleChainType]) { - componentsByType[ruleChainType] = {}; + if (!componentsByType[type]) { + componentsByType[type] = {}; } for (var i=componentTypes.length-1;i>=0;i--) { var componentType = componentTypes[i]; - if (componentsByType[ruleChainType][componentType]) { - result = result.concat(componentsByType[ruleChainType][componentType]); + if (componentsByType[type][componentType]) { + result = result.concat(componentsByType[type][componentType]); componentTypes.splice(i, 1); } } if (!componentTypes.length) { deferred.resolve(result); } else { - var url = '/api/components?componentTypes=' + componentTypes.join(',') + '&ruleChainType=' + ruleChainType; + var url = '/api/components?componentTypes=' + componentTypes.join(',') + '&ruleChainType=' + type; $http.get(url, null).then(function success(response) { var components = response.data; for (var i = 0; i < components.length; i++) { var component = components[i]; - var componentsList = componentsByType[ruleChainType][component.type]; + var componentsList = componentsByType[type][component.type]; if (!componentsList) { componentsList = []; - componentsByType[ruleChainType][component.type] = componentsList; + componentsByType[type][component.type] = componentsList; } componentsList.push(component); componentsByClazz[component.clazz] = component; @@ -65,4 +90,37 @@ function ComponentDescriptorService($http, $q) { } return deferred.promise; } + + function getComponentDescriptorByClazz(componentDescriptorClazz) { + var deferred = $q.defer(); + if (componentsByClazz[componentDescriptorClazz]) { + deferred.resolve(componentsByClazz[componentDescriptorClazz]); + } else { + var url = '/api/component/' + componentDescriptorClazz; + $http.get(url, null).then(function success(response) { + componentsByClazz[componentDescriptorClazz] = response.data; + deferred.resolve(componentsByClazz[componentDescriptorClazz]); + }, function fail() { + deferred.reject(); + }); + } + return deferred.promise; + } + + function getPluginActionsByPluginClazz(pluginClazz) { + var deferred = $q.defer(); + if (actionsByPlugin[pluginClazz]) { + deferred.resolve(actionsByPlugin[pluginClazz]); + } else { + var url = '/api/components/actions/' + pluginClazz; + $http.get(url, null).then(function success(response) { + actionsByPlugin[pluginClazz] = response.data; + deferred.resolve(actionsByPlugin[pluginClazz]); + }, function fail() { + deferred.reject(); + }); + } + return deferred.promise; + } + } From eb79aedc9b9ecabb2d467a6519d1741a9c244744 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Thu, 11 Feb 2021 05:32:14 +0200 Subject: [PATCH 22/23] Rename edge rule chains button --- ui/src/app/edge/edge-fieldset.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index 68daecac44..87329291e8 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -44,7 +44,7 @@ class="md-raised md-primary">{{ 'edge.dashboards' | translate }} {{ 'edge.rulechain-templates' | translate }} + class="md-raised md-primary">{{ 'edge.manage-edge-rulechains' | translate }}
From d349de2de53ee6dcd4a4d7f0426f5f7e1755d854 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 18 Feb 2021 17:17:58 +0200 Subject: [PATCH 23/23] Refactoring --- .../server/service/edge/rpc/processor/DeviceProcessor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java index 8d536d5076..88b8e09b2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java @@ -98,7 +98,6 @@ public class DeviceProcessor extends BaseProcessor { ObjectNode body = mapper.createObjectNode(); body.put("conflictName", deviceName); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), body); - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } futureToSet.set(null); } @@ -114,7 +113,6 @@ public class DeviceProcessor extends BaseProcessor { log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), null); - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } break; case ENTITY_UPDATED_RPC_MESSAGE: @@ -190,6 +188,7 @@ public class DeviceProcessor extends BaseProcessor { createRelationFromEdge(tenantId, edge.getId(), device.getId()); deviceStateService.onDeviceAdded(device); pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); + deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } finally { deviceCreationLock.unlock(); }