Commit cabdc68a429c5db9c852979d1486a5ad22f47092

Authored by Igor Kulikov
1 parent 2d730b2b

RuleChain page implementation.

... ... @@ -3,6 +3,10 @@
3 3 "target": "http://localhost:8080",
4 4 "secure": false
5 5 },
  6 + "/static/rulenode": {
  7 + "target": "http://localhost:8080",
  8 + "secure": false
  9 + },
6 10 "/api/ws": {
7 11 "target": "ws://localhost:8080",
8 12 "ws": true
... ...
  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 +}
... ...