Edge widgets with empty datasource

This commit is contained in:
deaflynx 2020-12-29 14:44:18 +02:00
parent 19c04f3387
commit 58c580ecaf
9 changed files with 9 additions and 889 deletions

File diff suppressed because one or more lines are too long

View File

@ -439,6 +439,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
this.deleteSystemWidgetBundle("input_widgets"); this.deleteSystemWidgetBundle("input_widgets");
this.deleteSystemWidgetBundle("date"); this.deleteSystemWidgetBundle("date");
this.deleteSystemWidgetBundle("entity_admin_widgets"); this.deleteSystemWidgetBundle("entity_admin_widgets");
this.deleteSystemWidgetBundle("edge_widgets");
installScripts.loadSystemWidgets(); installScripts.loadSystemWidgets();
} }

View File

@ -126,7 +126,7 @@ export class WidgetService {
return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe( return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe(
map((types) => { map((types) => {
if (!getCurrentAuthState(this.store).edgesSupportEnabled) { if (!getCurrentAuthState(this.store).edgesSupportEnabled) {
types = types.filter(type => type.alias !== 'edges_hierarchy') types = types.filter(type => type.alias !== 'edges_overview')
} }
types = types.sort((a, b) => { types = types.sort((a, b) => {
let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);

View File

@ -1,51 +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.
-->
<div class="tb-edge-instances-overview tb-absolute-fill" tb-toast toastTarget="{{ toastTargetId }}">
<div fxFlex fxLayout="column" class="tb-absolute-fill">
<mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode">
<div class="mat-toolbar-tools">
<button mat-button mat-icon-button
matTooltip="{{ 'action.search' | translate }}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<mat-form-field fxFlex>
<mat-label>&nbsp;</mat-label>
<input #searchInput matInput
[(ngModel)]="textSearch"
placeholder="{{ 'entity.search' | translate }}"/>
</mat-form-field>
<button mat-button mat-icon-button (click)="exitFilterMode()"
matTooltip="{{ 'action.close' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-toolbar>
<div fxFlex class="tb-edges-nav-tree-panel">
<tb-nav-tree
[loadNodes]="loadNodes"
[onNodeSelected]="onNodeSelected"
[onNodesInserted]="onNodesInserted"
[editCallbacks]="nodeEditCallbacks"
enableSearch="true"
[searchCallback]="searchCallback"
></tb-nav-tree>
</div>
</div>
</div>

View File

@ -1,554 +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 { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { WidgetAction, WidgetContext } from '@home/models/widget-component.models';
import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@shared/models/widget.models';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import cssjs from '@core/css/css';
import { fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { constructTableCssString } from '@home/components/widget/lib/table-widget.models';
import { Overlay } from '@angular/cdk/overlay';
import {
LoadNodesCallback,
NavTreeEditCallbacks, NodesCallback,
NodeSearchCallback,
NodeSelectedCallback,
NodesInsertedCallback
} from '@shared/components/nav-tree.component';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, hashCode } from '@core/utils';
import {
defaultNodeIconFunction,
defaultNodeOpenedFunction,
defaultNodeRelationQueryFunction,
defaultNodesSortFunction,
EdgeGroupsNodeData,
edgeGroupsNodeText,
edgeGroupsTypes,
EdgeNodeData,
edgeNodeText,
EdgeInstancesOverviewNode,
EdgeInstancesOverviewWidgetSettings,
HierarchyNavTreeNode,
HierarchyNodeContext,
HierarchyNodeDatasource,
iconUrlHtml,
loadNodeCtxFunction,
materialIconHtml,
NodeDisabledFunction,
NodeHasChildrenFunction,
NodeIconFunction,
NodeOpenedFunction,
NodeRelationQueryFunction,
NodesSortFunction,
NodeTextFunction
} from '@home/components/widget/lib/edge-instances-overview-widget.models';
import { EdgeService } from "@core/http/edge.service";
import { PageLink } from "@shared/models/page/page-link";
import { Edge, EdgeInfo } from "@shared/models/edge.models";
import { TranslateService } from "@ngx-translate/core";
import { EntityService } from "@core/http/entity.service";
import { Direction, SortOrder } from "@shared/models/page/sort-order";
import { EntityRelationsQuery } from "@shared/models/relation.models";
import { EntityFilter } from "@shared/models/query/query.models";
import { AliasFilterType, RelationsQueryFilter } from "@shared/models/alias.models";
@Component({
selector: 'tb-edge-instances-overview-widget',
templateUrl: './edge-instances-overview-widget.component.html',
styleUrls: ['./edge-instances-overview-widget.component.scss']
})
export class EdgeInstancesOverviewWidgetComponent extends PageComponent implements OnInit, AfterViewInit {
@Input()
ctx: WidgetContext;
@ViewChild('searchInput') searchInputField: ElementRef;
public toastTargetId = 'edge-instances-overview-' + this.utils.guid();
public textSearchMode = false;
public textSearch = null;
public nodeEditCallbacks: NavTreeEditCallbacks = {};
private settings: EdgeInstancesOverviewWidgetSettings;
private widgetConfig: WidgetConfig;
private subscription: IWidgetSubscription;
private datasources: Array<HierarchyNodeDatasource>;
private data: Array<Array<DatasourceData>>;
private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {};
private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {};
private nodeIdCounter = 0;
private nodeRelationQueryFunction: NodeRelationQueryFunction;
private nodeIconFunction: NodeIconFunction;
private nodeTextFunction: NodeTextFunction;
private nodeDisabledFunction: NodeDisabledFunction;
private nodeOpenedFunction: NodeOpenedFunction;
private nodeHasChildrenFunction: NodeHasChildrenFunction;
private nodesSortFunction: NodesSortFunction;
private edgeNodesMap: {[parentNodeId: string]: {[edgeId: string]: string}} = {};
private edgeGroupsNodesMap: {[edgeNodeId: string]: {[groupType: string]: string}} = {};
private searchAction: WidgetAction = {
name: 'action.search',
show: false,
icon: 'search',
onAction: () => {
this.enterFilterMode();
}
};
constructor(protected store: Store<AppState>,
private elementRef: ElementRef,
private edgeService: EdgeService,
private entityService: EntityService,
private translateService: TranslateService,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private utils: UtilsService) {
super(store);
}
ngOnInit(): void {
this.ctx.$scope.edgeInstancesOverviewWidget = this;
this.settings = this.ctx.settings;
this.widgetConfig = this.ctx.widgetConfig;
this.subscription = this.ctx.defaultSubscription;
this.datasources = this.subscription.datasources as Array<HierarchyNodeDatasource>;
this.data = this.subscription.dataPages[0].data;
this.initializeConfig();
this.ctx.updateWidgetParams();
}
ngAfterViewInit(): void {
fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
this.updateSearchNodes();
})
)
.subscribe();
}
public onDataUpdated() {
this.updateNodeData(this.subscription.data);
}
private initializeConfig() {
this.ctx.widgetActions = [this.searchAction];
const testNodeCtx: HierarchyNodeContext = {
entity: {
id: {
entityType: EntityType.DEVICE,
id: '123'
},
name: 'TEST DEV1'
},
data: {},
level: 2
};
const parentNodeCtx = deepClone(testNodeCtx);
parentNodeCtx.level = 1;
testNodeCtx.parentNodeCtx = parentNodeCtx;
this.nodeRelationQueryFunction = loadNodeCtxFunction(this.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx);
this.nodeIconFunction = loadNodeCtxFunction(this.settings.nodeIconFunction, 'nodeCtx', testNodeCtx);
this.nodeTextFunction = loadNodeCtxFunction(this.settings.nodeTextFunction, 'nodeCtx', testNodeCtx);
this.nodeDisabledFunction = loadNodeCtxFunction(this.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx);
this.nodeOpenedFunction = loadNodeCtxFunction(this.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx);
this.nodeHasChildrenFunction = loadNodeCtxFunction(this.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx);
const testNodeCtx2 = deepClone(testNodeCtx);
testNodeCtx2.entity.name = 'TEST DEV2';
this.nodesSortFunction = loadNodeCtxFunction(this.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2);
this.nodeRelationQueryFunction = this.nodeRelationQueryFunction || defaultNodeRelationQueryFunction;
this.nodeIconFunction = this.nodeIconFunction || defaultNodeIconFunction;
this.nodeTextFunction = this.nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name);
this.nodeDisabledFunction = this.nodeDisabledFunction || (() => false);
this.nodeOpenedFunction = this.nodeOpenedFunction || defaultNodeOpenedFunction;
this.nodeHasChildrenFunction = this.nodeHasChildrenFunction || (() => true);
this.nodesSortFunction = this.nodesSortFunction || defaultNodesSortFunction;
const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs();
cssParser.testMode = false;
const namespace = 'edges-instances-overview-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace);
}
private enterFilterMode() {
this.textSearchMode = true;
this.textSearch = '';
this.ctx.hideTitlePanel = true;
this.ctx.detectChanges(true);
setTimeout(() => {
this.searchInputField.nativeElement.focus();
this.searchInputField.nativeElement.setSelectionRange(0, 0);
}, 10);
}
exitFilterMode() {
this.textSearchMode = false;
this.textSearch = null;
this.updateSearchNodes();
this.ctx.hideTitlePanel = false;
this.ctx.detectChanges(true);
}
private updateSearchNodes() {
if (this.textSearch != null) {
this.nodeEditCallbacks.search(this.textSearch);
} else {
this.nodeEditCallbacks.clearSearch();
}
}
private updateNodeData(subscriptionData: Array<DatasourceData>) {
const affectedNodes: string[] = [];
if (subscriptionData) {
subscriptionData.forEach((datasourceData) => {
const datasource = datasourceData.datasource as HierarchyNodeDatasource;
if (datasource.nodeId) {
const node = this.nodesMap[datasource.nodeId];
const key = datasourceData.dataKey.label;
let value;
if (datasourceData.data && datasourceData.data.length) {
value = datasourceData.data[0][1];
}
if (node.data.nodeCtx.data[key] !== value) {
if (affectedNodes.indexOf(datasource.nodeId) === -1) {
affectedNodes.push(datasource.nodeId);
}
node.data.nodeCtx.data[key] = value;
}
}
});
}
affectedNodes.forEach((nodeId) => {
const node: HierarchyNavTreeNode = this.nodeEditCallbacks.getNode(nodeId);
if (node) {
this.updateNodeStyle(this.nodesMap[nodeId]);
} else {
this.pendingUpdateNodeTasks[nodeId] = () => {
this.updateNodeStyle(this.nodesMap[nodeId]);
};
}
});
}
public loadNodes: LoadNodesCallback = (node, cb) => {
if (node.id === '#') {
const sortOrder: SortOrder = { property: 'name', direction: Direction.ASC };
const pageLink = new PageLink(100, 0, null, sortOrder);
this.edgeService.getTenantEdgeInfos(pageLink).subscribe(
(edges) => {
cb(this.edgesToNodes(node.id, edges.data))
});
} else if (node.data.type === 'edge') {
const edge = node.data.entity;
cb(this.loadNodesForEdge(node.id, edge));
} else if (node.data.type === 'edgeGroups') {
const pageLink = new PageLink(100);
this.entityService.getAssignedToEdgeEntitiesByType(node, pageLink).subscribe(
(entities) => {
if (entities.data.length > 0) {
cb(this.edgesToNodes(node.id, entities.data));
} else {
cb([]);
}
}
)
}
}
private loadNodesForEdge(parentNodeId: string, edge: EdgeInfo): EdgeInstancesOverviewNode[] {
const nodes: EdgeInstancesOverviewNode[] = [];
const nodesMap = {};
this.edgeGroupsNodesMap[parentNodeId] = nodesMap;
edgeGroupsTypes.forEach((entityType) => {
const node: EdgeInstancesOverviewNode = {
id: (++this.nodeIdCounter)+'',
icon: false,
text: edgeGroupsNodeText(this.translateService, entityType),
children: true,
data: {
type: 'edgeGroups',
entityType,
edge,
internalId: edge.id.id + '_' + entityType
} as EdgeGroupsNodeData
};
nodes.push(node);
nodesMap[entityType] = node.id;
});
return nodes;
}
private createEdgeNode(parentNodeId: string, edge: Edge): EdgeInstancesOverviewNode {
let nodesMap = this.edgeNodesMap[parentNodeId];
if (!nodesMap) {
nodesMap = {};
this.edgeNodesMap[parentNodeId] = nodesMap;
}
const node: EdgeInstancesOverviewNode = {
id: (++this.nodeIdCounter)+'',
icon: false,
text: edgeNodeText(edge),
children: parentNodeId === '#',
state: {
disabled: false
},
data: {
type: 'edge',
entity: edge,
internalId: edge.id.id
} as EdgeNodeData
};
nodesMap[edge.id.id] = node.id;
return node;
}
private edgesToNodes(parentNodeId: string, edges: Array<Edge>): EdgeInstancesOverviewNode[] {
const nodes: EdgeInstancesOverviewNode[] = [];
this.edgeNodesMap[parentNodeId] = {};
if (edges) {
edges.forEach((edge) => {
const node = this.createEdgeNode(parentNodeId, edge);
nodes.push(node);
});
}
return nodes;
}
public onNodeSelected: NodeSelectedCallback = (node, event) => {
let nodeId;
if (!node) {
nodeId = -1;
} else {
nodeId = node.id;
}
if (nodeId !== -1) {
const selectedNode = this.nodesMap[nodeId];
if (selectedNode) {
const descriptors = this.ctx.actionsApi.getActionDescriptors('nodeSelected');
if (descriptors.length) {
const entity = selectedNode.data.nodeCtx.entity;
this.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx });
}
}
}
}
public onNodesInserted: NodesInsertedCallback = (nodes) => {
if (nodes) {
nodes.forEach((nodeId) => {
const task = this.pendingUpdateNodeTasks[nodeId];
if (task) {
task();
delete this.pendingUpdateNodeTasks[nodeId];
}
});
}
}
public searchCallback: NodeSearchCallback = (searchText, node) => {
const theNode = this.nodesMap[node.id];
if (theNode && theNode.data.searchText) {
return theNode.data.searchText.includes(searchText.toLowerCase());
}
return false;
}
private updateNodeStyle(node: HierarchyNavTreeNode) {
const newText = this.prepareNodeText(node);
if (node.text !== newText) {
node.text = newText;
this.nodeEditCallbacks.updateNode(node.id, node.text);
}
const newDisabled = this.nodeDisabledFunction(node.data.nodeCtx);
if (node.state.disabled !== newDisabled) {
node.state.disabled = newDisabled;
if (node.state.disabled) {
this.nodeEditCallbacks.disableNode(node.id);
} else {
this.nodeEditCallbacks.enableNode(node.id);
}
}
const newHasChildren = this.nodeHasChildrenFunction(node.data.nodeCtx);
if (node.children !== newHasChildren) {
node.children = newHasChildren;
this.nodeEditCallbacks.setNodeHasChildren(node.id, node.children);
}
}
private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] {
nodes = nodes.filter((node) => node !== null);
nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx));
return nodes;
}
private prepareNodeText(node: HierarchyNavTreeNode): string {
const nodeIcon = this.prepareNodeIcon(node.data.nodeCtx);
const nodeText = this.nodeTextFunction(node.data.nodeCtx);
node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : '';
return nodeIcon + nodeText;
}
private prepareNodeIcon(nodeCtx: HierarchyNodeContext): string {
let iconInfo = this.nodeIconFunction(nodeCtx);
if (iconInfo) {
if (iconInfo === 'default') {
iconInfo = defaultNodeIconFunction(nodeCtx);
}
if (iconInfo && iconInfo !== 'default' && (iconInfo.iconUrl || iconInfo.materialIcon)) {
if (iconInfo.materialIcon) {
return materialIconHtml(iconInfo.materialIcon);
} else {
return iconUrlHtml(iconInfo.iconUrl);
}
} else {
return '';
}
} else {
return '';
}
}
private datasourceToNode(datasource: HierarchyNodeDatasource,
data: DatasourceData[],
parentNodeCtx?: HierarchyNodeContext): HierarchyNavTreeNode {
const node: HierarchyNavTreeNode = {
id: (++this.nodeIdCounter) + ''
};
this.nodesMap[node.id] = node;
datasource.nodeId = node.id;
node.icon = false;
const nodeCtx: HierarchyNodeContext = {
parentNodeCtx,
entity: {
id: {
id: datasource.entityId,
entityType: datasource.entityType
},
name: datasource.entityName,
label: datasource.entityLabel ? datasource.entityLabel : datasource.entityName
},
data: {}
};
datasource.dataKeys.forEach((dataKey, index) => {
const keyData = data[index].data;
if (keyData && keyData.length && keyData[0].length > 1) {
nodeCtx.data[dataKey.label] = keyData[0][1];
} else {
nodeCtx.data[dataKey.label] = '';
}
});
nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
node.data = {
datasource,
nodeCtx
};
node.state = {
disabled: this.nodeDisabledFunction(node.data.nodeCtx),
opened: this.nodeOpenedFunction(node.data.nodeCtx)
};
node.text = this.prepareNodeText(node);
node.children = this.nodeHasChildrenFunction(node.data.nodeCtx);
return node;
}
private loadChildren(parentNode: HierarchyNavTreeNode, datasource: HierarchyNodeDatasource, childrenNodesLoadCb: NodesCallback) {
const nodeCtx = parentNode.data.nodeCtx;
nodeCtx.childrenNodesLoaded = false;
const entityFilter = this.prepareNodeRelationsQueryFilter(nodeCtx);
const childrenDatasource = {
dataKeys: datasource.dataKeys,
type: DatasourceType.entity,
filterId: datasource.filterId,
entityFilter
} as HierarchyNodeDatasource;
const subscriptionOptions: WidgetSubscriptionOptions = {
type: widgetType.latest,
datasources: [childrenDatasource],
callbacks: {
onSubscriptionMessage: (subscription, message) => {
this.ctx.showToast(message.severity, message.message, undefined,
'bottom', 'left', this.toastTargetId);
},
onInitialPageDataChanged: (subscription) => {
this.ctx.subscriptionApi.removeSubscription(subscription.id);
this.nodeEditCallbacks.refreshNode(parentNode.id);
},
onDataUpdated: subscription => {
if (nodeCtx.childrenNodesLoaded) {
this.updateNodeData(subscription.data);
} else {
const datasourcesPageData = subscription.datasourcePages[0];
const dataPageData = subscription.dataPages[0];
const childNodes: HierarchyNavTreeNode[] = [];
datasourcesPageData.data.forEach((childDatasource, index) => {
childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index]));
});
nodeCtx.childrenNodesLoaded = true;
childrenNodesLoadCb(this.prepareNodes(childNodes));
}
}
}
};
this.ctx.subscriptionApi.createSubscription(subscriptionOptions, true);
}
private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery {
let relationQuery = this.nodeRelationQueryFunction(nodeCtx);
if (relationQuery && relationQuery === 'default') {
relationQuery = defaultNodeRelationQueryFunction(nodeCtx);
}
return relationQuery as EntityRelationsQuery;
}
private prepareNodeRelationsQueryFilter(nodeCtx: HierarchyNodeContext): EntityFilter {
const relationQuery = this.prepareNodeRelationQuery(nodeCtx);
return {
rootEntity: {
id: relationQuery.parameters.rootId,
entityType: relationQuery.parameters.rootType
},
direction: relationQuery.parameters.direction,
filters: relationQuery.filters,
maxLevel: relationQuery.parameters.maxLevel,
fetchLastLevelOnly: relationQuery.parameters.fetchLastLevelOnly,
type: AliasFilterType.relationsQuery
} as RelationsQueryFilter;
}
}

View File

@ -1,260 +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 { BaseData } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { NavTreeNode } from '@shared/components/nav-tree.component';
import { Datasource } from '@shared/models/widget.models';
import { isDefined, isUndefined } from '@core/utils';
import { EntityRelationsQuery, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models';
import { EntityType } from '@shared/models/entity-type.models';
import { Edge } from "@shared/models/edge.models";
import { TranslateService } from "@ngx-translate/core";
export interface EdgeInstancesOverviewWidgetSettings {
nodeRelationQueryFunction: string;
nodeHasChildrenFunction: string;
nodeOpenedFunction: string;
nodeDisabledFunction: string;
nodeIconFunction: string;
nodeTextFunction: string;
nodesSortFunction: string;
}
export interface HierarchyNodeContext {
parentNodeCtx?: HierarchyNodeContext;
entity: BaseData<EntityId>;
childrenNodesLoaded?: boolean;
level?: number;
data: {[key: string]: any};
}
export interface HierarchyNavTreeNode extends NavTreeNode {
data?: {
datasource: HierarchyNodeDatasource;
nodeCtx: HierarchyNodeContext;
searchText?: string;
};
}
export interface HierarchyNodeDatasource extends Datasource {
nodeId: string;
}
export interface HierarchyNodeIconInfo {
iconUrl?: string;
materialIcon?: string;
}
export type NodeRelationQueryFunction = (nodeCtx: HierarchyNodeContext) => EntityRelationsQuery | 'default';
export type NodeTextFunction = (nodeCtx: HierarchyNodeContext) => string;
export type NodeDisabledFunction = (nodeCtx: HierarchyNodeContext) => boolean;
export type NodeIconFunction = (nodeCtx: HierarchyNodeContext) => HierarchyNodeIconInfo | 'default';
export type NodeOpenedFunction = (nodeCtx: HierarchyNodeContext) => boolean;
export type NodeHasChildrenFunction = (nodeCtx: HierarchyNodeContext) => boolean;
export type NodesSortFunction = (nodeCtx1: HierarchyNodeContext, nodeCtx2: HierarchyNodeContext) => number;
export function loadNodeCtxFunction<F extends (...args: any[]) => any>(functionBody: string, argNames: string, ...args: any[]): F {
let nodeCtxFunction: F = null;
if (isDefined(functionBody) && functionBody.length) {
try {
nodeCtxFunction = new Function(argNames, functionBody) as F;
const res = nodeCtxFunction.apply(null, args);
if (isUndefined(res)) {
nodeCtxFunction = null;
}
} catch (e) {
nodeCtxFunction = null;
}
}
return nodeCtxFunction;
}
export function materialIconHtml(materialIcon: string): string {
return '<mat-icon class="node-icon material-icons" role="img" aria-hidden="false">' + materialIcon + '</mat-icon>';
}
export function iconUrlHtml(iconUrl: string): string {
return '<div class="node-icon" style="background-image: url(' + iconUrl + ');">&nbsp;</div>';
}
export function edgeGroupsNodeText(translate: TranslateService, entityType: EntityType): string {
const nodeIcon = materialIconByEntityType(entityType);
const nodeText = textForEdgeGroupsType(translate, entityType);
return nodeIcon + nodeText;
}
export function edgeNodeText(edge: Edge): string {
const nodeIcon = materialIconByEntityType(edge.id.entityType);
const nodeText = edge.name;
return nodeIcon + nodeText;
}
export function materialIconByEntityType(entityType: EntityType): string {
let materialIcon = 'insert_drive_file';
switch (entityType) {
case EntityType.DEVICE:
materialIcon = 'devices_other';
break;
case EntityType.ASSET:
materialIcon = 'domain';
break;
case EntityType.CUSTOMER:
materialIcon = 'supervisor_account';
break;
case EntityType.USER:
materialIcon = 'account_circle';
break;
case EntityType.DASHBOARD:
materialIcon = 'dashboards';
break;
case EntityType.ENTITY_VIEW:
materialIcon = 'view_quilt';
break;
case EntityType.RULE_CHAIN:
materialIcon = 'settings_ethernet';
break;
case EntityType.EDGE:
materialIcon = 'router';
break;
}
return '<mat-icon class="node-icon material-icons" role="img" aria-hidden="false">' + materialIcon + '</mat-icon>';
}
export function textForEdgeGroupsType(translate: TranslateService, entityType: EntityType): string {
let textForEdgeGroupsType: string = '';
switch (entityType) {
case EntityType.DEVICE:
textForEdgeGroupsType = 'device.devices';
break;
case EntityType.ASSET:
textForEdgeGroupsType = 'asset.assets';
break;
case EntityType.DASHBOARD:
textForEdgeGroupsType = 'dashboard.dashboards';
break;
case EntityType.ENTITY_VIEW:
textForEdgeGroupsType = 'entity-view.entity-views';
break;
case EntityType.RULE_CHAIN:
textForEdgeGroupsType = 'rulechain.rulechains';
break;
}
return translate.instant(textForEdgeGroupsType);
}
export const defaultNodeRelationQueryFunction: NodeRelationQueryFunction = nodeCtx => {
const entity = nodeCtx.entity;
const query: EntityRelationsQuery = {
parameters: {
rootId: entity.id.id,
rootType: entity.id.entityType as EntityType,
direction: EntitySearchDirection.FROM,
relationTypeGroup: RelationTypeGroup.COMMON,
maxLevel: 1
},
filters: [
{
relationType: 'Contains',
entityTypes: []
}
]
};
return query;
};
export const edgeGroupsTypes: EntityType[] = [
EntityType.ASSET,
EntityType.DEVICE,
EntityType.ENTITY_VIEW,
EntityType.DASHBOARD,
EntityType.RULE_CHAIN
]
export const defaultNodeIconFunction: NodeIconFunction = nodeCtx => {
let materialIcon = 'insert_drive_file';
const entity = nodeCtx.entity;
if (entity && entity.id && entity.id.entityType) {
switch (entity.id.entityType as EntityType | string) {
case 'function':
materialIcon = 'functions';
break;
case EntityType.DEVICE:
materialIcon = 'devices_other';
break;
case EntityType.ASSET:
materialIcon = 'domain';
break;
case EntityType.TENANT:
materialIcon = 'supervisor_account';
break;
case EntityType.CUSTOMER:
materialIcon = 'supervisor_account';
break;
case EntityType.USER:
materialIcon = 'account_circle';
break;
case EntityType.DASHBOARD:
materialIcon = 'dashboards';
break;
case EntityType.ALARM:
materialIcon = 'notifications_active';
break;
case EntityType.ENTITY_VIEW:
materialIcon = 'view_quilt';
break;
}
}
return {
materialIcon
};
};
export const defaultNodeOpenedFunction: NodeOpenedFunction = nodeCtx => {
return nodeCtx.level <= 4;
};
export const defaultNodesSortFunction: NodesSortFunction = (nodeCtx1, nodeCtx2) => {
let result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);
if (result === 0) {
result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);
}
return result;
};
export interface EdgeInstancesOverviewNode extends NavTreeNode {
data?: EdgeInstancesOverviewNodeData;
}
export type EdgeInstancesOverviewNodeData = EdgeGroupsNodeData | EdgeNodeData;
export interface EdgeGroupsNodeData extends BaseEdgeInstancesOverviewNodeData {
type: 'edgeGroups';
entityType: EntityType;
edge: Edge;
}
export interface EdgeNodeData extends BaseEdgeInstancesOverviewNodeData {
type: 'edge';
entity: Edge;
}
export interface BaseEdgeInstancesOverviewNodeData {
type: EdgeInstancesOverviewNodeType;
internalId: string;
}
export type EdgeInstancesOverviewNodeType = 'edge' | 'edgeGroups';

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
:host-context(.tb-has-timewindow) { :host-context(.tb-has-timewindow) {
.tb-edge-instances-overview { .tb-edges-overview {
mat-toolbar { mat-toolbar {
height: 60px; height: 60px;
max-height: 60px; max-height: 60px;
@ -27,7 +27,7 @@
} }
:host { :host {
.tb-edge-instances-overview { .tb-edges-overview {
mat-toolbar.mat-table-toolbar:not([color="primary"]) { mat-toolbar.mat-table-toolbar:not([color="primary"]) {
background: transparent; background: transparent;
} }
@ -40,7 +40,7 @@
} }
} }
.tb-edges-nav-tree-panel { .tb-entities-nav-tree-panel {
overflow-x: auto; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
} }

View File

@ -35,7 +35,7 @@ import { TripAnimationComponent } from './trip-animation/trip-animation.componen
import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component';
import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; import { GatewayFormComponent } from './lib/gateway/gateway-form.component';
import { ImportExportService } from '@home/components/import-export/import-export.service'; import { ImportExportService } from '@home/components/import-export/import-export.service';
import { EdgeInstancesOverviewWidgetComponent } from "@home/components/widget/lib/edge-instances-overview-widget.component"; import { EdgesOverviewWidgetComponent } from "@home/components/widget/lib/edges-overview-widget.component";
@NgModule({ @NgModule({
declarations: declarations:
@ -46,7 +46,7 @@ import { EdgeInstancesOverviewWidgetComponent } from "@home/components/widget/li
AlarmsTableWidgetComponent, AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent, TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent, EntitiesHierarchyWidgetComponent,
EdgeInstancesOverviewWidgetComponent, EdgesOverviewWidgetComponent,
DateRangeNavigatorWidgetComponent, DateRangeNavigatorWidgetComponent,
DateRangeNavigatorPanelComponent, DateRangeNavigatorPanelComponent,
MultipleInputWidgetComponent, MultipleInputWidgetComponent,
@ -65,7 +65,7 @@ import { EdgeInstancesOverviewWidgetComponent } from "@home/components/widget/li
AlarmsTableWidgetComponent, AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent, TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent, EntitiesHierarchyWidgetComponent,
EdgeInstancesOverviewWidgetComponent, EdgesOverviewWidgetComponent,
RpcWidgetsModule, RpcWidgetsModule,
DateRangeNavigatorWidgetComponent, DateRangeNavigatorWidgetComponent,
MultipleInputWidgetComponent, MultipleInputWidgetComponent,

View File

@ -117,7 +117,7 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
break; break;
} }
if (!getCurrentAuthState(this.store).edgesSupportEnabled) { if (!getCurrentAuthState(this.store).edgesSupportEnabled) {
this.latestWidgetTypes = this.latestWidgetTypes.filter(type => type.typeAlias !== 'edges_instances_overview') this.staticWidgetTypes = this.staticWidgetTypes.filter(type => type.typeAlias !== 'edges_instances_overview')
} }
top += widget.sizeY; top += widget.sizeY;
}); });