Commit 6a566070585c3d87c55ba060685389a401677ce8

Authored by deaflynx
1 parent 9d4372dc

Edge hierarchy widget starting implementation

... ... @@ -22,6 +22,7 @@ import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-wid
22 22 import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
23 23 import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
24 24 import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy-widget';
  25 +import thingsboardEdgesHierarchyWidget from '../widget/lib/edges-hierarchy-widget';
25 26 import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget';
26 27 import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator';
27 28 import thingsboardMultipleInputWidget from '../widget/lib/multiple-input-widget';
... ... @@ -52,7 +53,7 @@ import thingsboardUtils from '../common/utils.service';
52 53
53 54 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight,
54 55 thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget,
55   - thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget,
  56 + thingsboardEntitiesHierarchyWidget, thingsboardEdgesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget,
56 57 thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes,
57 58 thingsboardUtils, thingsboardJsonToString, TripAnimationWidget])
58 59 .factory('widgetService', WidgetService)
... ...
  1 +/*
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './edges-hierarchy-widget.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import edgesHierarchyWidgetTemplate from './edges-hierarchy-widget.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +export default angular.module('thingsboard.widgets.edgesHierarchyWidget', [])
  25 + .directive('tbEdgesHierarchyWidget', EdgesHierarchyWidget)
  26 + .name;
  27 +/* eslint-disable no-unused-vars */
  28 +/*@ngInject*/
  29 +function EdgesHierarchyWidget() {
  30 + return {
  31 + restrict: "E",
  32 + scope: true,
  33 + bindToController: {
  34 + hierarchyId: '=',
  35 + ctx: '='
  36 + },
  37 + controller: EdgesHierarchyWidgetController,
  38 + controllerAs: 'vm',
  39 + templateUrl: edgesHierarchyWidgetTemplate
  40 + };
  41 +}
  42 +
  43 +/*@ngInject*/
  44 +function EdgesHierarchyWidgetController($element, $scope, $q, $timeout, toast, types, entityService, entityRelationService,
  45 + assetService, deviceService, entityViewService, dashboardService, ruleChainService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) {
  46 + var vm = this;
  47 +
  48 + vm.showData = true;
  49 +
  50 + vm.nodeEditCallbacks = {};
  51 +
  52 + vm.nodeIdCounter = 0;
  53 +
  54 + vm.nodesMap = {};
  55 + vm.pendingUpdateNodeTasks = {};
  56 + vm.edgeGroupsNodesMap = {};
  57 +
  58 + vm.query = {
  59 + search: null
  60 + };
  61 +
  62 + vm.searchAction = {
  63 + name: 'action.search',
  64 + show: true,
  65 + onAction: function() {
  66 + vm.enterFilterMode();
  67 + },
  68 + icon: 'search'
  69 + };
  70 +
  71 + vm.onNodesInserted = onNodesInserted;
  72 + vm.onNodeSelected = onNodeSelected;
  73 + vm.enterFilterMode = enterFilterMode;
  74 + vm.exitFilterMode = exitFilterMode;
  75 + vm.searchCallback = searchCallback;
  76 +
  77 + $scope.$watch('vm.ctx', function() {
  78 + if (vm.ctx && vm.ctx.defaultSubscription) {
  79 + vm.settings = vm.ctx.settings;
  80 + vm.widgetConfig = vm.ctx.widgetConfig;
  81 + vm.subscription = vm.ctx.defaultSubscription;
  82 + vm.datasources = vm.subscription.datasources;
  83 + initializeConfig();
  84 + updateDatasources();
  85 + }
  86 + });
  87 +
  88 + $scope.$watch("vm.query.search", function(newVal, prevVal) {
  89 + if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
  90 + updateSearchNodes();
  91 + }
  92 + });
  93 +
  94 + $scope.$on('edges-hierarchy-data-updated', function(event, hierarchyId) {
  95 + if (vm.hierarchyId == hierarchyId) {
  96 + if (vm.subscription) {
  97 + updateNodeData(vm.subscription.data);
  98 + }
  99 + }
  100 + });
  101 +
  102 + function initializeConfig() {
  103 +
  104 + vm.ctx.widgetActions = [ vm.searchAction ];
  105 +
  106 + var testNodeCtx = {
  107 + entity: {
  108 + id: {
  109 + entityType: 'DEVICE',
  110 + id: '123'
  111 + },
  112 + name: 'TEST DEV1'
  113 + },
  114 + data: {},
  115 + level: 2
  116 + };
  117 + var parentNodeCtx = angular.copy(testNodeCtx);
  118 + parentNodeCtx.level = 1;
  119 + testNodeCtx.parentNodeCtx = parentNodeCtx;
  120 +
  121 + var nodeRelationQueryFunction = loadNodeCtxFunction(vm.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx);
  122 + var nodeIconFunction = loadNodeCtxFunction(vm.settings.nodeIconFunction, 'nodeCtx', testNodeCtx);
  123 + var nodeTextFunction = loadNodeCtxFunction(vm.settings.nodeTextFunction, 'nodeCtx', testNodeCtx);
  124 + var nodeDisabledFunction = loadNodeCtxFunction(vm.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx);
  125 + var nodeOpenedFunction = loadNodeCtxFunction(vm.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx);
  126 + var nodeHasChildrenFunction = loadNodeCtxFunction(vm.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx);
  127 +
  128 + var testNodeCtx2 = angular.copy(testNodeCtx);
  129 + testNodeCtx2.entity.name = 'TEST DEV2';
  130 +
  131 + var nodesSortFunction = loadNodeCtxFunction(vm.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2);
  132 +
  133 + vm.nodeRelationQueryFunction = nodeRelationQueryFunction || defaultNodeRelationQueryFunction;
  134 + vm.nodeIconFunction = nodeIconFunction || defaultNodeIconFunction;
  135 + vm.nodeTextFunction = nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name);
  136 + vm.nodeDisabledFunction = nodeDisabledFunction || (() => false);
  137 + vm.nodeOpenedFunction = nodeOpenedFunction || defaultNodeOpenedFunction;
  138 + vm.nodeHasChildrenFunction = nodeHasChildrenFunction || (() => true);
  139 + vm.nodesSortFunction = nodesSortFunction || defaultSortFunction;
  140 + }
  141 +
  142 + function loadNodeCtxFunction(functionBody, argNames, ...args) {
  143 + var nodeCtxFunction = null;
  144 + if (angular.isDefined(functionBody) && functionBody.length) {
  145 + try {
  146 + nodeCtxFunction = new Function(argNames, functionBody);
  147 + var res = nodeCtxFunction.apply(null, args);
  148 + if (angular.isUndefined(res)) {
  149 + nodeCtxFunction = null;
  150 + }
  151 + } catch (e) {
  152 + nodeCtxFunction = null;
  153 + }
  154 + }
  155 + return nodeCtxFunction;
  156 + }
  157 +
  158 + function enterFilterMode () {
  159 + vm.query.search = '';
  160 + vm.ctx.hideTitlePanel = true;
  161 + $timeout(()=>{
  162 + angular.element(vm.ctx.$container).find('.searchInput').focus();
  163 + })
  164 + }
  165 +
  166 + function exitFilterMode () {
  167 + vm.query.search = null;
  168 + updateSearchNodes();
  169 + vm.ctx.hideTitlePanel = false;
  170 + }
  171 +
  172 + function searchCallback (searchText, node) {
  173 + var theNode = vm.nodesMap[node.id];
  174 + if (theNode && theNode.data.searchText) {
  175 + return theNode.data.searchText.includes(searchText.toLowerCase());
  176 + }
  177 + return false;
  178 + }
  179 +
  180 + function updateDatasources() {
  181 + vm.loadNodes = loadNodes;
  182 + }
  183 +
  184 + function updateSearchNodes() {
  185 + if (vm.query.search != null) {
  186 + vm.nodeEditCallbacks.search(vm.query.search);
  187 + } else {
  188 + vm.nodeEditCallbacks.clearSearch();
  189 + }
  190 + }
  191 +
  192 + function onNodesInserted(nodes/*, parent*/) {
  193 + if (nodes) {
  194 + nodes.forEach((nodeId) => {
  195 + var task = vm.pendingUpdateNodeTasks[nodeId];
  196 + if (task) {
  197 + task();
  198 + delete vm.pendingUpdateNodeTasks[nodeId];
  199 + }
  200 + });
  201 + }
  202 + }
  203 +
  204 + function onNodeSelected(node, event) {
  205 + var nodeId;
  206 + if (!node) {
  207 + nodeId = -1;
  208 + } else {
  209 + nodeId = node.id;
  210 + }
  211 + if (nodeId !== -1) {
  212 + var selectedNode = vm.nodesMap[nodeId];
  213 + if (selectedNode) {
  214 + var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected');
  215 + if (descriptors.length) {
  216 + var entity = selectedNode.data.nodeCtx.entity;
  217 + vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx });
  218 + }
  219 + }
  220 + }
  221 + }
  222 +
  223 + function updateNodeData(subscriptionData) {
  224 + var affectedNodes = [];
  225 + if (subscriptionData) {
  226 + for (var i=0;i<subscriptionData.length;i++) {
  227 + var datasource = subscriptionData[i].datasource;
  228 + if (datasource.nodeId) {
  229 + var node = vm.nodesMap[datasource.nodeId];
  230 + var key = subscriptionData[i].dataKey.label;
  231 + var value = undefined;
  232 + if (subscriptionData[i].data && subscriptionData[i].data.length) {
  233 + value = subscriptionData[i].data[0][1];
  234 + }
  235 + if (node.data.nodeCtx.data[key] !== value) {
  236 + if (affectedNodes.indexOf(datasource.nodeId) === -1) {
  237 + affectedNodes.push(datasource.nodeId);
  238 + }
  239 + node.data.nodeCtx.data[key] = value;
  240 + }
  241 + }
  242 + }
  243 + }
  244 + affectedNodes.forEach((nodeId) => {
  245 + var node = vm.nodeEditCallbacks.getNode(nodeId);
  246 + if (node) {
  247 + updateNodeStyle(vm.nodesMap[nodeId]);
  248 + } else {
  249 + vm.pendingUpdateNodeTasks[nodeId] = () => {
  250 + updateNodeStyle(vm.nodesMap[nodeId]);
  251 + };
  252 + }
  253 + });
  254 + }
  255 +
  256 + function updateNodeStyle(node) {
  257 + var newText = prepareNodeText(node);
  258 + if (!angular.equals(node.text, newText)) {
  259 + node.text = newText;
  260 + vm.nodeEditCallbacks.updateNode(node.id, node.text);
  261 + }
  262 + var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx);
  263 + if (!angular.equals(node.state.disabled, newDisabled)) {
  264 + node.state.disabled = newDisabled;
  265 + if (node.state.disabled) {
  266 + vm.nodeEditCallbacks.disableNode(node.id);
  267 + } else {
  268 + vm.nodeEditCallbacks.enableNode(node.id);
  269 + }
  270 + }
  271 + var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx);
  272 + if (!angular.equals(node.children, newHasChildren)) {
  273 + node.children = newHasChildren;
  274 + vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children);
  275 + }
  276 + }
  277 +
  278 + function prepareNodeText(node) {
  279 + var nodeIcon = prepareNodeIcon(node.data.nodeCtx);
  280 + var nodeText = vm.nodeTextFunction(node.data.nodeCtx);
  281 + node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : "";
  282 + return nodeIcon + nodeText;
  283 + }
  284 +
  285 + function loadNodes(node, cb) {
  286 + if (node.id === '#') {
  287 + var tasks = [];
  288 + for (var i=0;i<vm.datasources.length;i++) {
  289 + var datasource = vm.datasources[i];
  290 + tasks.push(datasourceToNode(datasource));
  291 + }
  292 + $q.all(tasks).then((nodes) => {
  293 + cb(prepareNodes(nodes));
  294 + updateNodeData(vm.subscription.data);
  295 + });
  296 + } else {
  297 + if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') {
  298 + if (node.data.nodeCtx.entity.id.entityType === types.entityType.edge) {
  299 + /* assetService.getEdgeAssets(node.data.nodeCtx.entity.id.id, {limit: 20}, null).then(
  300 + (entities) => {
  301 + var tasks = [];
  302 + for (var i=0;i<entities.data.length;i++) {
  303 + var relation = entities.data[i];
  304 + var targetId = node.data.nodeCtx.entity.id.entityType === types.entityType.edge ? relation.id : node.data.nodeCtx.entity.id;
  305 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  306 + }
  307 + $q.all(tasks).then((nodes) => {
  308 + cb(prepareNodes(nodes));
  309 + });
  310 + }
  311 + );
  312 + deviceService.getEdgeDevices(node.data.nodeCtx.entity.id.id, {limit: 20}, null).then(
  313 + (entities) => {
  314 + var tasks = [];
  315 + for (var i=0;i<entities.data.length;i++) {
  316 + var relation = entities.data[i];
  317 + var targetId = node.data.nodeCtx.entity.id.entityType === types.entityType.edge ? relation.id : node.data.nodeCtx.entity.id;
  318 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  319 + }
  320 + $q.all(tasks).then((nodes) => {
  321 + cb(prepareNodes(nodes));
  322 + });
  323 + }
  324 + );
  325 + entityViewService.getEdgeEntityViews(node.data.nodeCtx.entity.id.id, {limit: 20}, null).then(
  326 + (entities) => {
  327 + var tasks = [];
  328 + for (var i=0;i<entities.data.length;i++) {
  329 + var relation = entities.data[i];
  330 + var targetId = node.data.nodeCtx.entity.id.entityType === types.entityType.edge ? relation.id : node.data.nodeCtx.entity.id;
  331 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  332 + }
  333 + $q.all(tasks).then((nodes) => {
  334 + cb(prepareNodes(nodes));
  335 + });
  336 + }
  337 + );
  338 + dashboardService.getEdgeDashboards(node.data.nodeCtx.entity.id.id, {limit: 20}, null).then(
  339 + (entities) => {
  340 + var tasks = [];
  341 + for (var i=0;i<entities.data.length;i++) {
  342 + var relation = entities.data[i];
  343 + var targetId = node.data.nodeCtx.entity.id.entityType === types.entityType.edge ? relation.id : node.data.nodeCtx.entity.id;
  344 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  345 + }
  346 + $q.all(tasks).then((nodes) => {
  347 + cb(prepareNodes(nodes));
  348 + });
  349 + }
  350 + )
  351 + ruleChainService.getEdgeRuleChains(node.data.nodeCtx.entity.id.id, {limit: 20}, null).then(
  352 + (entities) => {
  353 + var tasks = [];
  354 + for (var i=0;i<entities.data.length;i++) {
  355 + var relation = entities.data[i];
  356 + var targetId = node.data.nodeCtx.entity.id.entityType === types.entityType.edge ? relation.id : node.data.nodeCtx.entity.id;
  357 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  358 + }
  359 + $q.all(tasks).then((nodes) => {
  360 + cb(prepareNodes(nodes));
  361 + });
  362 + }
  363 + )
  364 + */
  365 +
  366 + entityIdToNodeEdge("edgeGroup", "001", node.data.datasource, )
  367 +
  368 + } else {
  369 + var relationQuery = prepareNodeRelationQuery(node.data.nodeCtx);
  370 + entityRelationService.findByQuery(relationQuery, {ignoreErrors: true, ignoreLoading: true}).then(
  371 + (entityRelations) => {
  372 + var tasks = [];
  373 + for (var i=0;i<entityRelations.length;i++) {
  374 + var relation = entityRelations[i];
  375 + var targetId = relationQuery.parameters.direction === types.entitySearchDirection.from ? relation.to : relation.from;
  376 + tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
  377 + }
  378 + $q.all(tasks).then((nodes) => {
  379 + cb(prepareNodes(nodes));
  380 + });
  381 + },
  382 + (error) => {
  383 + var errorText = "Failed to get relations!";
  384 + if (error && error.status === 400) {
  385 + errorText = "Invalid relations query returned by 'Node relations query function'! Please check widget configuration!";
  386 + }
  387 + showError(errorText);
  388 + }
  389 + );
  390 + }
  391 + } else {
  392 + cb([]);
  393 + }
  394 + }
  395 + }
  396 +
  397 + function showError(errorText) {
  398 + var toastParent = angular.element('.tb-edges-hierarchy', $element);
  399 + toast.showError(errorText, toastParent, 'bottom left');
  400 + }
  401 +
  402 + function prepareNodes(nodes) {
  403 + nodes = nodes.filter((node) => node !== null);
  404 + nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx));
  405 + return nodes;
  406 + }
  407 +
  408 + function datasourceToNode(datasource, parentNodeCtx) {
  409 + var deferred = $q.defer();
  410 + resolveEntity(datasource).then(
  411 + (entity) => {
  412 + if (entity != null) {
  413 + var node = {
  414 + id: ++vm.nodeIdCounter
  415 + };
  416 + vm.nodesMap[node.id] = node;
  417 + datasource.nodeId = node.id;
  418 + node.icon = false;
  419 + var nodeCtx = {
  420 + parentNodeCtx: parentNodeCtx,
  421 + entity: entity,
  422 + data: {}
  423 + };
  424 + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
  425 + node.data = {
  426 + datasource: datasource,
  427 + nodeCtx: nodeCtx
  428 + };
  429 + node.state = {
  430 + disabled: vm.nodeDisabledFunction(node.data.nodeCtx),
  431 + opened: vm.nodeOpenedFunction(node.data.nodeCtx)
  432 + };
  433 + node.text = prepareNodeText(node);
  434 + node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx);
  435 + deferred.resolve(node);
  436 + } else {
  437 + deferred.resolve(null);
  438 + }
  439 + }
  440 + );
  441 + return deferred.promise;
  442 + }
  443 +
  444 + function datasourceToNodeEdge(datasource, parentNodeCtx) {
  445 + var deferred = $q.defer();
  446 + resolveEntity(datasource).then(
  447 + (entity) => {
  448 + if (entity != null) {
  449 + var node = {
  450 + id: ++vm.nodeIdCounter
  451 + };
  452 + vm.nodesMap[node.id] = node;
  453 + datasource.nodeId = node.id;
  454 + node.icon = false;
  455 + var nodeCtx = {
  456 + parentNodeCtx: parentNodeCtx,
  457 + entity: entity,
  458 + data: {}
  459 + };
  460 + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
  461 + node.data = {
  462 + datasource: datasource,
  463 + nodeCtx: nodeCtx
  464 + };
  465 + node.state = {
  466 + disabled: vm.nodeDisabledFunction(node.data.nodeCtx),
  467 + opened: vm.nodeOpenedFunction(node.data.nodeCtx)
  468 + };
  469 + node.text = prepareNodeText(node);
  470 + node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx);
  471 + deferred.resolve(node);
  472 + } else {
  473 + deferred.resolve(null);
  474 + }
  475 + }
  476 + );
  477 + return deferred.promise;
  478 + }
  479 +
  480 +
  481 + function entityIdToNodeEdge(entityType, entityId, parentDatasource, parentNodeCtx) {
  482 + var deferred = $q.defer();
  483 + var datasource = {
  484 + dataKeys: parentDatasource.dataKeys,
  485 + type: types.datasourceType.entity,
  486 + entityType: entityType,
  487 + entityId: entityId
  488 + };
  489 + datasourceToNodeEdge(datasource, parentNodeCtx).then(
  490 + (node) => {
  491 + if (node != null) {
  492 + var subscriptionOptions = {
  493 + type: types.widgetType.latest.value,
  494 + datasources: [datasource],
  495 + callbacks: {
  496 + onDataUpdated: (subscription) => {
  497 + updateNodeData(subscription.data);
  498 + }
  499 + }
  500 + };
  501 + vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then(
  502 + (/*subscription*/) => {
  503 + deferred.resolve(node);
  504 + }
  505 + );
  506 + } else {
  507 + deferred.resolve(node);
  508 + }
  509 + }
  510 + );
  511 + return deferred.promise;
  512 + }
  513 +
  514 + function entityIdToNode(entityType, entityId, parentDatasource, parentNodeCtx) {
  515 + var deferred = $q.defer();
  516 + var datasource = {
  517 + dataKeys: parentDatasource.dataKeys,
  518 + type: types.datasourceType.entity,
  519 + entityType: entityType,
  520 + entityId: entityId
  521 + };
  522 + datasourceToNode(datasource, parentNodeCtx).then(
  523 + (node) => {
  524 + if (node != null) {
  525 + var subscriptionOptions = {
  526 + type: types.widgetType.latest.value,
  527 + datasources: [datasource],
  528 + callbacks: {
  529 + onDataUpdated: (subscription) => {
  530 + updateNodeData(subscription.data);
  531 + }
  532 + }
  533 + };
  534 + vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then(
  535 + (/*subscription*/) => {
  536 + deferred.resolve(node);
  537 + }
  538 + );
  539 + } else {
  540 + deferred.resolve(node);
  541 + }
  542 + }
  543 + );
  544 + return deferred.promise;
  545 + }
  546 +
  547 + function resolveEntity(datasource) {
  548 + var deferred = $q.defer();
  549 + if (datasource.type === types.datasourceType.function) {
  550 + var entity = {
  551 + id: {
  552 + entityType: "function"
  553 + },
  554 + name: datasource.name
  555 + }
  556 + deferred.resolve(entity);
  557 + } else {
  558 + entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then(
  559 + (entity) => {
  560 + deferred.resolve(entity);
  561 + },
  562 + () => {
  563 + deferred.resolve(null);
  564 + }
  565 + );
  566 + }
  567 + return deferred.promise;
  568 + }
  569 +
  570 +
  571 + function prepareNodeRelationQuery(nodeCtx) {
  572 + var relationQuery = vm.nodeRelationQueryFunction(nodeCtx);
  573 + if (relationQuery && relationQuery === 'default') {
  574 + relationQuery = defaultNodeRelationQueryFunction(nodeCtx);
  575 + }
  576 + return relationQuery;
  577 + }
  578 +
  579 + function defaultNodeRelationQueryFunction(nodeCtx) {
  580 + var entity = nodeCtx.entity;
  581 + var query = {
  582 + parameters: {
  583 + rootId: entity.id.id,
  584 + rootType: entity.id.entityType,
  585 + direction: types.entitySearchDirection.from,
  586 + relationTypeGroup: "COMMON",
  587 + maxLevel: 1
  588 + },
  589 + filters: [
  590 + {
  591 + relationType: "Contains",
  592 + entityTypes: []
  593 + }
  594 + ]
  595 + };
  596 + return query;
  597 + }
  598 +
  599 + function prepareNodeIcon(nodeCtx) {
  600 + var iconInfo = vm.nodeIconFunction(nodeCtx);
  601 + if (iconInfo && iconInfo === 'default') {
  602 + iconInfo = defaultNodeIconFunction(nodeCtx);
  603 + }
  604 + if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) {
  605 + if (iconInfo.materialIcon) {
  606 + return materialIconHtml(iconInfo.materialIcon);
  607 + } else {
  608 + return iconUrlHtml(iconInfo.iconUrl);
  609 + }
  610 + } else {
  611 + return "";
  612 + }
  613 + }
  614 +
  615 + function materialIconHtml(materialIcon) {
  616 + return '<md-icon aria-label="'+materialIcon+'" class="node-icon material-icons" role="img" aria-hidden="false">'+materialIcon+'</md-icon>';
  617 + }
  618 +
  619 + function iconUrlHtml(iconUrl) {
  620 + return '<div class="node-icon" style="background-image: url('+iconUrl+');">&nbsp;</div>';
  621 + }
  622 +
  623 + function defaultNodeIconFunction(nodeCtx) {
  624 + var materialIcon = 'insert_drive_file';
  625 + var entity = nodeCtx.entity;
  626 + if (entity && entity.id && entity.id.entityType) {
  627 + switch (entity.id.entityType) {
  628 + case 'function':
  629 + materialIcon = 'functions';
  630 + break;
  631 + case types.entityType.device:
  632 + materialIcon = 'devices_other';
  633 + break;
  634 + case types.entityType.asset:
  635 + materialIcon = 'domain';
  636 + break;
  637 + case types.entityType.tenant:
  638 + materialIcon = 'supervisor_account';
  639 + break;
  640 + case types.entityType.customer:
  641 + materialIcon = 'supervisor_account';
  642 + break;
  643 + case types.entityType.user:
  644 + materialIcon = 'account_circle';
  645 + break;
  646 + case types.entityType.dashboard:
  647 + materialIcon = 'dashboards';
  648 + break;
  649 + case types.entityType.alarm:
  650 + materialIcon = 'notifications_active';
  651 + break;
  652 + case types.entityType.entityView:
  653 + materialIcon = 'view_quilt';
  654 + break;
  655 + case types.entityType.edge:
  656 + materialIcon = 'router';
  657 + break;
  658 + case types.entityType.rulechain:
  659 + materialIcon = 'settings_ethernet';
  660 + break;
  661 + }
  662 + }
  663 + return {
  664 + materialIcon: materialIcon
  665 + };
  666 + }
  667 +
  668 + function defaultNodeOpenedFunction(nodeCtx) {
  669 + return nodeCtx.level <= 4;
  670 + }
  671 +
  672 + function defaultSortFunction(nodeCtx1, nodeCtx2) {
  673 + var result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);
  674 + if (result === 0) {
  675 + result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);
  676 + }
  677 + return result;
  678 + }
  679 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +.tb-has-timewindow {
  17 + .tb-edges-hierarchy {
  18 + md-toolbar {
  19 + min-height: 60px;
  20 + max-height: 60px;
  21 + }
  22 + }
  23 +}
  24 +
  25 +.tb-edges-hierarchy {
  26 + md-toolbar {
  27 + min-height: 39px;
  28 + max-height: 39px;
  29 + }
  30 +
  31 + .tb-entities-nav-tree-panel {
  32 + overflow-x: auto;
  33 + overflow-y: auto;
  34 +
  35 + .tb-nav-tree-container {
  36 + &.jstree-proton {
  37 + .jstree-anchor {
  38 + div.node-icon {
  39 + display: inline-block;
  40 + width: 22px;
  41 + height: 22px;
  42 + margin-right: 2px;
  43 + margin-bottom: 2px;
  44 + background-color: transparent;
  45 + background-repeat: no-repeat;
  46 + background-attachment: scroll;
  47 + background-position: center center;
  48 + background-size: 18px 18px;
  49 + }
  50 +
  51 + md-icon.node-icon {
  52 + width: 22px;
  53 + min-width: 22px;
  54 + height: 22px;
  55 + min-height: 22px;
  56 + margin-right: 2px;
  57 + margin-bottom: 2px;
  58 + color: inherit;
  59 +
  60 + &.material-icons { /* stylelint-disable-line selector-max-class */
  61 + font-size: 18px;
  62 + line-height: 22px;
  63 + text-align: center;
  64 + }
  65 + }
  66 +
  67 + &.jstree-hovered:not(.jstree-clicked),
  68 + &.jstree-disabled {
  69 + div.node-icon { /* stylelint-disable-line selector-max-class */
  70 + opacity: .5;
  71 + }
  72 + }
  73 + }
  74 + }
  75 + }
  76 + }
  77 +}
  78 +
  79 +@media (max-width: 768px) {
  80 + .tb-edges-hierarchy {
  81 + .tb-entities-nav-tree-panel {
  82 + .tb-nav-tree-container {
  83 + &.jstree-proton-responsive {
  84 + .jstree-anchor {
  85 + div.node-icon {
  86 + width: 40px;
  87 + height: 40px;
  88 + margin: 0;
  89 + background-size: 24px 24px;
  90 + }
  91 +
  92 + md-icon.node-icon {
  93 + width: 40px;
  94 + min-width: 40px;
  95 + height: 40px;
  96 + min-height: 40px;
  97 + margin: 0;
  98 +
  99 + &.material-icons { /* stylelint-disable-line selector-max-class */
  100 + font-size: 24px;
  101 + line-height: 40px;
  102 + }
  103 + }
  104 + }
  105 + }
  106 + }
  107 + }
  108 + }
  109 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-absolute-fill tb-edges-hierarchy" layout="column">
  19 + <div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column">
  20 + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
  21 + <div class="md-toolbar-tools">
  22 + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
  23 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
  24 + <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
  25 + {{'entity.search' | translate}}
  26 + </md-tooltip>
  27 + </md-button>
  28 + <md-input-container flex>
  29 + <label>&nbsp;</label>
  30 + <input ng-model="vm.query.search" class="searchInput" placeholder="{{'entity.search' | translate}}"/>
  31 + </md-input-container>
  32 + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
  33 + <md-icon aria-label="Close" class="material-icons">close</md-icon>
  34 + <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
  35 + {{ 'action.close' | translate }}
  36 + </md-tooltip>
  37 + </md-button>
  38 + </div>
  39 + </md-toolbar>
  40 + <div flex class="tb-entities-nav-tree-panel">
  41 + <tb-nav-tree
  42 + load-nodes="vm.loadNodes"
  43 + on-node-selected="vm.onNodeSelected(node, event)"
  44 + on-nodes-inserted="vm.onNodesInserted(nodes, parent)"
  45 + edit-callbacks="vm.nodeEditCallbacks"
  46 + enable-search="true"
  47 + search-callback="vm.searchCallback(searchText, node)"
  48 + ></tb-nav-tree>
  49 + </div>
  50 + </div>
  51 +</div>
... ...