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 @@
+
+