Commit 02b5462720b7b2c58a2af9126ad0f6fa6b20deb5
Merge branch 'develop/3.3-edge' of github.com:volodymyr-babak/thingsboard into develop/3.3-edge
Showing
11 changed files
with
531 additions
and
5 deletions
1 | +{ | |
2 | + "widgetsBundle": { | |
3 | + "alias": "edge_widgets", | |
4 | + "title": "Edge widgets", | |
5 | + "image": null | |
6 | + }, | |
7 | + "widgetTypes": [ | |
8 | + { | |
9 | + "alias": "edges_overview", | |
10 | + "name": "Edges Quick Overview", | |
11 | + "descriptor": { | |
12 | + "type": "latest", | |
13 | + "sizeX": 7.5, | |
14 | + "sizeY": 5, | |
15 | + "resources": [], | |
16 | + "templateHtml": "<tb-edges-overview-widget \n [ctx]=\"ctx\">\n</tb-edges-overview-widget>", | |
17 | + "templateCss": "", | |
18 | + "controllerScript": "self.onInit = function() {\n};\n\nself.onDestroy = function() {\n};\n", | |
19 | + "settingsSchema": "{}", | |
20 | + "dataKeySettingsSchema": "{}\n", | |
21 | + "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\":{}}" | |
22 | + } | |
23 | + } | |
24 | + ] | |
25 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -439,6 +439,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
439 | 439 | this.deleteSystemWidgetBundle("input_widgets"); |
440 | 440 | this.deleteSystemWidgetBundle("date"); |
441 | 441 | this.deleteSystemWidgetBundle("entity_admin_widgets"); |
442 | + this.deleteSystemWidgetBundle("edge_widgets"); | |
442 | 443 | installScripts.loadSystemWidgets(); |
443 | 444 | } |
444 | 445 | ... | ... |
... | ... | @@ -1157,4 +1157,26 @@ export class EntityService { |
1157 | 1157 | datasource.dataKeys.push(dataKey); |
1158 | 1158 | }); |
1159 | 1159 | } |
1160 | + | |
1161 | + public getAssignedToEdgeEntitiesByType(edgeId: string, entityType: EntityType, pageLink: PageLink): Observable<PageData<any>> { | |
1162 | + let entitiesObservable: Observable<PageData<any>>; | |
1163 | + switch (entityType) { | |
1164 | + case (EntityType.ASSET): | |
1165 | + entitiesObservable = this.assetService.getEdgeAssets(edgeId, pageLink); | |
1166 | + break; | |
1167 | + case (EntityType.DEVICE): | |
1168 | + entitiesObservable = this.deviceService.getEdgeDevices(edgeId, pageLink); | |
1169 | + break; | |
1170 | + case (EntityType.ENTITY_VIEW): | |
1171 | + entitiesObservable = this.entityViewService.getEdgeEntityViews(edgeId, pageLink); | |
1172 | + break; | |
1173 | + case (EntityType.DASHBOARD): | |
1174 | + entitiesObservable = this.dashboardService.getEdgeDashboards(edgeId, pageLink); | |
1175 | + break; | |
1176 | + case (EntityType.RULE_CHAIN): | |
1177 | + entitiesObservable = this.ruleChainService.getEdgeRuleChains(edgeId, pageLink); | |
1178 | + break; | |
1179 | + } | |
1180 | + return entitiesObservable; | |
1181 | + } | |
1160 | 1182 | } | ... | ... |
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-edges-overview tb-absolute-fill" tb-toast toastTarget="{{ toastTargetId }}"> | |
19 | + <div fxFlex fxLayout="column" class="tb-absolute-fill"> | |
20 | + <span class="mat-subheader" *ngIf="customerTitle" fxLayout="row">{{ customerTitle }}</span> | |
21 | + <span class="mat-subheader" *ngIf="!edgeIsDatasource" fxLayout="row" translate>edge.widget-datasource-error</span> | |
22 | + <div fxFlex class="tb-entities-nav-tree-panel"> | |
23 | + <tb-nav-tree | |
24 | + [loadNodes]="loadNodes" | |
25 | + ></tb-nav-tree> | |
26 | + </div> | |
27 | + </div> | |
28 | +</div> | ... | ... |
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 | +:host-context(.tb-has-timewindow) { | |
17 | + .tb-edges-overview { | |
18 | + mat-toolbar { | |
19 | + height: 60px; | |
20 | + max-height: 60px; | |
21 | + .mat-toolbar-tools { | |
22 | + height: 60px; | |
23 | + max-height: 60px; | |
24 | + } | |
25 | + } | |
26 | + } | |
27 | +} | |
28 | + | |
29 | +:host { | |
30 | + .tb-edges-overview { | |
31 | + mat-toolbar.mat-table-toolbar:not([color="primary"]) { | |
32 | + background: transparent; | |
33 | + } | |
34 | + mat-toolbar { | |
35 | + min-height: 39px; | |
36 | + max-height: 39px; | |
37 | + .mat-toolbar-tools { | |
38 | + min-height: 39px; | |
39 | + max-height: 39px; | |
40 | + } | |
41 | + } | |
42 | + | |
43 | + .mat-subheader { | |
44 | + padding: 15px; | |
45 | + color: rgba(0,0,0,0.54); | |
46 | + font-size: 14px; | |
47 | + font-weight: 400; | |
48 | + } | |
49 | + | |
50 | + .tb-entities-nav-tree-panel { | |
51 | + overflow-x: auto; | |
52 | + overflow-y: auto; | |
53 | + } | |
54 | + } | |
55 | +} | |
56 | + | |
57 | +:host ::ng-deep { | |
58 | + .tb-nav-tree-container { | |
59 | + &.jstree-proton { | |
60 | + .jstree-anchor { | |
61 | + div.node-icon { | |
62 | + display: inline-block; | |
63 | + width: 22px; | |
64 | + height: 22px; | |
65 | + margin-right: 2px; | |
66 | + margin-bottom: 2px; | |
67 | + background-color: transparent; | |
68 | + background-repeat: no-repeat; | |
69 | + background-attachment: scroll; | |
70 | + background-position: center center; | |
71 | + background-size: 18px 18px; | |
72 | + } | |
73 | + | |
74 | + mat-icon.node-icon { | |
75 | + width: 22px; | |
76 | + min-width: 22px; | |
77 | + height: 22px; | |
78 | + min-height: 22px; | |
79 | + margin-right: 2px; | |
80 | + margin-bottom: 2px; | |
81 | + color: inherit; | |
82 | + vertical-align: middle; | |
83 | + | |
84 | + &.material-icons { | |
85 | + font-size: 18px; | |
86 | + line-height: 22px; | |
87 | + text-align: center; | |
88 | + } | |
89 | + } | |
90 | + | |
91 | + &.jstree-hovered:not(.jstree-clicked), | |
92 | + &.jstree-disabled { | |
93 | + div.node-icon { | |
94 | + opacity: .5; | |
95 | + } | |
96 | + } | |
97 | + } | |
98 | + } | |
99 | + } | |
100 | + | |
101 | + @media (max-width: 768px) { | |
102 | + .tb-nav-tree-container { | |
103 | + &.jstree-proton-responsive { | |
104 | + .jstree-anchor { | |
105 | + div.node-icon { | |
106 | + width: 40px; | |
107 | + height: 40px; | |
108 | + margin: 0; | |
109 | + background-size: 24px 24px; | |
110 | + } | |
111 | + | |
112 | + mat-icon.node-icon { | |
113 | + width: 40px; | |
114 | + min-width: 40px; | |
115 | + height: 40px; | |
116 | + min-height: 40px; | |
117 | + margin: 0; | |
118 | + | |
119 | + &.material-icons { | |
120 | + font-size: 24px; | |
121 | + line-height: 40px; | |
122 | + } | |
123 | + } | |
124 | + } | |
125 | + } | |
126 | + } | |
127 | + } | |
128 | +} | ... | ... |
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 | + | |
17 | +import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; | |
18 | +import { PageComponent } from '@shared/components/page.component'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@core/core.state'; | |
21 | +import { WidgetContext } from '@home/models/widget-component.models'; | |
22 | +import { Datasource, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; | |
23 | +import { IWidgetSubscription } from '@core/api/widget-api.models'; | |
24 | +import { UtilsService } from '@core/services/utils.service'; | |
25 | +import { LoadNodesCallback } from '@shared/components/nav-tree.component'; | |
26 | +import { EntityType } from '@shared/models/entity-type.models'; | |
27 | +import { | |
28 | + EdgeGroupNodeData, | |
29 | + edgeGroupsNodeText, | |
30 | + edgeGroupsTypes, | |
31 | + entityNodeText, | |
32 | + EdgeOverviewNode, | |
33 | + EntityNodeData, | |
34 | + EntityNodeDatasource | |
35 | +} from '@home/components/widget/lib/edges-overview-widget.models'; | |
36 | +import { EdgeService } from "@core/http/edge.service"; | |
37 | +import { EntityService } from "@core/http/entity.service"; | |
38 | +import { TranslateService } from "@ngx-translate/core"; | |
39 | +import { PageLink } from "@shared/models/page/page-link"; | |
40 | +import { BaseData, HasId } from "@shared/models/base-data"; | |
41 | +import { EntityId } from "@shared/models/id/entity-id"; | |
42 | +import { getCurrentAuthUser } from "@core/auth/auth.selectors"; | |
43 | +import { Authority } from "@shared/models/authority.enum"; | |
44 | + | |
45 | +@Component({ | |
46 | + selector: 'tb-edges-overview-widget', | |
47 | + templateUrl: './edges-overview-widget.component.html', | |
48 | + styleUrls: ['./edges-overview-widget.component.scss'] | |
49 | +}) | |
50 | +export class EdgesOverviewWidgetComponent extends PageComponent implements OnInit { | |
51 | + | |
52 | + @Input() | |
53 | + ctx: WidgetContext; | |
54 | + | |
55 | + public toastTargetId = 'edges-overview-' + this.utils.guid(); | |
56 | + public customerTitle: string = null; | |
57 | + public edgeIsDatasource: boolean = true; | |
58 | + | |
59 | + private widgetConfig: WidgetConfig; | |
60 | + private subscription: IWidgetSubscription; | |
61 | + private datasources: Array<EntityNodeDatasource>; | |
62 | + | |
63 | + private nodeIdCounter = 0; | |
64 | + | |
65 | + private entityNodesMap: {[parentNodeId: string]: {[edgeId: string]: string}} = {}; | |
66 | + private entityGroupsNodesMap: {[edgeNodeId: string]: {[groupType: string]: string}} = {}; | |
67 | + | |
68 | + constructor(protected store: Store<AppState>, | |
69 | + private edgeService: EdgeService, | |
70 | + private entityService: EntityService, | |
71 | + private translateService: TranslateService, | |
72 | + private utils: UtilsService, | |
73 | + private cd: ChangeDetectorRef) { | |
74 | + super(store); | |
75 | + } | |
76 | + | |
77 | + ngOnInit(): void { | |
78 | + this.widgetConfig = this.ctx.widgetConfig; | |
79 | + this.subscription = this.ctx.defaultSubscription; | |
80 | + this.datasources = this.subscription.datasources as Array<EntityNodeDatasource>; | |
81 | + this.ctx.updateWidgetParams(); | |
82 | + } | |
83 | + | |
84 | + public loadNodes: LoadNodesCallback = (node, cb) => { | |
85 | + const datasource: Datasource = this.datasources[0]; | |
86 | + if (node.id === '#' && datasource) { | |
87 | + if (datasource.type === DatasourceType.entity && datasource.entity.id.entityType === EntityType.EDGE) { | |
88 | + var selectedEdge: BaseData<EntityId> = datasource.entity; | |
89 | + this.getCustomerTitle(selectedEdge.id.id); | |
90 | + this.ctx.widgetTitle = selectedEdge.name; | |
91 | + cb(this.loadNodesForEdge(selectedEdge.id.id, selectedEdge)); | |
92 | + } else if (datasource.type === DatasourceType.function) { | |
93 | + cb(this.loadNodesForEdge(datasource.entityId, datasource.entity)); | |
94 | + } else { | |
95 | + this.edgeIsDatasource = false; | |
96 | + cb([]); | |
97 | + } | |
98 | + } | |
99 | + else if (node.data && node.data.entity.id.entityType === EntityType.EDGE) { | |
100 | + const edgeId = node.data.entity.id.id; | |
101 | + const entityType = node.data.entityType; | |
102 | + const pageLink = new PageLink(datasource.pageLink.pageSize); | |
103 | + this.entityService.getAssignedToEdgeEntitiesByType(edgeId, entityType, pageLink).subscribe( | |
104 | + (entities) => { | |
105 | + if (entities.data.length > 0) { | |
106 | + cb(this.entitiesToNodes(node.id, entities.data)); | |
107 | + } else { | |
108 | + cb([]); | |
109 | + } | |
110 | + } | |
111 | + ) | |
112 | + } else { | |
113 | + cb([]); | |
114 | + } | |
115 | + } | |
116 | + | |
117 | + private loadNodesForEdge(parentNodeId: string, entity: BaseData<HasId>): EdgeOverviewNode[] { | |
118 | + const nodes: EdgeOverviewNode[] = []; | |
119 | + const nodesMap = {}; | |
120 | + this.entityGroupsNodesMap[parentNodeId] = nodesMap; | |
121 | + const authUser = getCurrentAuthUser(this.store); | |
122 | + var allowedGroupTypes: EntityType[] = edgeGroupsTypes; | |
123 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
124 | + allowedGroupTypes = edgeGroupsTypes.filter(type => type !== EntityType.RULE_CHAIN); | |
125 | + } | |
126 | + allowedGroupTypes.forEach((entityType) => { | |
127 | + const node: EdgeOverviewNode = { | |
128 | + id: (++this.nodeIdCounter)+'', | |
129 | + icon: false, | |
130 | + text: edgeGroupsNodeText(this.translateService, entityType), | |
131 | + children: true, | |
132 | + data: { | |
133 | + entityType, | |
134 | + entity, | |
135 | + internalId: entity.id.id + '_' + entityType | |
136 | + } as EdgeGroupNodeData | |
137 | + }; | |
138 | + nodes.push(node); | |
139 | + nodesMap[entityType] = node.id; | |
140 | + }); | |
141 | + return nodes; | |
142 | + } | |
143 | + | |
144 | + private createEntityNode(parentNodeId: string, entity: BaseData<HasId>): EdgeOverviewNode { | |
145 | + let nodesMap = this.entityNodesMap[parentNodeId]; | |
146 | + if (!nodesMap) { | |
147 | + nodesMap = {}; | |
148 | + this.entityNodesMap[parentNodeId] = nodesMap; | |
149 | + } | |
150 | + const node: EdgeOverviewNode = { | |
151 | + id: (++this.nodeIdCounter)+'', | |
152 | + icon: false, | |
153 | + text: entityNodeText(entity), | |
154 | + children: false, | |
155 | + state: { | |
156 | + disabled: false | |
157 | + }, | |
158 | + data: { | |
159 | + entity: entity, | |
160 | + internalId: entity.id.id | |
161 | + } as EntityNodeData | |
162 | + }; | |
163 | + nodesMap[entity.id.id] = node.id; | |
164 | + return node; | |
165 | + } | |
166 | + | |
167 | + private entitiesToNodes(parentNodeId: string, entities: BaseData<HasId>[]): EdgeOverviewNode[] { | |
168 | + const nodes: EdgeOverviewNode[] = []; | |
169 | + this.entityNodesMap[parentNodeId] = {}; | |
170 | + if (entities) { | |
171 | + entities.forEach((entity) => { | |
172 | + const node = this.createEntityNode(parentNodeId, entity); | |
173 | + nodes.push(node); | |
174 | + }); | |
175 | + } | |
176 | + return nodes; | |
177 | + } | |
178 | + | |
179 | + private getCustomerTitle(edgeId: string) { | |
180 | + this.edgeService.getEdgeInfo(edgeId).subscribe( | |
181 | + (edge) => { | |
182 | + if (edge.customerTitle) { | |
183 | + this.customerTitle = this.translateService.instant('edge.assigned-to-customer', {customerTitle: edge.customerTitle}); | |
184 | + } else { | |
185 | + this.customerTitle = null; | |
186 | + } | |
187 | + this.cd.detectChanges(); | |
188 | + }); | |
189 | + } | |
190 | +} | ... | ... |
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 | + | |
17 | +import { NavTreeNode } from '@shared/components/nav-tree.component'; | |
18 | +import { Datasource } from '@shared/models/widget.models'; | |
19 | +import { EntityType } from '@shared/models/entity-type.models'; | |
20 | +import { TranslateService } from "@ngx-translate/core"; | |
21 | +import { BaseData, HasId } from "@shared/models/base-data"; | |
22 | + | |
23 | +export interface EntityNodeDatasource extends Datasource { | |
24 | + nodeId: string; | |
25 | +} | |
26 | + | |
27 | +export function edgeGroupsNodeText(translate: TranslateService, entityType: EntityType): string { | |
28 | + const nodeIcon = materialIconByEntityType(entityType); | |
29 | + const nodeText = textForEdgeGroupsType(translate, entityType); | |
30 | + return nodeIcon + nodeText; | |
31 | +} | |
32 | + | |
33 | +export function entityNodeText(entity: any): string { | |
34 | + const nodeIcon = materialIconByEntityType(entity.id.entityType); | |
35 | + const nodeText = entity.name; | |
36 | + return nodeIcon + nodeText; | |
37 | +} | |
38 | + | |
39 | +export function materialIconByEntityType(entityType: EntityType): string { | |
40 | + let materialIcon = 'insert_drive_file'; | |
41 | + switch (entityType) { | |
42 | + case EntityType.DEVICE: | |
43 | + materialIcon = 'devices_other'; | |
44 | + break; | |
45 | + case EntityType.ASSET: | |
46 | + materialIcon = 'domain'; | |
47 | + break; | |
48 | + case EntityType.DASHBOARD: | |
49 | + materialIcon = 'dashboards'; | |
50 | + break; | |
51 | + case EntityType.ENTITY_VIEW: | |
52 | + materialIcon = 'view_quilt'; | |
53 | + break; | |
54 | + case EntityType.RULE_CHAIN: | |
55 | + materialIcon = 'settings_ethernet'; | |
56 | + break; | |
57 | + } | |
58 | + return '<mat-icon class="node-icon material-icons" role="img" aria-hidden="false">' + materialIcon + '</mat-icon>'; | |
59 | +} | |
60 | + | |
61 | +export function textForEdgeGroupsType(translate: TranslateService, entityType: EntityType): string { | |
62 | + let textForEdgeGroupsType: string = ''; | |
63 | + switch (entityType) { | |
64 | + case EntityType.DEVICE: | |
65 | + textForEdgeGroupsType = 'device.devices'; | |
66 | + break; | |
67 | + case EntityType.ASSET: | |
68 | + textForEdgeGroupsType = 'asset.assets'; | |
69 | + break; | |
70 | + case EntityType.DASHBOARD: | |
71 | + textForEdgeGroupsType = 'dashboard.dashboards'; | |
72 | + break; | |
73 | + case EntityType.ENTITY_VIEW: | |
74 | + textForEdgeGroupsType = 'entity-view.entity-views'; | |
75 | + break; | |
76 | + case EntityType.RULE_CHAIN: | |
77 | + textForEdgeGroupsType = 'rulechain.rulechains'; | |
78 | + break; | |
79 | + } | |
80 | + return translate.instant(textForEdgeGroupsType); | |
81 | +} | |
82 | + | |
83 | +export const edgeGroupsTypes: EntityType[] = [ | |
84 | + EntityType.ASSET, | |
85 | + EntityType.DEVICE, | |
86 | + EntityType.ENTITY_VIEW, | |
87 | + EntityType.DASHBOARD, | |
88 | + EntityType.RULE_CHAIN | |
89 | +] | |
90 | + | |
91 | +export interface EdgeOverviewNode extends NavTreeNode { | |
92 | + data?: EdgeOverviewNodeData; | |
93 | +} | |
94 | + | |
95 | +export type EdgeOverviewNodeData = EdgeGroupNodeData | EntityNodeData; | |
96 | + | |
97 | +export interface EdgeGroupNodeData extends BaseEdgeOverviewNodeData { | |
98 | + entityType: EntityType; | |
99 | + entity: BaseData<HasId>; | |
100 | +} | |
101 | + | |
102 | +export interface EntityNodeData extends BaseEdgeOverviewNodeData { | |
103 | + entity: BaseData<HasId>; | |
104 | +} | |
105 | + | |
106 | +export interface BaseEdgeOverviewNodeData { | |
107 | + internalId: string; | |
108 | +} | ... | ... |
... | ... | @@ -35,6 +35,7 @@ import { TripAnimationComponent } from './trip-animation/trip-animation.componen |
35 | 35 | import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; |
36 | 36 | import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; |
37 | 37 | import { ImportExportService } from '@home/components/import-export/import-export.service'; |
38 | +import { EdgesOverviewWidgetComponent } from "@home/components/widget/lib/edges-overview-widget.component"; | |
38 | 39 | |
39 | 40 | @NgModule({ |
40 | 41 | declarations: |
... | ... | @@ -45,6 +46,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor |
45 | 46 | AlarmsTableWidgetComponent, |
46 | 47 | TimeseriesTableWidgetComponent, |
47 | 48 | EntitiesHierarchyWidgetComponent, |
49 | + EdgesOverviewWidgetComponent, | |
48 | 50 | DateRangeNavigatorWidgetComponent, |
49 | 51 | DateRangeNavigatorPanelComponent, |
50 | 52 | MultipleInputWidgetComponent, |
... | ... | @@ -63,6 +65,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor |
63 | 65 | AlarmsTableWidgetComponent, |
64 | 66 | TimeseriesTableWidgetComponent, |
65 | 67 | EntitiesHierarchyWidgetComponent, |
68 | + EdgesOverviewWidgetComponent, | |
66 | 69 | RpcWidgetsModule, |
67 | 70 | DateRangeNavigatorWidgetComponent, |
68 | 71 | MultipleInputWidgetComponent, | ... | ... |
... | ... | @@ -33,11 +33,12 @@ import { WidgetsBundleComponent } from '@modules/home/pages/widget/widgets-bundl |
33 | 33 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
34 | 34 | import { Store } from '@ngrx/store'; |
35 | 35 | import { AppState } from '@core/core.state'; |
36 | -import { getCurrentAuthUser } from '@app/core/auth/auth.selectors'; | |
36 | +import { getCurrentAuthState, getCurrentAuthUser } from '@app/core/auth/auth.selectors'; | |
37 | 37 | import { Authority } from '@shared/models/authority.enum'; |
38 | 38 | import { DialogService } from '@core/services/dialog.service'; |
39 | 39 | import { ImportExportService } from '@home/components/import-export/import-export.service'; |
40 | 40 | import { Direction } from "@shared/models/page/sort-order"; |
41 | +import { map } from "rxjs/operators"; | |
41 | 42 | |
42 | 43 | @Injectable() |
43 | 44 | export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableConfig<WidgetsBundle>> { |
... | ... | @@ -106,7 +107,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon |
106 | 107 | this.config.deleteEntitiesTitle = count => this.translate.instant('widgets-bundle.delete-widgets-bundles-title', {count}); |
107 | 108 | this.config.deleteEntitiesContent = () => this.translate.instant('widgets-bundle.delete-widgets-bundles-text'); |
108 | 109 | |
109 | - this.config.entitiesFetchFunction = pageLink => this.widgetsService.getWidgetBundles(pageLink); | |
110 | + | |
110 | 111 | this.config.loadEntity = id => this.widgetsService.getWidgetsBundle(id.id); |
111 | 112 | this.config.saveEntity = widgetsBundle => this.widgetsService.saveWidgetsBundle(widgetsBundle); |
112 | 113 | this.config.deleteEntity = id => this.widgetsService.deleteWidgetsBundle(id.id); |
... | ... | @@ -119,6 +120,17 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon |
119 | 120 | this.config.deleteEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); |
120 | 121 | this.config.entitySelectionEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); |
121 | 122 | this.config.detailsReadonly = (widgetsBundle) => !this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); |
123 | + const authState = getCurrentAuthState(this.store); | |
124 | + if (!authState.edgesSupportEnabled) { | |
125 | + this.config.entitiesFetchFunction = pageLink => this.widgetsService.getWidgetBundles(pageLink).pipe( | |
126 | + map((widgetBundles) => { | |
127 | + widgetBundles.data = widgetBundles.data.filter(widgetBundle => widgetBundle.alias !== 'edge_widgets'); | |
128 | + return widgetBundles; | |
129 | + }) | |
130 | + ); | |
131 | + } else { | |
132 | + this.config.entitiesFetchFunction = pageLink => this.widgetsService.getWidgetBundles(pageLink); | |
133 | + } | |
122 | 134 | return this.config; |
123 | 135 | } |
124 | 136 | ... | ... |
... | ... | @@ -17,7 +17,7 @@ |
17 | 17 | import { Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; |
18 | 18 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | 19 | import { Observable } from 'rxjs'; |
20 | -import { share, tap } from 'rxjs/operators'; | |
20 | +import { map, share, tap } from 'rxjs/operators'; | |
21 | 21 | import { Store } from '@ngrx/store'; |
22 | 22 | import { AppState } from '@app/core/core.state'; |
23 | 23 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
... | ... | @@ -25,6 +25,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
25 | 25 | import { WidgetService } from '@core/http/widget.service'; |
26 | 26 | import { isDefined } from '@core/utils'; |
27 | 27 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
28 | +import { getCurrentAuthState } from "@core/auth/auth.selectors"; | |
28 | 29 | |
29 | 30 | @Component({ |
30 | 31 | selector: 'tb-widgets-bundle-select', |
... | ... | @@ -94,6 +95,13 @@ export class WidgetsBundleSelectComponent implements ControlValueAccessor, OnIni |
94 | 95 | } |
95 | 96 | } |
96 | 97 | }), |
98 | + map((widgetsBundles) => { | |
99 | + const authState = getCurrentAuthState(this.store); | |
100 | + if (!authState.edgesSupportEnabled) { | |
101 | + widgetsBundles = widgetsBundles.filter(widgetsBundle => widgetsBundle.alias !== 'edge_widgets'); | |
102 | + } | |
103 | + return widgetsBundles; | |
104 | + }), | |
97 | 105 | share() |
98 | 106 | ); |
99 | 107 | } | ... | ... |
... | ... | @@ -1233,7 +1233,7 @@ |
1233 | 1233 | "assign-to-customer-text": "Please select the customer to assign the edge(s)", |
1234 | 1234 | "assign-edge-to-customer": "Assign Edge(s) To Customer", |
1235 | 1235 | "assign-edge-to-customer-text": "Please select the edges to assign to the customer", |
1236 | - "assigned-to-customer": "Assigned to customer", | |
1236 | + "assigned-to-customer": "Assigned to: {{customerTitle}}", | |
1237 | 1237 | "unassign-from-customer": "Unassign from customer", |
1238 | 1238 | "assign-edges-text": "Assign { count, plural, 1 {1 edge} other {# edges} } to customer", |
1239 | 1239 | "unassign-edge-title": "Are you sure you want to unassign the edge '{{edgeName}}'?", |
... | ... | @@ -1287,7 +1287,8 @@ |
1287 | 1287 | "no-downlinks-prompt": "No downlinks found", |
1288 | 1288 | "sync-process-started-successfully": "Sync process started successfully!", |
1289 | 1289 | "missing-related-rule-chains-title": "Edge has missing related rule chain(s)", |
1290 | - "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. <br><br> List of missing rule chain(s): <br> {{missingRuleChains}}" | |
1290 | + "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. <br><br> List of missing rule chain(s): <br> {{missingRuleChains}}", | |
1291 | + "widget-datasource-error": "This widget supports only EDGE entity datasource" | |
1291 | 1292 | }, |
1292 | 1293 | "edge-event": { |
1293 | 1294 | "type-dashboard": "Dashboard", | ... | ... |