Showing
10 changed files
with
972 additions
and
141 deletions
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; | |
18 | +import { HttpClient } from '@angular/common/http'; | |
19 | +import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models'; | |
20 | +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; | |
21 | +import { Observable, of } from 'rxjs'; | |
22 | +import { RuleChainMetaData } from '@shared/models/rule-chain.models'; | |
23 | +import { tap, map } from 'rxjs/operators'; | |
24 | +import { RuleNodeType } from '@shared/models/rule-node.models'; | |
25 | + | |
26 | +@Injectable({ | |
27 | + providedIn: 'root' | |
28 | +}) | |
29 | +export class ComponentDescriptorService { | |
30 | + | |
31 | + private componentsByType: Map<ComponentType | RuleNodeType, Array<ComponentDescriptor>> = | |
32 | + new Map<ComponentType, Array<ComponentDescriptor>>(); | |
33 | + private componentsByClazz: Map<string, ComponentDescriptor> = new Map<string, ComponentDescriptor>(); | |
34 | + | |
35 | + constructor( | |
36 | + private http: HttpClient | |
37 | + ) { | |
38 | + } | |
39 | + | |
40 | + public getComponentDescriptorsByType(componentType: ComponentType, config?: RequestConfig): Observable<Array<ComponentDescriptor>> { | |
41 | + const existing = this.componentsByType.get(componentType); | |
42 | + if (existing) { | |
43 | + return of(existing); | |
44 | + } else { | |
45 | + return this.http.get<Array<ComponentDescriptor>>(`/api/components/${componentType}`, defaultHttpOptionsFromConfig(config)).pipe( | |
46 | + map((componentDescriptors) => { | |
47 | + this.componentsByType.set(componentType, componentDescriptors); | |
48 | + componentDescriptors.forEach((componentDescriptor) => { | |
49 | + this.componentsByClazz.set(componentDescriptor.clazz, componentDescriptor); | |
50 | + }); | |
51 | + return componentDescriptors; | |
52 | + }) | |
53 | + ); | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + public getComponentDescriptorsByTypes(componentTypes: Array<ComponentType>, | |
58 | + config?: RequestConfig): Observable<Array<ComponentDescriptor>> { | |
59 | + let result: ComponentDescriptor[] = []; | |
60 | + for (let i = componentTypes.length - 1; i >= 0; i--) { | |
61 | + const componentType = componentTypes[i]; | |
62 | + const componentDescriptors = this.componentsByType.get(componentType); | |
63 | + if (componentDescriptors) { | |
64 | + result = result.concat(componentDescriptors); | |
65 | + componentTypes.splice(i, 1); | |
66 | + } | |
67 | + } | |
68 | + if (!componentTypes.length) { | |
69 | + return of(result); | |
70 | + } else { | |
71 | + return this.http.get<Array<ComponentDescriptor>>(`/api/components?componentTypes=${componentTypes.join(',')}`, | |
72 | + defaultHttpOptionsFromConfig(config)).pipe( | |
73 | + map((componentDescriptors) => { | |
74 | + componentDescriptors.forEach((componentDescriptor) => { | |
75 | + let componentsList = this.componentsByType.get(componentDescriptor.type); | |
76 | + if (!componentsList) { | |
77 | + componentsList = new Array<ComponentDescriptor>(); | |
78 | + this.componentsByType.set(componentDescriptor.type, componentsList); | |
79 | + } | |
80 | + componentsList.push(componentDescriptor); | |
81 | + this.componentsByClazz.set(componentDescriptor.clazz, componentDescriptor); | |
82 | + }); | |
83 | + result = result.concat(componentDescriptors); | |
84 | + return result; | |
85 | + }) | |
86 | + ); | |
87 | + } | |
88 | + } | |
89 | + | |
90 | + public getComponentDescriptorByClazz(componentDescriptorClazz: string, config?: RequestConfig): Observable<ComponentDescriptor> { | |
91 | + const existing = this.componentsByClazz.get(componentDescriptorClazz); | |
92 | + if (existing) { | |
93 | + return of(existing); | |
94 | + } else { | |
95 | + return this.http.get<ComponentDescriptor>(`/api/component/${componentDescriptorClazz}`, defaultHttpOptionsFromConfig(config)).pipe( | |
96 | + map((componentDescriptor) => { | |
97 | + this.componentsByClazz.set(componentDescriptorClazz, componentDescriptor); | |
98 | + return componentDescriptor; | |
99 | + }) | |
100 | + ); | |
101 | + } | |
102 | + } | |
103 | +} | ... | ... |
... | ... | @@ -14,21 +14,39 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import {Injectable} from '@angular/core'; | |
18 | -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; | |
19 | -import {Observable} from 'rxjs/index'; | |
20 | -import {HttpClient} from '@angular/common/http'; | |
21 | -import {PageLink} from '@shared/models/page/page-link'; | |
22 | -import {PageData} from '@shared/models/page/page-data'; | |
23 | -import {RuleChain} from '@shared/models/rule-chain.models'; | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; | |
19 | +import { forkJoin, Observable, of } from 'rxjs/index'; | |
20 | +import { HttpClient } from '@angular/common/http'; | |
21 | +import { PageLink } from '@shared/models/page/page-link'; | |
22 | +import { PageData } from '@shared/models/page/page-data'; | |
23 | +import { | |
24 | + ResolvedRuleChainMetaData, | |
25 | + RuleChain, RuleChainConnectionInfo, | |
26 | + RuleChainMetaData, | |
27 | + ruleChainNodeComponent, | |
28 | + ruleNodeTypeComponentTypes, unknownNodeComponent | |
29 | +} from '@shared/models/rule-chain.models'; | |
30 | +import { ComponentDescriptorService } from './component-descriptor.service'; | |
31 | +import { RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models'; | |
32 | +import { ResourcesService } from '../services/resources.service'; | |
33 | +import { catchError, map, mergeMap } from 'rxjs/operators'; | |
34 | +import { TranslateService } from '@ngx-translate/core'; | |
35 | +import { EntityType } from '@shared/models/entity-type.models'; | |
36 | +import { deepClone } from '@core/utils'; | |
24 | 37 | |
25 | 38 | @Injectable({ |
26 | 39 | providedIn: 'root' |
27 | 40 | }) |
28 | 41 | export class RuleChainService { |
29 | 42 | |
43 | + private ruleNodeComponents: Array<RuleNodeComponentDescriptor>; | |
44 | + | |
30 | 45 | constructor( |
31 | - private http: HttpClient | |
46 | + private http: HttpClient, | |
47 | + private componentDescriptorService: ComponentDescriptorService, | |
48 | + private resourcesService: ResourcesService, | |
49 | + private translate: TranslateService | |
32 | 50 | ) { } |
33 | 51 | |
34 | 52 | public getRuleChains(pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> { |
... | ... | @@ -52,4 +70,159 @@ export class RuleChainService { |
52 | 70 | return this.http.post<RuleChain>(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptionsFromConfig(config)); |
53 | 71 | } |
54 | 72 | |
73 | + public getRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable<RuleChainMetaData> { | |
74 | + return this.http.get<RuleChainMetaData>(`/api/ruleChain/${ruleChainId}/metadata`, defaultHttpOptionsFromConfig(config)); | |
75 | + } | |
76 | + | |
77 | + public getResolvedRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable<ResolvedRuleChainMetaData> { | |
78 | + return this.getRuleChainMetadata(ruleChainId, config).pipe( | |
79 | + mergeMap((ruleChainMetaData) => { | |
80 | + return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe( | |
81 | + map((targetRuleChainsMap) => { | |
82 | + const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap}; | |
83 | + return resolvedRuleChainMetadata; | |
84 | + }) | |
85 | + ); | |
86 | + }) | |
87 | + ); | |
88 | + } | |
89 | + | |
90 | + public saveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable<RuleChainMetaData> { | |
91 | + return this.http.post<RuleChainMetaData>('/api/ruleChain/metadata', ruleChainMetaData, defaultHttpOptionsFromConfig(config)); | |
92 | + } | |
93 | + | |
94 | + public saveAndGetResolvedRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, | |
95 | + config?: RequestConfig): Observable<ResolvedRuleChainMetaData> { | |
96 | + return this.saveRuleChainMetadata(ruleChainMetaData, config).pipe( | |
97 | + mergeMap((savedRuleChainMetaData) => { | |
98 | + return this.resolveTargetRuleChains(savedRuleChainMetaData.ruleChainConnections).pipe( | |
99 | + map((targetRuleChainsMap) => { | |
100 | + const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...savedRuleChainMetaData, targetRuleChainsMap}; | |
101 | + return resolvedRuleChainMetadata; | |
102 | + }) | |
103 | + ); | |
104 | + }) | |
105 | + ); | |
106 | + } | |
107 | + | |
108 | + public getRuleNodeComponents(config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> { | |
109 | + if (this.ruleNodeComponents) { | |
110 | + return of(this.ruleNodeComponents); | |
111 | + } else { | |
112 | + return this.loadRuleNodeComponents(config).pipe( | |
113 | + mergeMap((components) => { | |
114 | + return this.resolveRuleNodeComponentsUiResources(components).pipe( | |
115 | + map((ruleNodeComponents) => { | |
116 | + this.ruleNodeComponents = ruleNodeComponents; | |
117 | + this.ruleNodeComponents.push(ruleChainNodeComponent); | |
118 | + this.ruleNodeComponents.sort( | |
119 | + (comp1, comp2) => { | |
120 | + let result = comp1.type.toString().localeCompare(comp2.type.toString()); | |
121 | + if (result === 0) { | |
122 | + result = comp1.name.localeCompare(comp2.name); | |
123 | + } | |
124 | + return result; | |
125 | + } | |
126 | + ); | |
127 | + return this.ruleNodeComponents; | |
128 | + }) | |
129 | + ); | |
130 | + }) | |
131 | + ); | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + public getRuleNodeComponentByClazz(clazz: string): RuleNodeComponentDescriptor { | |
136 | + const found = this.ruleNodeComponents.filter((component) => component.clazz === clazz); | |
137 | + if (found && found.length) { | |
138 | + return found[0]; | |
139 | + } else { | |
140 | + const unknownComponent = deepClone(unknownNodeComponent); | |
141 | + unknownComponent.clazz = clazz; | |
142 | + unknownComponent.configurationDescriptor.nodeDefinition.details = 'Unknown Rule Node class: ' + clazz; | |
143 | + return unknownComponent; | |
144 | + } | |
145 | + } | |
146 | + | |
147 | + private resolveTargetRuleChains(ruleChainConnections: Array<RuleChainConnectionInfo>): Observable<{[ruleChainId: string]: RuleChain}> { | |
148 | + if (ruleChainConnections && ruleChainConnections.length) { | |
149 | + const tasks: Observable<RuleChain>[] = []; | |
150 | + ruleChainConnections.forEach((connection) => { | |
151 | + tasks.push(this.resolveRuleChain(connection.targetRuleChainId.id)); | |
152 | + }); | |
153 | + return forkJoin(tasks).pipe( | |
154 | + map((ruleChains) => { | |
155 | + const ruleChainsMap: {[ruleChainId: string]: RuleChain} = {}; | |
156 | + ruleChains.forEach((ruleChain) => { | |
157 | + ruleChainsMap[ruleChain.id.id] = ruleChain; | |
158 | + }); | |
159 | + return ruleChainsMap; | |
160 | + }) | |
161 | + ); | |
162 | + } else { | |
163 | + return of({} as {[ruleChainId: string]: RuleChain}); | |
164 | + } | |
165 | + } | |
166 | + | |
167 | + private loadRuleNodeComponents(config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> { | |
168 | + return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, config).pipe( | |
169 | + map((components) => { | |
170 | + const ruleNodeComponents: RuleNodeComponentDescriptor[] = []; | |
171 | + components.forEach((component) => { | |
172 | + ruleNodeComponents.push(component as RuleNodeComponentDescriptor); | |
173 | + }); | |
174 | + return ruleNodeComponents; | |
175 | + }) | |
176 | + ); | |
177 | + } | |
178 | + | |
179 | + private resolveRuleNodeComponentsUiResources(components: Array<RuleNodeComponentDescriptor>): | |
180 | + Observable<Array<RuleNodeComponentDescriptor>> { | |
181 | + const tasks: Observable<RuleNodeComponentDescriptor>[] = []; | |
182 | + components.forEach((component) => { | |
183 | + tasks.push(this.resolveRuleNodeComponentUiResources(component)); | |
184 | + }); | |
185 | + return forkJoin(tasks).pipe( | |
186 | + catchError((err) => { | |
187 | + return of(components); | |
188 | + }) | |
189 | + ); | |
190 | + } | |
191 | + | |
192 | + private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor): Observable<RuleNodeComponentDescriptor> { | |
193 | + const uiResources = component.configurationDescriptor.nodeDefinition.uiResources; | |
194 | + if (uiResources && uiResources.length) { | |
195 | + const tasks: Observable<any>[] = []; | |
196 | + uiResources.forEach((uiResource) => { | |
197 | + tasks.push(this.resourcesService.loadResource(uiResource)); | |
198 | + }); | |
199 | + return forkJoin(tasks).pipe( | |
200 | + map((res) => { | |
201 | + return component; | |
202 | + }), | |
203 | + catchError(() => { | |
204 | + component.configurationDescriptor.nodeDefinition.uiResourceLoadError = this.translate.instant('rulenode.ui-resources-load-error'); | |
205 | + return of(component); | |
206 | + }) | |
207 | + ); | |
208 | + } else { | |
209 | + return of(component); | |
210 | + } | |
211 | + } | |
212 | + | |
213 | + private resolveRuleChain(ruleChainId: string): Observable<RuleChain> { | |
214 | + return this.getRuleChain(ruleChainId, {ignoreErrors: true}).pipe( | |
215 | + map(ruleChain => ruleChain), | |
216 | + catchError((err) => { | |
217 | + const ruleChain = { | |
218 | + id: { | |
219 | + entityType: EntityType.RULE_CHAIN, | |
220 | + id: ruleChainId | |
221 | + } | |
222 | + } as RuleChain; | |
223 | + return of(ruleChain); | |
224 | + }) | |
225 | + ); | |
226 | + } | |
227 | + | |
55 | 228 | } | ... | ... |
... | ... | @@ -17,9 +17,9 @@ |
17 | 17 | --> |
18 | 18 | <fc-canvas #fcCanvas |
19 | 19 | id="fc-target-canvas" |
20 | - [model]="model" | |
20 | + [model]="ruleChainModel" | |
21 | 21 | (modelChanged)="onModelChanged()" |
22 | - [selectedObjects]="selected" | |
22 | + [selectedObjects]="selectedObjects" | |
23 | 23 | [edgeStyle]="flowchartConstants.curvedStyle" |
24 | 24 | [automaticResize]="true" |
25 | 25 | [dragAnimation]="flowchartConstants.dragAnimationRepaint"> | ... | ... |
... | ... | @@ -14,160 +14,341 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import {Component, OnInit} from '@angular/core'; | |
18 | -import {UserService} from '@core/http/user.service'; | |
19 | -import {User} from '@shared/models/user.model'; | |
20 | -import {Authority} from '@shared/models/authority.enum'; | |
21 | -import {PageComponent} from '@shared/components/page.component'; | |
22 | -import {Store} from '@ngrx/store'; | |
23 | -import {AppState} from '@core/core.state'; | |
24 | -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; | |
25 | -import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard'; | |
26 | -import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions'; | |
27 | -import {environment as env} from '@env/environment'; | |
28 | -import {TranslateService} from '@ngx-translate/core'; | |
29 | -import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; | |
30 | -import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; | |
31 | -import {MatDialog} from '@angular/material'; | |
32 | -import {DialogService} from '@core/services/dialog.service'; | |
33 | -import {AuthService} from '@core/auth/auth.service'; | |
34 | -import {ActivatedRoute} from '@angular/router'; | |
35 | -import { Dashboard } from '@shared/models/dashboard.models'; | |
36 | -import { RuleChain } from '@shared/models/rule-chain.models'; | |
37 | -import { FcModel, FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; | |
17 | +import { Component, 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 { FormBuilder } from '@angular/forms'; | |
22 | +import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; | |
23 | +import { TranslateService } from '@ngx-translate/core'; | |
24 | +import { MatDialog } from '@angular/material'; | |
25 | +import { DialogService } from '@core/services/dialog.service'; | |
26 | +import { AuthService } from '@core/auth/auth.service'; | |
27 | +import { ActivatedRoute, Router } from '@angular/router'; | |
28 | +import { | |
29 | + inputNodeComponent, | |
30 | + ResolvedRuleChainMetaData, | |
31 | + RuleChain, | |
32 | + ruleChainNodeComponent | |
33 | +} from '@shared/models/rule-chain.models'; | |
34 | +import { FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; | |
35 | +import { RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; | |
36 | +import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models'; | |
37 | +import { RuleChainService } from '@core/http/rule-chain.service'; | |
38 | 38 | |
39 | 39 | @Component({ |
40 | 40 | selector: 'tb-rulechain-page', |
41 | 41 | templateUrl: './rulechain-page.component.html', |
42 | 42 | styleUrls: [] |
43 | 43 | }) |
44 | -export class RuleChainPageComponent extends PageComponent implements OnInit { | |
44 | +export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag { | |
45 | + | |
46 | + get isDirty(): boolean { | |
47 | + return this.isDirtyValue || this.isImport; | |
48 | + } | |
49 | + | |
50 | + isImport: boolean; | |
51 | + isDirtyValue: boolean; | |
52 | + | |
53 | + errorTooltips = {}; | |
54 | + isFullscreen = false; | |
55 | + | |
56 | + editingRuleNode = null; | |
57 | + isEditingRuleNode = false; | |
58 | + | |
59 | + editingRuleNodeLink = null; | |
60 | + isEditingRuleNodeLink = false; | |
61 | + | |
62 | + isLibraryOpen = true; | |
63 | + | |
64 | + ruleNodeSearch = ''; | |
45 | 65 | |
46 | 66 | ruleChain: RuleChain; |
67 | + ruleChainMetaData: ResolvedRuleChainMetaData; | |
47 | 68 | |
48 | - flowchartConstants = FlowchartConstants; | |
49 | - selected = []; | |
50 | - model: FcModel = { | |
69 | + ruleChainModel: FcRuleNodeModel = { | |
51 | 70 | nodes: [], |
52 | 71 | edges: [] |
53 | 72 | }; |
73 | + selectedObjects = []; | |
74 | + nextNodeID: number; | |
75 | + nextConnectorID: number; | |
76 | + inputConnectorId: number; | |
77 | + | |
78 | + ruleNodeTypesModel: {[type: string]: {model: FcRuleNodeTypeModel, selectedObjects: any[]}} = {}; | |
79 | + | |
80 | + ruleNodeComponents: Array<RuleNodeComponentDescriptor>; | |
81 | + | |
82 | + flowchartConstants = FlowchartConstants; | |
54 | 83 | |
55 | 84 | constructor(protected store: Store<AppState>, |
56 | 85 | private route: ActivatedRoute, |
57 | - private userService: UserService, | |
86 | + private router: Router, | |
87 | + private ruleChainService: RuleChainService, | |
58 | 88 | private authService: AuthService, |
59 | 89 | private translate: TranslateService, |
60 | 90 | public dialog: MatDialog, |
61 | 91 | public dialogService: DialogService, |
62 | 92 | public fb: FormBuilder) { |
63 | 93 | super(store); |
94 | + this.init(); | |
64 | 95 | } |
65 | 96 | |
66 | 97 | ngOnInit() { |
98 | + } | |
99 | + | |
100 | + private init() { | |
67 | 101 | this.ruleChain = this.route.snapshot.data.ruleChain; |
68 | - this.testData(); | |
102 | + if (this.route.snapshot.data.import && !this.ruleChain) { | |
103 | + this.router.navigateByUrl('ruleChains'); | |
104 | + return; | |
105 | + } | |
106 | + this.isImport = this.route.snapshot.data.import; | |
107 | + this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; | |
108 | + this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents; | |
109 | + for (const type of Object.keys(RuleNodeType)) { | |
110 | + const desc = ruleNodeTypeDescriptors.get(RuleNodeType[type]); | |
111 | + if (!desc.special) { | |
112 | + this.ruleNodeTypesModel[type] = { | |
113 | + model: { | |
114 | + nodes: [], | |
115 | + edges: [] | |
116 | + }, | |
117 | + selectedObjects: [] | |
118 | + }; | |
119 | + } | |
120 | + } | |
121 | + this.loadRuleChainLibrary(this.ruleNodeComponents); | |
122 | + this.createRuleChainModel(); | |
69 | 123 | } |
70 | 124 | |
71 | - onModelChanged() { | |
72 | - console.log('Model changed!'); | |
125 | + private loadRuleChainLibrary(ruleNodeComponents: Array<RuleNodeComponentDescriptor>) { | |
126 | + for (const componentType of Object.keys(this.ruleNodeTypesModel)) { | |
127 | + this.ruleNodeTypesModel[componentType].model.nodes.length = 0; | |
128 | + } | |
129 | + ruleNodeComponents.forEach((ruleNodeComponent) => { | |
130 | + const componentType = ruleNodeComponent.type; | |
131 | + const model = this.ruleNodeTypesModel[componentType].model; | |
132 | + const desc = ruleNodeTypeDescriptors.get(RuleNodeType[componentType]); | |
133 | + let icon = desc.icon; | |
134 | + let iconUrl = null; | |
135 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) { | |
136 | + icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon; | |
137 | + } | |
138 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) { | |
139 | + iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl; | |
140 | + } | |
141 | + const node: FcRuleNodeType = { | |
142 | + id: 'node-lib-' + componentType + '-' + model.nodes.length, | |
143 | + component: ruleNodeComponent, | |
144 | + name: '', | |
145 | + nodeClass: desc.nodeClass, | |
146 | + icon, | |
147 | + iconUrl, | |
148 | + x: 30, | |
149 | + y: 10 + 50 * model.nodes.length, | |
150 | + connectors: [] | |
151 | + }; | |
152 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { | |
153 | + node.connectors.push( | |
154 | + { | |
155 | + type: FlowchartConstants.leftConnectorType, | |
156 | + id: (model.nodes.length * 2) + '' | |
157 | + } | |
158 | + ); | |
159 | + } | |
160 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { | |
161 | + node.connectors.push( | |
162 | + { | |
163 | + type: FlowchartConstants.rightConnectorType, | |
164 | + id: (model.nodes.length * 2 + 1) + '' | |
165 | + } | |
166 | + ); | |
167 | + } | |
168 | + model.nodes.push(node); | |
169 | + }); | |
73 | 170 | } |
74 | 171 | |
75 | - private testData() { | |
76 | - this.model.nodes.push(... | |
77 | - [ | |
78 | - { | |
79 | - name: 'Node 1', | |
80 | - readonly: true, | |
81 | - id: '2', | |
82 | - x: 300, | |
83 | - y: 100, | |
84 | - color: '#000', | |
85 | - borderColor: '#000', | |
86 | - connectors: [ | |
87 | - { | |
88 | - type: FlowchartConstants.leftConnectorType, | |
89 | - id: '1' | |
90 | - }, | |
91 | - { | |
92 | - type: FlowchartConstants.rightConnectorType, | |
93 | - id: '2' | |
94 | - } | |
95 | - ] | |
96 | - }, | |
97 | - { | |
98 | - name: 'Node 2', | |
99 | - id: '3', | |
100 | - x: 600, | |
101 | - y: 100, | |
102 | - color: '#F15B26', | |
103 | - connectors: [ | |
104 | - { | |
105 | - type: FlowchartConstants.leftConnectorType, | |
106 | - id: '3' | |
107 | - }, | |
108 | - { | |
109 | - type: FlowchartConstants.rightConnectorType, | |
110 | - id: '4' | |
111 | - } | |
112 | - ] | |
113 | - }, | |
114 | - { | |
115 | - name: 'Node 3', | |
116 | - id: '4', | |
117 | - x: 1000, | |
118 | - y: 100, | |
119 | - color: '#000', | |
120 | - borderColor: '#000', | |
121 | - connectors: [ | |
122 | - { | |
123 | - type: FlowchartConstants.leftConnectorType, | |
124 | - id: '5' | |
125 | - }, | |
126 | - { | |
127 | - type: FlowchartConstants.rightConnectorType, | |
128 | - id: '6' | |
129 | - } | |
130 | - ] | |
131 | - }, | |
132 | - { | |
133 | - name: 'Node 4', | |
134 | - id: '5', | |
135 | - x: 1300, | |
136 | - y: 100, | |
137 | - color: '#000', | |
138 | - borderColor: '#000', | |
139 | - connectors: [ | |
140 | - { | |
141 | - type: FlowchartConstants.leftConnectorType, | |
142 | - id: '7' | |
143 | - }, | |
144 | - { | |
145 | - type: FlowchartConstants.rightConnectorType, | |
146 | - id: '8' | |
172 | + private createRuleChainModel() { | |
173 | + this.nextNodeID = 1; | |
174 | + this.nextConnectorID = 1; | |
175 | + | |
176 | + this.selectedObjects.length = 0; | |
177 | + this.ruleChainModel.nodes.length = 0; | |
178 | + this.ruleChainModel.edges.length = 0; | |
179 | + | |
180 | + this.inputConnectorId = this.nextConnectorID++; | |
181 | + this.ruleChainModel.nodes.push( | |
182 | + { | |
183 | + id: 'rule-chain-node-' + this.nextNodeID++, | |
184 | + component: inputNodeComponent, | |
185 | + name: '', | |
186 | + nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).nodeClass, | |
187 | + icon: ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).icon, | |
188 | + readonly: true, | |
189 | + x: 50, | |
190 | + y: 150, | |
191 | + connectors: [ | |
192 | + { | |
193 | + type: FlowchartConstants.rightConnectorType, | |
194 | + id: this.inputConnectorId + '' | |
195 | + }, | |
196 | + ] | |
197 | + | |
198 | + } | |
199 | + ); | |
200 | + const nodes: FcRuleNode[] = []; | |
201 | + this.ruleChainMetaData.nodes.forEach((ruleNode) => { | |
202 | + const component = this.ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); | |
203 | + const descriptor = ruleNodeTypeDescriptors.get(component.type); | |
204 | + let icon = descriptor.icon; | |
205 | + let iconUrl = null; | |
206 | + if (component.configurationDescriptor.nodeDefinition.icon) { | |
207 | + icon = component.configurationDescriptor.nodeDefinition.icon; | |
208 | + } | |
209 | + if (component.configurationDescriptor.nodeDefinition.iconUrl) { | |
210 | + iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl; | |
211 | + } | |
212 | + const node: FcRuleNode = { | |
213 | + id: 'rule-chain-node-' + this.nextNodeID++, | |
214 | + ruleNodeId: ruleNode.id, | |
215 | + additionalInfo: ruleNode.additionalInfo, | |
216 | + configuration: ruleNode.configuration, | |
217 | + debugMode: ruleNode.debugMode, | |
218 | + x: Math.round(ruleNode.additionalInfo.layoutX), | |
219 | + y: Math.round(ruleNode.additionalInfo.layoutY), | |
220 | + component, | |
221 | + name: ruleNode.name, | |
222 | + nodeClass: descriptor.nodeClass, | |
223 | + icon, | |
224 | + iconUrl, | |
225 | + connectors: [] | |
226 | + }; | |
227 | + if (component.configurationDescriptor.nodeDefinition.inEnabled) { | |
228 | + node.connectors.push( | |
229 | + { | |
230 | + type: FlowchartConstants.leftConnectorType, | |
231 | + id: (this.nextConnectorID++) + '' | |
232 | + } | |
233 | + ); | |
234 | + } | |
235 | + if (component.configurationDescriptor.nodeDefinition.outEnabled) { | |
236 | + node.connectors.push( | |
237 | + { | |
238 | + type: FlowchartConstants.rightConnectorType, | |
239 | + id: (this.nextConnectorID++) + '' | |
240 | + } | |
241 | + ); | |
242 | + } | |
243 | + nodes.push(node); | |
244 | + this.ruleChainModel.nodes.push(node); | |
245 | + }); | |
246 | + if (this.ruleChainMetaData.firstNodeIndex > -1) { | |
247 | + const destNode = nodes[this.ruleChainMetaData.firstNodeIndex]; | |
248 | + if (destNode) { | |
249 | + const connectors = destNode.connectors.filter(connector => connector.type === FlowchartConstants.leftConnectorType); | |
250 | + if (connectors && connectors.length) { | |
251 | + const edge: FcRuleEdge = { | |
252 | + source: this.inputConnectorId + '', | |
253 | + destination: connectors[0].id | |
254 | + }; | |
255 | + this.ruleChainModel.edges.push(edge); | |
256 | + } | |
257 | + } | |
258 | + } | |
259 | + if (this.ruleChainMetaData.connections) { | |
260 | + const edgeMap: {[edgeKey: string]: FcRuleEdge} = {}; | |
261 | + this.ruleChainMetaData.connections.forEach((connection) => { | |
262 | + const sourceNode = nodes[connection.fromIndex]; | |
263 | + const destNode = nodes[connection.toIndex]; | |
264 | + if (sourceNode && destNode) { | |
265 | + const sourceConnectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType); | |
266 | + const destConnectors = destNode.connectors.filter(connector => connector.type === FlowchartConstants.leftConnectorType); | |
267 | + if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { | |
268 | + const sourceId = sourceConnectors[0].id; | |
269 | + const destId = destConnectors[0].id; | |
270 | + const edgeKey = sourceId + '_' + destId; | |
271 | + let edge = edgeMap[edgeKey]; | |
272 | + if (!edge) { | |
273 | + edge = { | |
274 | + source: sourceId, | |
275 | + destination: destId, | |
276 | + label: connection.type, | |
277 | + labels: [connection.type] | |
278 | + }; | |
279 | + edgeMap[edgeKey] = edge; | |
280 | + this.ruleChainModel.edges.push(edge); | |
281 | + } else { | |
282 | + edge.label += ' / ' + connection.type; | |
283 | + edge.labels.push(connection.type); | |
147 | 284 | } |
148 | - ] | |
285 | + } | |
149 | 286 | } |
150 | - ] | |
151 | - ); | |
152 | - this.model.edges.push(... | |
153 | - [ | |
154 | - { | |
155 | - source: '2', | |
156 | - destination: '3', | |
157 | - label: 'label1' | |
158 | - }, | |
159 | - { | |
160 | - source: '4', | |
161 | - destination: '5', | |
162 | - label: 'label2' | |
163 | - }, | |
164 | - { | |
165 | - source: '6', | |
166 | - destination: '7', | |
167 | - label: 'label3' | |
287 | + }); | |
288 | + } | |
289 | + if (this.ruleChainMetaData.ruleChainConnections) { | |
290 | + const ruleChainsMap = this.ruleChainMetaData.targetRuleChainsMap; | |
291 | + const ruleChainNodesMap: {[ruleChainNodeId: string]: FcRuleNode} = {}; | |
292 | + const ruleChainEdgeMap: {[edgeKey: string]: FcRuleEdge} = {}; | |
293 | + this.ruleChainMetaData.ruleChainConnections.forEach((ruleChainConnection) => { | |
294 | + const ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; | |
295 | + if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) { | |
296 | + let ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; | |
297 | + if (!ruleChainNode) { | |
298 | + ruleChainNode = { | |
299 | + id: 'rule-chain-node-' + this.nextNodeID++, | |
300 | + name: ruleChain.name ? name : 'Unresolved', | |
301 | + targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null, | |
302 | + error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'), | |
303 | + additionalInfo: ruleChainConnection.additionalInfo, | |
304 | + x: Math.round(ruleChainConnection.additionalInfo.layoutX), | |
305 | + y: Math.round(ruleChainConnection.additionalInfo.layoutY), | |
306 | + component: ruleChainNodeComponent, | |
307 | + nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).nodeClass, | |
308 | + icon: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).icon, | |
309 | + connectors: [ | |
310 | + { | |
311 | + type: FlowchartConstants.leftConnectorType, | |
312 | + id: (this.nextConnectorID++) + '' | |
313 | + } | |
314 | + ] | |
315 | + }; | |
316 | + ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; | |
317 | + this.ruleChainModel.nodes.push(ruleChainNode); | |
318 | + } | |
319 | + const sourceNode = nodes[ruleChainConnection.fromIndex]; | |
320 | + if (sourceNode) { | |
321 | + const connectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType); | |
322 | + if (connectors && connectors.length) { | |
323 | + const sourceId = connectors[0].id; | |
324 | + const destId = ruleChainNode.connectors[0].id; | |
325 | + const edgeKey = sourceId + '_' + destId; | |
326 | + let ruleChainEdge = ruleChainEdgeMap[edgeKey]; | |
327 | + if (!ruleChainEdge) { | |
328 | + ruleChainEdge = { | |
329 | + source: sourceId, | |
330 | + destination: destId, | |
331 | + label: ruleChainConnection.type, | |
332 | + labels: [ruleChainConnection.type] | |
333 | + }; | |
334 | + ruleChainEdgeMap[edgeKey] = ruleChainEdge; | |
335 | + this.ruleChainModel.edges.push(ruleChainEdge); | |
336 | + } else { | |
337 | + ruleChainEdge.label += ' / ' + ruleChainConnection.type; | |
338 | + ruleChainEdge.labels.push(ruleChainConnection.type); | |
339 | + } | |
340 | + } | |
341 | + } | |
168 | 342 | } |
169 | - ] | |
170 | - ); | |
343 | + }); | |
344 | + } | |
345 | + this.isDirtyValue = false; | |
346 | + } | |
347 | + | |
348 | + onModelChanged() { | |
349 | + console.log('Model changed!'); | |
350 | + this.isDirtyValue = true; | |
171 | 351 | } |
172 | 352 | |
353 | + | |
173 | 354 | } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { FcNode, FcEdge, FcModel } from 'ngx-flowchart/dist/ngx-flowchart'; | |
18 | +import { RuleNodeComponentDescriptor, RuleNodeConfiguration } from '@shared/models/rule-node.models'; | |
19 | +import { RuleNodeId } from '@app/shared/models/id/rule-node-id'; | |
20 | +import { RuleChainId } from '@shared/models/id/rule-chain-id'; | |
21 | + | |
22 | +export interface FcRuleNodeType extends FcNode { | |
23 | + component: RuleNodeComponentDescriptor; | |
24 | + nodeClass: string; | |
25 | + icon: string; | |
26 | + iconUrl?: string; | |
27 | +} | |
28 | + | |
29 | +export interface FcRuleNodeTypeModel extends FcModel { | |
30 | + nodes: Array<FcRuleNodeType>; | |
31 | +} | |
32 | + | |
33 | +export interface FcRuleNode extends FcRuleNodeType { | |
34 | + ruleNodeId?: RuleNodeId; | |
35 | + additionalInfo?: any; | |
36 | + configuration?: RuleNodeConfiguration; | |
37 | + debugMode?: boolean; | |
38 | + targetRuleChainId?: string; | |
39 | + error?: string; | |
40 | +} | |
41 | + | |
42 | +export interface FcRuleEdge extends FcEdge { | |
43 | + labels?: string[]; | |
44 | +} | |
45 | + | |
46 | +export interface FcRuleNodeModel extends FcModel { | |
47 | + nodes: Array<FcRuleNode>; | |
48 | + edges: Array<FcRuleEdge>; | |
49 | +} | ... | ... |
... | ... | @@ -26,11 +26,18 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
26 | 26 | import { Observable } from 'rxjs'; |
27 | 27 | import { map } from 'rxjs/operators'; |
28 | 28 | import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; |
29 | -import { RuleChain } from '@shared/models/rule-chain.models'; | |
29 | +import { | |
30 | + RuleChain, | |
31 | + RuleChainMetaData, | |
32 | + RuleChainImport, | |
33 | + ResolvedRuleChainMetaData | |
34 | +} from '@shared/models/rule-chain.models'; | |
30 | 35 | import { RuleChainService } from '@core/http/rule-chain.service'; |
31 | 36 | import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; |
32 | 37 | import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module'; |
33 | 38 | import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component'; |
39 | +import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models'; | |
40 | +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; | |
34 | 41 | |
35 | 42 | |
36 | 43 | @Injectable() |
... | ... | @@ -45,6 +52,29 @@ export class RuleChainResolver implements Resolve<RuleChain> { |
45 | 52 | } |
46 | 53 | } |
47 | 54 | |
55 | +@Injectable() | |
56 | +export class ResolvedRuleChainMetaDataResolver implements Resolve<ResolvedRuleChainMetaData> { | |
57 | + | |
58 | + constructor(private ruleChainService: RuleChainService) { | |
59 | + } | |
60 | + | |
61 | + resolve(route: ActivatedRouteSnapshot): Observable<ResolvedRuleChainMetaData> { | |
62 | + const ruleChainId = route.params.ruleChainId; | |
63 | + return this.ruleChainService.getResolvedRuleChainMetadata(ruleChainId); | |
64 | + } | |
65 | +} | |
66 | + | |
67 | +@Injectable() | |
68 | +export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeComponentDescriptor>> { | |
69 | + | |
70 | + constructor(private ruleChainService: RuleChainService) { | |
71 | + } | |
72 | + | |
73 | + resolve(route: ActivatedRouteSnapshot): Observable<Array<RuleNodeComponentDescriptor>> { | |
74 | + return this.ruleChainService.getRuleNodeComponents(); | |
75 | + } | |
76 | +} | |
77 | + | |
48 | 78 | export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { |
49 | 79 | let label: string = component.ruleChain.name; |
50 | 80 | if (component.ruleChain.root) { |
... | ... | @@ -53,6 +83,10 @@ export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, |
53 | 83 | return label; |
54 | 84 | }); |
55 | 85 | |
86 | +export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { | |
87 | + return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`; | |
88 | +}); | |
89 | + | |
56 | 90 | const routes: Routes = [ |
57 | 91 | { |
58 | 92 | path: 'ruleChains', |
... | ... | @@ -77,6 +111,7 @@ const routes: Routes = [ |
77 | 111 | { |
78 | 112 | path: ':ruleChainId', |
79 | 113 | component: RuleChainPageComponent, |
114 | + canDeactivate: [ConfirmOnExitGuard], | |
80 | 115 | data: { |
81 | 116 | breadcrumb: { |
82 | 117 | labelFunction: ruleChainBreadcumbLabelFunction, |
... | ... | @@ -84,10 +119,31 @@ const routes: Routes = [ |
84 | 119 | } as BreadCrumbConfig, |
85 | 120 | auth: [Authority.TENANT_ADMIN], |
86 | 121 | title: 'rulechain.rulechain', |
87 | - widgetEditMode: false | |
122 | + import: false | |
88 | 123 | }, |
89 | 124 | resolve: { |
90 | - ruleChain: RuleChainResolver | |
125 | + ruleChain: RuleChainResolver, | |
126 | + ruleChainMetaData: ResolvedRuleChainMetaDataResolver, | |
127 | + ruleNodeComponents: RuleNodeComponentsResolver | |
128 | + } | |
129 | + }, | |
130 | + { | |
131 | + path: 'ruleChain/import', | |
132 | + component: RuleChainPageComponent, | |
133 | + canDeactivate: [ConfirmOnExitGuard], | |
134 | + data: { | |
135 | + breadcrumb: { | |
136 | + labelFunction: importRuleChainBreadcumbLabelFunction, | |
137 | + icon: 'settings_ethernet' | |
138 | + } as BreadCrumbConfig, | |
139 | + auth: [Authority.TENANT_ADMIN], | |
140 | + title: 'rulechain.rulechain', | |
141 | + import: true | |
142 | + }, | |
143 | + resolve: { | |
144 | + ruleChain: 'importRuleChain', | |
145 | + ruleChainMetaData: 'importRuleChainMetadata', | |
146 | + ruleNodeComponents: RuleNodeComponentsResolver | |
91 | 147 | } |
92 | 148 | } |
93 | 149 | ] |
... | ... | @@ -99,7 +155,23 @@ const routes: Routes = [ |
99 | 155 | exports: [RouterModule], |
100 | 156 | providers: [ |
101 | 157 | RuleChainsTableConfigResolver, |
102 | - RuleChainResolver | |
158 | + RuleChainResolver, | |
159 | + ResolvedRuleChainMetaDataResolver, | |
160 | + RuleNodeComponentsResolver, | |
161 | + { | |
162 | + provide: 'importRuleChain', | |
163 | + useValue: (route: ActivatedRouteSnapshot) => { | |
164 | + const ruleChainImport: RuleChainImport = route.params.ruleChainImport; | |
165 | + return ruleChainImport.ruleChain; | |
166 | + } | |
167 | + }, | |
168 | + { | |
169 | + provide: 'importRuleChainMetadata', | |
170 | + useValue: (route: ActivatedRouteSnapshot) => { | |
171 | + const ruleChainImport: RuleChainImport = route.params.ruleChainImport; | |
172 | + return ruleChainImport.metadata; | |
173 | + } | |
174 | + } | |
103 | 175 | ] |
104 | 176 | }) |
105 | 177 | export class RuleChainRoutingModule { } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { RuleNodeType } from '@shared/models/rule-node.models'; | |
18 | + | |
19 | +export enum ComponentType { | |
20 | + ENRICHMENT = 'ENRICHMENT', | |
21 | + FILTER = 'FILTER', | |
22 | + TRANSFORMATION = 'TRANSFORMATION', | |
23 | + ACTION = 'ACTION', | |
24 | + EXTERNAL = 'EXTERNAL' | |
25 | +} | |
26 | + | |
27 | +export enum ComponentScope { | |
28 | + SYSTEM = 'SYSTEM', | |
29 | + TENANT = 'TENANT' | |
30 | +} | |
31 | + | |
32 | +export interface ComponentDescriptor { | |
33 | + type: ComponentType | RuleNodeType; | |
34 | + scope?: ComponentScope; | |
35 | + name: string; | |
36 | + clazz: string; | |
37 | + configurationDescriptor?: any; | |
38 | + actions?: string; | |
39 | +} | ... | ... |
... | ... | @@ -14,10 +14,12 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import {BaseData} from '@shared/models/base-data'; | |
18 | -import {TenantId} from '@shared/models/id/tenant-id'; | |
19 | -import {RuleChainId} from '@shared/models/id/rule-chain-id'; | |
20 | -import {RuleNodeId} from '@shared/models/id/rule-node-id'; | |
17 | +import { BaseData } from '@shared/models/base-data'; | |
18 | +import { TenantId } from '@shared/models/id/tenant-id'; | |
19 | +import { RuleChainId } from '@shared/models/id/rule-chain-id'; | |
20 | +import { RuleNodeId } from '@shared/models/id/rule-node-id'; | |
21 | +import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models'; | |
22 | +import { ComponentType } from '@shared/models/component-descriptor.models'; | |
21 | 23 | |
22 | 24 | export interface RuleChain extends BaseData<RuleChainId> { |
23 | 25 | tenantId: TenantId; |
... | ... | @@ -28,3 +30,82 @@ export interface RuleChain extends BaseData<RuleChainId> { |
28 | 30 | configuration?: any; |
29 | 31 | additionalInfo?: any; |
30 | 32 | } |
33 | + | |
34 | +export interface RuleChainMetaData { | |
35 | + ruleChainId: RuleChainId; | |
36 | + firstNodeIndex: number; | |
37 | + nodes: Array<RuleNode>; | |
38 | + connections: Array<NodeConnectionInfo>; | |
39 | + ruleChainConnections: Array<RuleChainConnectionInfo>; | |
40 | +} | |
41 | + | |
42 | +export interface ResolvedRuleChainMetaData extends RuleChainMetaData { | |
43 | + targetRuleChainsMap: {[ruleChainId: string]: RuleChain}; | |
44 | +} | |
45 | + | |
46 | +export interface RuleChainImport { | |
47 | + ruleChain: RuleChain; | |
48 | + metadata: ResolvedRuleChainMetaData; | |
49 | +} | |
50 | + | |
51 | +export interface NodeConnectionInfo { | |
52 | + fromIndex: number; | |
53 | + toIndex: number; | |
54 | + type: string; | |
55 | +} | |
56 | + | |
57 | +export interface RuleChainConnectionInfo { | |
58 | + fromIndex: number; | |
59 | + targetRuleChainId: RuleChainId; | |
60 | + additionalInfo: any; | |
61 | + type: string; | |
62 | +} | |
63 | + | |
64 | +export const ruleNodeTypeComponentTypes: ComponentType[] = | |
65 | + [ | |
66 | + ComponentType.FILTER, | |
67 | + ComponentType.ENRICHMENT, | |
68 | + ComponentType.TRANSFORMATION, | |
69 | + ComponentType.ACTION, | |
70 | + ComponentType.EXTERNAL | |
71 | + ]; | |
72 | + | |
73 | +export const ruleChainNodeComponent: RuleNodeComponentDescriptor = { | |
74 | + type: RuleNodeType.RULE_CHAIN, | |
75 | + name: 'rule chain', | |
76 | + clazz: 'tb.internal.RuleChain', | |
77 | + configurationDescriptor: { | |
78 | + nodeDefinition: { | |
79 | + description: '', | |
80 | + details: 'Forwards incoming messages to specified Rule Chain', | |
81 | + inEnabled: true, | |
82 | + outEnabled: false, | |
83 | + relationTypes: [], | |
84 | + customRelations: false, | |
85 | + defaultConfiguration: {} | |
86 | + } | |
87 | + } | |
88 | +}; | |
89 | + | |
90 | +export const unknownNodeComponent: RuleNodeComponentDescriptor = { | |
91 | + type: RuleNodeType.UNKNOWN, | |
92 | + name: 'unknown', | |
93 | + clazz: 'tb.internal.Unknown', | |
94 | + configurationDescriptor: { | |
95 | + nodeDefinition: { | |
96 | + description: '', | |
97 | + details: '', | |
98 | + inEnabled: true, | |
99 | + outEnabled: true, | |
100 | + relationTypes: [], | |
101 | + customRelations: false, | |
102 | + defaultConfiguration: {} | |
103 | + } | |
104 | + } | |
105 | +}; | |
106 | + | |
107 | +export const inputNodeComponent: RuleNodeComponentDescriptor = { | |
108 | + type: RuleNodeType.INPUT, | |
109 | + name: 'Input', | |
110 | + clazz: 'tb.internal.Input' | |
111 | +}; | ... | ... |
... | ... | @@ -20,6 +20,8 @@ import {TenantId} from '@shared/models/id/tenant-id'; |
20 | 20 | import {CustomerId} from '@shared/models/id/customer-id'; |
21 | 21 | import {RuleChainId} from '@shared/models/id/rule-chain-id'; |
22 | 22 | import {RuleNodeId} from '@shared/models/id/rule-node-id'; |
23 | +import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models'; | |
24 | +import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; | |
23 | 25 | |
24 | 26 | export enum MsgDataType { |
25 | 27 | JSON = 'JSON', |
... | ... | @@ -28,7 +30,7 @@ export enum MsgDataType { |
28 | 30 | } |
29 | 31 | |
30 | 32 | export interface RuleNodeConfiguration { |
31 | - todo: Array<any>; | |
33 | + [key: string]: any; | |
32 | 34 | // TODO: |
33 | 35 | } |
34 | 36 | |
... | ... | @@ -40,3 +42,130 @@ export interface RuleNode extends BaseData<RuleNodeId> { |
40 | 42 | configuration: RuleNodeConfiguration; |
41 | 43 | additionalInfo?: any; |
42 | 44 | } |
45 | + | |
46 | +export interface RuleNodeConfigurationDescriptor { | |
47 | + nodeDefinition: { | |
48 | + description: string; | |
49 | + details: string; | |
50 | + inEnabled: boolean; | |
51 | + outEnabled: boolean; | |
52 | + relationTypes: string[]; | |
53 | + customRelations: boolean; | |
54 | + defaultConfiguration: any; | |
55 | + icon?: string; | |
56 | + iconUrl?: string; | |
57 | + uiResources?: string[]; | |
58 | + uiResourceLoadError?: string; | |
59 | + }; | |
60 | +} | |
61 | + | |
62 | +export enum RuleNodeType { | |
63 | + FILTER = 'FILTER', | |
64 | + ENRICHMENT = 'ENRICHMENT', | |
65 | + TRANSFORMATION = 'TRANSFORMATION', | |
66 | + ACTION = 'ACTION', | |
67 | + EXTERNAL = 'EXTERNAL', | |
68 | + RULE_CHAIN = 'RULE_CHAIN', | |
69 | + UNKNOWN = 'UNKNOWN', | |
70 | + INPUT = 'INPUT' | |
71 | +} | |
72 | + | |
73 | +export interface RuleNodeTypeDescriptor { | |
74 | + value: RuleNodeType; | |
75 | + name: string; | |
76 | + details: string; | |
77 | + nodeClass: string; | |
78 | + icon: string; | |
79 | + special?: boolean; | |
80 | +} | |
81 | + | |
82 | +export const ruleNodeTypeDescriptors = new Map<RuleNodeType, RuleNodeTypeDescriptor>( | |
83 | + [ | |
84 | + [ | |
85 | + RuleNodeType.FILTER, | |
86 | + { | |
87 | + value: RuleNodeType.FILTER, | |
88 | + name: 'rulenode.type-filter', | |
89 | + details: 'rulenode.type-filter-details', | |
90 | + nodeClass: 'tb-filter-type', | |
91 | + icon: 'filter_list' | |
92 | + } | |
93 | + ], | |
94 | + [ | |
95 | + RuleNodeType.ENRICHMENT, | |
96 | + { | |
97 | + value: RuleNodeType.ENRICHMENT, | |
98 | + name: 'rulenode.type-enrichment', | |
99 | + details: 'rulenode.type-enrichment-details', | |
100 | + nodeClass: 'tb-enrichment-type', | |
101 | + icon: 'playlist_add' | |
102 | + } | |
103 | + ], | |
104 | + [ | |
105 | + RuleNodeType.TRANSFORMATION, | |
106 | + { | |
107 | + value: RuleNodeType.TRANSFORMATION, | |
108 | + name: 'rulenode.type-transformation', | |
109 | + details: 'rulenode.type-transformation-details', | |
110 | + nodeClass: 'tb-transformation-type', | |
111 | + icon: 'transform' | |
112 | + } | |
113 | + ], | |
114 | + [ | |
115 | + RuleNodeType.ACTION, | |
116 | + { | |
117 | + value: RuleNodeType.ACTION, | |
118 | + name: 'rulenode.type-action', | |
119 | + details: 'rulenode.type-action-details', | |
120 | + nodeClass: 'tb-action-type', | |
121 | + icon: 'flash_on' | |
122 | + } | |
123 | + ], | |
124 | + [ | |
125 | + RuleNodeType.EXTERNAL, | |
126 | + { | |
127 | + value: RuleNodeType.EXTERNAL, | |
128 | + name: 'rulenode.type-external', | |
129 | + details: 'rulenode.type-external-details', | |
130 | + nodeClass: 'tb-external-type', | |
131 | + icon: 'cloud_upload' | |
132 | + } | |
133 | + ], | |
134 | + [ | |
135 | + RuleNodeType.RULE_CHAIN, | |
136 | + { | |
137 | + value: RuleNodeType.RULE_CHAIN, | |
138 | + name: 'rulenode.type-rule-chain', | |
139 | + details: 'rulenode.type-rule-chain-details', | |
140 | + nodeClass: 'tb-rule-chain-type', | |
141 | + icon: 'settings_ethernet' | |
142 | + } | |
143 | + ], | |
144 | + [ | |
145 | + RuleNodeType.INPUT, | |
146 | + { | |
147 | + value: RuleNodeType.INPUT, | |
148 | + name: 'rulenode.type-input', | |
149 | + details: 'rulenode.type-input-details', | |
150 | + nodeClass: 'tb-input-type', | |
151 | + icon: 'input', | |
152 | + special: true | |
153 | + } | |
154 | + ], | |
155 | + [ | |
156 | + RuleNodeType.UNKNOWN, | |
157 | + { | |
158 | + value: RuleNodeType.UNKNOWN, | |
159 | + name: 'rulenode.type-unknown', | |
160 | + details: 'rulenode.type-unknown-details', | |
161 | + nodeClass: 'tb-unknown-type', | |
162 | + icon: 'help_outline' | |
163 | + } | |
164 | + ] | |
165 | + ] | |
166 | +); | |
167 | + | |
168 | +export interface RuleNodeComponentDescriptor extends ComponentDescriptor { | |
169 | + type: RuleNodeType; | |
170 | + configurationDescriptor?: RuleNodeConfigurationDescriptor; | |
171 | +} | ... | ... |