Showing
23 changed files
with
1761 additions
and
121 deletions
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.rule; | 16 | package org.thingsboard.server.common.data.rule; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
18 | import lombok.Data; | 19 | import lombok.Data; |
19 | import org.thingsboard.server.common.data.id.RuleChainId; | 20 | import org.thingsboard.server.common.data.id.RuleChainId; |
20 | 21 | ||
@@ -47,11 +48,12 @@ public class RuleChainMetaData { | @@ -47,11 +48,12 @@ public class RuleChainMetaData { | ||
47 | } | 48 | } |
48 | connections.add(connectionInfo); | 49 | connections.add(connectionInfo); |
49 | } | 50 | } |
50 | - public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type) { | 51 | + public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type, JsonNode additionalInfo) { |
51 | RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); | 52 | RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); |
52 | connectionInfo.setFromIndex(fromIndex); | 53 | connectionInfo.setFromIndex(fromIndex); |
53 | connectionInfo.setTargetRuleChainId(targetRuleChainId); | 54 | connectionInfo.setTargetRuleChainId(targetRuleChainId); |
54 | connectionInfo.setType(type); | 55 | connectionInfo.setType(type); |
56 | + connectionInfo.setAdditionalInfo(additionalInfo); | ||
55 | if (ruleChainConnections == null) { | 57 | if (ruleChainConnections == null) { |
56 | ruleChainConnections = new ArrayList<>(); | 58 | ruleChainConnections = new ArrayList<>(); |
57 | } | 59 | } |
@@ -59,16 +61,17 @@ public class RuleChainMetaData { | @@ -59,16 +61,17 @@ public class RuleChainMetaData { | ||
59 | } | 61 | } |
60 | 62 | ||
61 | @Data | 63 | @Data |
62 | - public class NodeConnectionInfo { | 64 | + public static class NodeConnectionInfo { |
63 | private int fromIndex; | 65 | private int fromIndex; |
64 | private int toIndex; | 66 | private int toIndex; |
65 | private String type; | 67 | private String type; |
66 | } | 68 | } |
67 | 69 | ||
68 | @Data | 70 | @Data |
69 | - public class RuleChainConnectionInfo { | 71 | + public static class RuleChainConnectionInfo { |
70 | private int fromIndex; | 72 | private int fromIndex; |
71 | private RuleChainId targetRuleChainId; | 73 | private RuleChainId targetRuleChainId; |
74 | + private JsonNode additionalInfo; | ||
72 | private String type; | 75 | private String type; |
73 | } | 76 | } |
74 | 77 |
@@ -166,7 +166,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC | @@ -166,7 +166,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC | ||
166 | EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); | 166 | EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); |
167 | String type = nodeToRuleChainConnection.getType(); | 167 | String type = nodeToRuleChainConnection.getType(); |
168 | try { | 168 | try { |
169 | - createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); | 169 | + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); |
170 | } catch (ExecutionException | InterruptedException e) { | 170 | } catch (ExecutionException | InterruptedException e) { |
171 | log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); | 171 | log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); |
172 | throw new RuntimeException(e); | 172 | throw new RuntimeException(e); |
@@ -206,7 +206,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC | @@ -206,7 +206,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC | ||
206 | ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); | 206 | ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); |
207 | } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { | 207 | } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { |
208 | RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); | 208 | RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); |
209 | - ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type); | 209 | + ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type, nodeRelation.getAdditionalInfo()); |
210 | } | 210 | } |
211 | } | 211 | } |
212 | } | 212 | } |
@@ -69,6 +69,7 @@ | @@ -69,6 +69,7 @@ | ||
69 | "moment": "^2.15.0", | 69 | "moment": "^2.15.0", |
70 | "ngclipboard": "^1.1.1", | 70 | "ngclipboard": "^1.1.1", |
71 | "ngreact": "^0.3.0", | 71 | "ngreact": "^0.3.0", |
72 | + "ngFlowchart": "git://github.com/ikulikov/ngFlowchart.git#master", | ||
72 | "objectpath": "^1.2.1", | 73 | "objectpath": "^1.2.1", |
73 | "oclazyload": "^1.0.9", | 74 | "oclazyload": "^1.0.9", |
74 | "raphael": "^2.2.7", | 75 | "raphael": "^2.2.7", |
@@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes]) | @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes]) | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, | 23 | function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, |
24 | assetService, tenantService, customerService, | 24 | assetService, tenantService, customerService, |
25 | - ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) { | 25 | + ruleService, pluginService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) { |
26 | var service = { | 26 | var service = { |
27 | getEntity: getEntity, | 27 | getEntity: getEntity, |
28 | getEntities: getEntities, | 28 | getEntities: getEntities, |
@@ -73,6 +73,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device | @@ -73,6 +73,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device | ||
73 | case types.entityType.user: | 73 | case types.entityType.user: |
74 | promise = userService.getUser(entityId, true, config); | 74 | promise = userService.getUser(entityId, true, config); |
75 | break; | 75 | break; |
76 | + case types.entityType.rulechain: | ||
77 | + promise = ruleChainService.getRuleChain(entityId, config); | ||
78 | + break; | ||
76 | case types.entityType.alarm: | 79 | case types.entityType.alarm: |
77 | $log.error('Get Alarm Entity is not implemented!'); | 80 | $log.error('Get Alarm Entity is not implemented!'); |
78 | break; | 81 | break; |
@@ -271,6 +274,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device | @@ -271,6 +274,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device | ||
271 | case types.entityType.plugin: | 274 | case types.entityType.plugin: |
272 | promise = pluginService.getAllPlugins(pageLink, config); | 275 | promise = pluginService.getAllPlugins(pageLink, config); |
273 | break; | 276 | break; |
277 | + case types.entityType.rulechain: | ||
278 | + promise = ruleChainService.getRuleChains(pageLink, config); | ||
279 | + break; | ||
274 | case types.entityType.dashboard: | 280 | case types.entityType.dashboard: |
275 | if (user.authority === 'CUSTOMER_USER') { | 281 | if (user.authority === 'CUSTOMER_USER') { |
276 | promise = dashboardService.getCustomerDashboards(customerId, pageLink, config); | 282 | promise = dashboardService.getCustomerDashboards(customerId, pageLink, config); |
@@ -17,7 +17,9 @@ export default angular.module('thingsboard.api.ruleChain', []) | @@ -17,7 +17,9 @@ export default angular.module('thingsboard.api.ruleChain', []) | ||
17 | .factory('ruleChainService', RuleChainService).name; | 17 | .factory('ruleChainService', RuleChainService).name; |
18 | 18 | ||
19 | /*@ngInject*/ | 19 | /*@ngInject*/ |
20 | -function RuleChainService($http, $q) { | 20 | +function RuleChainService($http, $q, $filter, types) { |
21 | + | ||
22 | + var ruleNodeTypes = null; | ||
21 | 23 | ||
22 | var service = { | 24 | var service = { |
23 | getSystemRuleChains: getSystemRuleChains, | 25 | getSystemRuleChains: getSystemRuleChains, |
@@ -27,7 +29,11 @@ function RuleChainService($http, $q) { | @@ -27,7 +29,11 @@ function RuleChainService($http, $q) { | ||
27 | saveRuleChain: saveRuleChain, | 29 | saveRuleChain: saveRuleChain, |
28 | deleteRuleChain: deleteRuleChain, | 30 | deleteRuleChain: deleteRuleChain, |
29 | getRuleChainMetaData: getRuleChainMetaData, | 31 | getRuleChainMetaData: getRuleChainMetaData, |
30 | - saveRuleChainMetaData: saveRuleChainMetaData | 32 | + saveRuleChainMetaData: saveRuleChainMetaData, |
33 | + getRuleNodeTypes: getRuleNodeTypes, | ||
34 | + getRuleNodeComponentType: getRuleNodeComponentType, | ||
35 | + getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, | ||
36 | + resolveTargetRuleChains: resolveTargetRuleChains | ||
31 | }; | 37 | }; |
32 | 38 | ||
33 | return service; | 39 | return service; |
@@ -147,4 +153,131 @@ function RuleChainService($http, $q) { | @@ -147,4 +153,131 @@ function RuleChainService($http, $q) { | ||
147 | return deferred.promise; | 153 | return deferred.promise; |
148 | } | 154 | } |
149 | 155 | ||
156 | + function getRuleNodeSupportedLinks(nodeType) { //eslint-disable-line | ||
157 | + //TODO: | ||
158 | + var deferred = $q.defer(); | ||
159 | + var linkLabels = [ | ||
160 | + { name: 'Success', custom: false }, | ||
161 | + { name: 'Fail', custom: false }, | ||
162 | + { name: 'Custom', custom: true }, | ||
163 | + ]; | ||
164 | + deferred.resolve(linkLabels); | ||
165 | + return deferred.promise; | ||
166 | + } | ||
167 | + | ||
168 | + function getRuleNodeTypes() { | ||
169 | + var deferred = $q.defer(); | ||
170 | + if (ruleNodeTypes) { | ||
171 | + deferred.resolve(ruleNodeTypes); | ||
172 | + } else { | ||
173 | + loadRuleNodeTypes().then( | ||
174 | + (nodeTypes) => { | ||
175 | + ruleNodeTypes = nodeTypes; | ||
176 | + ruleNodeTypes.push( | ||
177 | + { | ||
178 | + nodeType: types.ruleNodeType.RULE_CHAIN.value, | ||
179 | + type: 'Rule chain' | ||
180 | + } | ||
181 | + ); | ||
182 | + deferred.resolve(ruleNodeTypes); | ||
183 | + }, | ||
184 | + () => { | ||
185 | + deferred.reject(); | ||
186 | + } | ||
187 | + ); | ||
188 | + } | ||
189 | + return deferred.promise; | ||
190 | + } | ||
191 | + | ||
192 | + function getRuleNodeComponentType(type) { | ||
193 | + var res = $filter('filter')(ruleNodeTypes, {type: type}, true); | ||
194 | + if (res && res.length) { | ||
195 | + return res[0].nodeType; | ||
196 | + } | ||
197 | + return null; | ||
198 | + } | ||
199 | + | ||
200 | + function resolveTargetRuleChains(ruleChainConnections) { | ||
201 | + var deferred = $q.defer(); | ||
202 | + if (ruleChainConnections && ruleChainConnections.length) { | ||
203 | + var tasks = []; | ||
204 | + for (var i = 0; i < ruleChainConnections.length; i++) { | ||
205 | + tasks.push(getRuleChain(ruleChainConnections[i].targetRuleChainId.id)); | ||
206 | + } | ||
207 | + $q.all(tasks).then( | ||
208 | + (ruleChains) => { | ||
209 | + var ruleChainsMap = {}; | ||
210 | + for (var i = 0; i < ruleChains.length; i++) { | ||
211 | + ruleChainsMap[ruleChains[i].id.id] = ruleChains[i]; | ||
212 | + } | ||
213 | + deferred.resolve(ruleChainsMap); | ||
214 | + }, | ||
215 | + () => { | ||
216 | + deferred.reject(); | ||
217 | + } | ||
218 | + ); | ||
219 | + } else { | ||
220 | + deferred.resolve({}); | ||
221 | + } | ||
222 | + return deferred.promise; | ||
223 | + } | ||
224 | + | ||
225 | + function loadRuleNodeTypes() { | ||
226 | + var deferred = $q.defer(); | ||
227 | + deferred.resolve( | ||
228 | + [ | ||
229 | + { | ||
230 | + nodeType: types.ruleNodeType.FILTER.value, | ||
231 | + type: 'Filter' | ||
232 | + }, | ||
233 | + { | ||
234 | + nodeType: types.ruleNodeType.FILTER.value, | ||
235 | + type: 'Switch' | ||
236 | + }, | ||
237 | + { | ||
238 | + nodeType: types.ruleNodeType.ENRICHMENT.value, | ||
239 | + type: 'Self' | ||
240 | + }, | ||
241 | + { | ||
242 | + nodeType: types.ruleNodeType.ENRICHMENT.value, | ||
243 | + type: 'Tenant/Customer' | ||
244 | + }, | ||
245 | + { | ||
246 | + nodeType: types.ruleNodeType.ENRICHMENT.value, | ||
247 | + type: 'Related Entity' | ||
248 | + }, | ||
249 | + { | ||
250 | + nodeType: types.ruleNodeType.ENRICHMENT.value, | ||
251 | + type: 'Last Telemetry' | ||
252 | + }, | ||
253 | + { | ||
254 | + nodeType: types.ruleNodeType.TRANSFORMATION.value, | ||
255 | + type: 'Modify' | ||
256 | + }, | ||
257 | + { | ||
258 | + nodeType: types.ruleNodeType.TRANSFORMATION.value, | ||
259 | + type: 'New/Update' | ||
260 | + }, | ||
261 | + { | ||
262 | + nodeType: types.ruleNodeType.ACTION.value, | ||
263 | + type: 'Telemetry' | ||
264 | + }, | ||
265 | + { | ||
266 | + nodeType: types.ruleNodeType.ACTION.value, | ||
267 | + type: 'RPC call' | ||
268 | + }, | ||
269 | + { | ||
270 | + nodeType: types.ruleNodeType.ACTION.value, | ||
271 | + type: 'Send email' | ||
272 | + }, | ||
273 | + { | ||
274 | + nodeType: types.ruleNodeType.ACTION.value, | ||
275 | + type: 'Alarm' | ||
276 | + } | ||
277 | + ] | ||
278 | + ); | ||
279 | + return deferred.promise; | ||
280 | + } | ||
281 | + | ||
282 | + | ||
150 | } | 283 | } |
@@ -49,6 +49,7 @@ import 'material-ui'; | @@ -49,6 +49,7 @@ import 'material-ui'; | ||
49 | import 'react-schema-form'; | 49 | import 'react-schema-form'; |
50 | import react from 'ngreact'; | 50 | import react from 'ngreact'; |
51 | import '@flowjs/ng-flow/dist/ng-flow-standalone.min'; | 51 | import '@flowjs/ng-flow/dist/ng-flow-standalone.min'; |
52 | +import 'ngFlowchart/dist/ngFlowchart'; | ||
52 | 53 | ||
53 | import thingsboardLocales from './locale/locale.constant'; | 54 | import thingsboardLocales from './locale/locale.constant'; |
54 | import thingsboardLogin from './login'; | 55 | import thingsboardLogin from './login'; |
@@ -86,6 +87,7 @@ import 'mdPickers/dist/mdPickers.min.css'; | @@ -86,6 +87,7 @@ import 'mdPickers/dist/mdPickers.min.css'; | ||
86 | import 'angular-hotkeys/build/hotkeys.min.css'; | 87 | import 'angular-hotkeys/build/hotkeys.min.css'; |
87 | import 'angular-carousel/dist/angular-carousel.min.css'; | 88 | import 'angular-carousel/dist/angular-carousel.min.css'; |
88 | import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css'; | 89 | import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css'; |
90 | +import 'ngFlowchart/dist/flowchart.css'; | ||
89 | import '../scss/main.scss'; | 91 | import '../scss/main.scss'; |
90 | 92 | ||
91 | import AppConfig from './app.config'; | 93 | import AppConfig from './app.config'; |
@@ -113,6 +115,7 @@ angular.module('thingsboard', [ | @@ -113,6 +115,7 @@ angular.module('thingsboard', [ | ||
113 | 'ngclipboard', | 115 | 'ngclipboard', |
114 | react.name, | 116 | react.name, |
115 | 'flow', | 117 | 'flow', |
118 | + 'flowchart', | ||
116 | thingsboardLocales, | 119 | thingsboardLocales, |
117 | thingsboardLogin, | 120 | thingsboardLogin, |
118 | thingsboardDialogs, | 121 | thingsboardDialogs, |
@@ -457,6 +457,44 @@ export default angular.module('thingsboard.types', []) | @@ -457,6 +457,44 @@ export default angular.module('thingsboard.types', []) | ||
457 | clientSide: false | 457 | clientSide: false |
458 | } | 458 | } |
459 | }, | 459 | }, |
460 | + ruleNodeType: { | ||
461 | + FILTER: { | ||
462 | + value: "FILTER", | ||
463 | + name: "rulenode.type-filter", | ||
464 | + nodeClass: "tb-filter-type", | ||
465 | + icon: "filter_list" | ||
466 | + }, | ||
467 | + ENRICHMENT: { | ||
468 | + value: "ENRICHMENT", | ||
469 | + name: "rulenode.type-enrichment", | ||
470 | + nodeClass: "tb-enrichment-type", | ||
471 | + icon: "playlist_add" | ||
472 | + }, | ||
473 | + TRANSFORMATION: { | ||
474 | + value: "TRANSFORMATION", | ||
475 | + name: "rulenode.type-transformation", | ||
476 | + nodeClass: "tb-transformation-type", | ||
477 | + icon: "transform" | ||
478 | + }, | ||
479 | + ACTION: { | ||
480 | + value: "ACTION", | ||
481 | + name: "rulenode.type-action", | ||
482 | + nodeClass: "tb-action-type", | ||
483 | + icon: "flash_on" | ||
484 | + }, | ||
485 | + RULE_CHAIN: { | ||
486 | + value: "RULE_CHAIN", | ||
487 | + name: "rulenode.type-rule-chain", | ||
488 | + nodeClass: "tb-rule-chain-type", | ||
489 | + icon: "settings_ethernet" | ||
490 | + }, | ||
491 | + INPUT: { | ||
492 | + value: "INPUT", | ||
493 | + nodeClass: "tb-input-type", | ||
494 | + icon: "input", | ||
495 | + special: true | ||
496 | + } | ||
497 | + }, | ||
460 | valueType: { | 498 | valueType: { |
461 | string: { | 499 | string: { |
462 | value: "string", | 500 | value: "string", |
@@ -143,6 +143,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter | @@ -143,6 +143,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter | ||
143 | scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; | 143 | scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; |
144 | scope.entityRequiredText = 'plugin.plugin-required'; | 144 | scope.entityRequiredText = 'plugin.plugin-required'; |
145 | break; | 145 | break; |
146 | + case types.entityType.rulechain: | ||
147 | + scope.selectEntityText = 'rulechain.select-rulechain'; | ||
148 | + scope.entityText = 'rulechain.rulechain'; | ||
149 | + scope.noEntitiesMatchingText = 'rulechain.no-rulechains-matching'; | ||
150 | + scope.entityRequiredText = 'rulechain.rulechain-required'; | ||
151 | + break; | ||
146 | case types.entityType.tenant: | 152 | case types.entityType.tenant: |
147 | scope.selectEntityText = 'tenant.select-tenant'; | 153 | scope.selectEntityText = 'tenant.select-tenant'; |
148 | scope.entityText = 'tenant.tenant'; | 154 | scope.entityText = 'tenant.tenant'; |
@@ -1169,6 +1169,26 @@ export default angular.module('thingsboard.locale', []) | @@ -1169,6 +1169,26 @@ export default angular.module('thingsboard.locale', []) | ||
1169 | "rulechain-required": "Rule chain is required", | 1169 | "rulechain-required": "Rule chain is required", |
1170 | "management": "Rules management" | 1170 | "management": "Rules management" |
1171 | }, | 1171 | }, |
1172 | + "rulenode": { | ||
1173 | + "add": "Add rule node", | ||
1174 | + "name": "Name", | ||
1175 | + "name-required": "Name is required.", | ||
1176 | + "type": "Type", | ||
1177 | + "description": "Description", | ||
1178 | + "delete": "Delete rule node", | ||
1179 | + "rulenode-details": "Rule node details", | ||
1180 | + "link-details": "Rule node link details", | ||
1181 | + "add-link": "Add link", | ||
1182 | + "link-label": "Link label", | ||
1183 | + "link-label-required": "Link label is required.", | ||
1184 | + "custom-link-label": "Custom link label", | ||
1185 | + "custom-link-label-required": "Custom link label is required.", | ||
1186 | + "type-filter": "Filter", | ||
1187 | + "type-enrichment": "Enrichment", | ||
1188 | + "type-transformation": "Transformation", | ||
1189 | + "type-action": "Action", | ||
1190 | + "type-rule-chain": "Rule Chain" | ||
1191 | + }, | ||
1172 | "rule-plugin": { | 1192 | "rule-plugin": { |
1173 | "management": "Rules and plugins management" | 1193 | "management": "Rules and plugins management" |
1174 | }, | 1194 | }, |
ui/src/app/rulechain/add-link.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-dialog aria-label="{{ 'rulenode.add-link' | translate }}" tb-help="'rulechains'" help-container-id="help-container"> | ||
19 | + <form name="theForm" ng-submit="vm.add()"> | ||
20 | + <md-toolbar> | ||
21 | + <div class="md-toolbar-tools"> | ||
22 | + <h2 translate>rulenode.add-link</h2> | ||
23 | + <span flex></span> | ||
24 | + <div id="help-container"></div> | ||
25 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | ||
26 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
27 | + </md-button> | ||
28 | + </div> | ||
29 | + </md-toolbar> | ||
30 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> | ||
31 | + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> | ||
32 | + <md-dialog-content> | ||
33 | + <div class="md-dialog-content"> | ||
34 | + <tb-rule-node-link link="vm.link" labels="vm.labels" is-edit="true" the-form="theForm"></tb-rule-node-link> | ||
35 | + </div> | ||
36 | + </md-dialog-content> | ||
37 | + <md-dialog-actions layout="row"> | ||
38 | + <span flex></span> | ||
39 | + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" | ||
40 | + class="md-raised md-primary"> | ||
41 | + {{ 'action.add' | translate }} | ||
42 | + </md-button> | ||
43 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | | ||
44 | + translate }} | ||
45 | + </md-button> | ||
46 | + </md-dialog-actions> | ||
47 | + </form> | ||
48 | +</md-dialog> |
ui/src/app/rulechain/add-rulenode.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-dialog aria-label="{{ 'rulenode.add' | translate }}" tb-help="'rulechains'" help-container-id="help-container" style="min-width: 650px;"> | ||
19 | + <form name="theForm" ng-submit="vm.add()"> | ||
20 | + <md-toolbar> | ||
21 | + <div class="md-toolbar-tools"> | ||
22 | + <h2 translate>rulenode.add</h2> | ||
23 | + <span flex></span> | ||
24 | + <div id="help-container"></div> | ||
25 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | ||
26 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
27 | + </md-button> | ||
28 | + </div> | ||
29 | + </md-toolbar> | ||
30 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> | ||
31 | + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> | ||
32 | + <md-dialog-content> | ||
33 | + <div class="md-dialog-content"> | ||
34 | + <tb-rule-node rule-node="vm.ruleNode" rule-chain-id="vm.ruleChainId" is-edit="true" the-form="theForm"></tb-rule-node> | ||
35 | + </div> | ||
36 | + </md-dialog-content> | ||
37 | + <md-dialog-actions layout="row"> | ||
38 | + <span flex></span> | ||
39 | + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" | ||
40 | + class="md-raised md-primary"> | ||
41 | + {{ 'action.add' | translate }} | ||
42 | + </md-button> | ||
43 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | | ||
44 | + translate }} | ||
45 | + </md-button> | ||
46 | + </md-dialog-actions> | ||
47 | + </form> | ||
48 | +</md-dialog> |
@@ -15,11 +15,19 @@ | @@ -15,11 +15,19 @@ | ||
15 | */ | 15 | */ |
16 | 16 | ||
17 | import RuleChainRoutes from './rulechain.routes'; | 17 | import RuleChainRoutes from './rulechain.routes'; |
18 | -import RuleChainController from './rulechain.controller'; | 18 | +import RuleChainsController from './rulechains.controller'; |
19 | +import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; | ||
19 | import RuleChainDirective from './rulechain.directive'; | 20 | import RuleChainDirective from './rulechain.directive'; |
21 | +import RuleNodeDirective from './rulenode.directive'; | ||
22 | +import LinkDirective from './link.directive'; | ||
20 | 23 | ||
21 | export default angular.module('thingsboard.ruleChain', []) | 24 | export default angular.module('thingsboard.ruleChain', []) |
22 | .config(RuleChainRoutes) | 25 | .config(RuleChainRoutes) |
26 | + .controller('RuleChainsController', RuleChainsController) | ||
23 | .controller('RuleChainController', RuleChainController) | 27 | .controller('RuleChainController', RuleChainController) |
28 | + .controller('AddRuleNodeController', AddRuleNodeController) | ||
29 | + .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) | ||
24 | .directive('tbRuleChain', RuleChainDirective) | 30 | .directive('tbRuleChain', RuleChainDirective) |
31 | + .directive('tbRuleNode', RuleNodeDirective) | ||
32 | + .directive('tbRuleNodeLink', LinkDirective) | ||
25 | .name; | 33 | .name; |
ui/src/app/rulechain/link-fieldset.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-content class="md-padding tb-link" layout="column"> | ||
19 | + <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> | ||
20 | + <md-input-container class="md-block"> | ||
21 | + <label translate>rulenode.link-label</label> | ||
22 | + <md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()"> | ||
23 | + <md-option ng-repeat="label in labels" ng-value="label"> | ||
24 | + {{label.name}} | ||
25 | + </md-option> | ||
26 | + </md-select> | ||
27 | + <div ng-messages="theForm.linkLabel.$error"> | ||
28 | + <div translate ng-message="required">rulenode.link-label-required</div> | ||
29 | + </div> | ||
30 | + </md-input-container> | ||
31 | + <md-input-container ng-if="selectedLabel.custom" class="md-block"> | ||
32 | + <label translate>rulenode.link-label</label> | ||
33 | + <input required name="customLinkLabel" ng-model="link.label"> | ||
34 | + <div ng-messages="theForm.customLinkLabel.$error"> | ||
35 | + <div translate ng-message="required">rulenode.custom-link-label-required</div> | ||
36 | + </div> | ||
37 | + </md-input-container> | ||
38 | + </fieldset> | ||
39 | +</md-content> |
ui/src/app/rulechain/link.directive.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2018 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 | +/* eslint-disable import/no-unresolved, import/default */ | ||
18 | + | ||
19 | +import linkFieldsetTemplate from './link-fieldset.tpl.html'; | ||
20 | + | ||
21 | +/* eslint-enable import/no-unresolved, import/default */ | ||
22 | + | ||
23 | +/*@ngInject*/ | ||
24 | +export default function LinkDirective($compile, $templateCache, $filter) { | ||
25 | + var linker = function (scope, element) { | ||
26 | + var template = $templateCache.get(linkFieldsetTemplate); | ||
27 | + element.html(template); | ||
28 | + | ||
29 | + scope.selectedLabel = null; | ||
30 | + | ||
31 | + scope.$watch('link', function() { | ||
32 | + scope.selectedLabel = null; | ||
33 | + if (scope.link && scope.labels) { | ||
34 | + if (scope.link.label) { | ||
35 | + var result = $filter('filter')(scope.labels, {name: scope.link.label}); | ||
36 | + if (result && result.length) { | ||
37 | + scope.selectedLabel = result[0]; | ||
38 | + } else { | ||
39 | + result = $filter('filter')(scope.labels, {custom: true}); | ||
40 | + if (result && result.length && result[0].custom) { | ||
41 | + scope.selectedLabel = result[0]; | ||
42 | + } | ||
43 | + } | ||
44 | + } | ||
45 | + } | ||
46 | + }); | ||
47 | + | ||
48 | + scope.selectedLabelChanged = function() { | ||
49 | + if (scope.link && scope.selectedLabel) { | ||
50 | + if (!scope.selectedLabel.custom) { | ||
51 | + scope.link.label = scope.selectedLabel.name; | ||
52 | + } else { | ||
53 | + scope.link.label = ""; | ||
54 | + } | ||
55 | + } | ||
56 | + }; | ||
57 | + | ||
58 | + $compile(element.contents())(scope); | ||
59 | + } | ||
60 | + return { | ||
61 | + restrict: "E", | ||
62 | + link: linker, | ||
63 | + scope: { | ||
64 | + link: '=', | ||
65 | + labels: '=', | ||
66 | + isEdit: '=', | ||
67 | + isReadOnly: '=', | ||
68 | + theForm: '=' | ||
69 | + } | ||
70 | + }; | ||
71 | +} |
@@ -32,7 +32,7 @@ | @@ -32,7 +32,7 @@ | ||
32 | </md-button> | 32 | </md-button> |
33 | </div> | 33 | </div> |
34 | 34 | ||
35 | -<md-content class="md-padding tb-rulechain" layout="column"> | 35 | +<md-content class="md-padding tb-rulechain-fieldset" layout="column"> |
36 | <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> | 36 | <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> |
37 | <md-input-container class="md-block"> | 37 | <md-input-container class="md-block"> |
38 | <label translate>rulechain.name</label> | 38 | <label translate>rulechain.name</label> |
@@ -13,160 +13,582 @@ | @@ -13,160 +13,582 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | + | ||
17 | +import './rulechain.scss'; | ||
18 | + | ||
16 | /* eslint-disable import/no-unresolved, import/default */ | 19 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 20 | ||
18 | -import addRuleChainTemplate from './add-rulechain.tpl.html'; | ||
19 | -import ruleChainCard from './rulechain-card.tpl.html'; | 21 | +import addRuleNodeTemplate from './add-rulenode.tpl.html'; |
22 | +import addRuleNodeLinkTemplate from './add-link.tpl.html'; | ||
20 | 23 | ||
21 | /* eslint-enable import/no-unresolved, import/default */ | 24 | /* eslint-enable import/no-unresolved, import/default */ |
22 | 25 | ||
26 | + | ||
27 | +const deleteKeyCode = 46; | ||
28 | +const ctrlKeyCode = 17; | ||
29 | +const aKeyCode = 65; | ||
30 | +const escKeyCode = 27; | ||
31 | + | ||
23 | /*@ngInject*/ | 32 | /*@ngInject*/ |
24 | -export default function RuleChainController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { | ||
25 | - | ||
26 | - var ruleChainActionsList = [ | ||
27 | - { | ||
28 | - onAction: function ($event, item) { | ||
29 | - exportRuleChain($event, item); | ||
30 | - }, | ||
31 | - name: function() { $translate.instant('action.export') }, | ||
32 | - details: function() { return $translate.instant('rulechain.export') }, | ||
33 | - icon: "file_download" | ||
34 | - }, | ||
35 | - { | ||
36 | - onAction: function ($event, item) { | ||
37 | - vm.grid.deleteItem($event, item); | ||
38 | - }, | ||
39 | - name: function() { return $translate.instant('action.delete') }, | ||
40 | - details: function() { return $translate.instant('rulechain.delete') }, | ||
41 | - icon: "delete", | ||
42 | - isEnabled: isRuleChainEditable | ||
43 | - } | ||
44 | - ]; | ||
45 | - | ||
46 | - var ruleChainAddItemActionsList = [ | ||
47 | - { | ||
48 | - onAction: function ($event) { | ||
49 | - vm.grid.addItem($event); | ||
50 | - }, | ||
51 | - name: function() { return $translate.instant('action.create') }, | ||
52 | - details: function() { return $translate.instant('rulechain.create-new-rulechain') }, | ||
53 | - icon: "insert_drive_file" | ||
54 | - }, | ||
55 | - { | ||
56 | - onAction: function ($event) { | ||
57 | - importExport.importRuleChain($event).then( | ||
58 | - function() { | ||
59 | - vm.grid.refreshList(); | ||
60 | - } | ||
61 | - ); | ||
62 | - }, | ||
63 | - name: function() { return $translate.instant('action.import') }, | ||
64 | - details: function() { return $translate.instant('rulechain.import') }, | ||
65 | - icon: "file_upload" | ||
66 | - } | ||
67 | - ]; | 33 | +export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { |
68 | 34 | ||
69 | var vm = this; | 35 | var vm = this; |
70 | 36 | ||
37 | + vm.$mdExpansionPanel = $mdExpansionPanel; | ||
71 | vm.types = types; | 38 | vm.types = types; |
72 | 39 | ||
73 | - vm.ruleChainGridConfig = { | 40 | + vm.editingRuleNode = null; |
41 | + vm.isEditingRuleNode = false; | ||
42 | + | ||
43 | + vm.editingRuleNodeLink = null; | ||
44 | + vm.isEditingRuleNodeLink = false; | ||
74 | 45 | ||
75 | - refreshParamsFunc: null, | 46 | + vm.ruleChain = ruleChain; |
47 | + vm.ruleChainMetaData = ruleChainMetaData; | ||
76 | 48 | ||
77 | - deleteItemTitleFunc: deleteRuleChainTitle, | ||
78 | - deleteItemContentFunc: deleteRuleChainText, | ||
79 | - deleteItemsTitleFunc: deleteRuleChainsTitle, | ||
80 | - deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, | ||
81 | - deleteItemsContentFunc: deleteRuleChainsText, | 49 | + vm.canvasControl = {}; |
82 | 50 | ||
83 | - fetchItemsFunc: fetchRuleChains, | ||
84 | - saveItemFunc: saveRuleChain, | ||
85 | - deleteItemFunc: deleteRuleChain, | 51 | + vm.ruleChainModel = { |
52 | + nodes: [], | ||
53 | + edges: [] | ||
54 | + }; | ||
55 | + | ||
56 | + vm.ruleNodeTypesModel = {}; | ||
57 | + for (var type in types.ruleNodeType) { | ||
58 | + if (!types.ruleNodeType[type].special) { | ||
59 | + vm.ruleNodeTypesModel[type] = { | ||
60 | + model: { | ||
61 | + nodes: [], | ||
62 | + edges: [] | ||
63 | + }, | ||
64 | + selectedObjects: [] | ||
65 | + }; | ||
66 | + } | ||
67 | + } | ||
86 | 68 | ||
87 | - getItemTitleFunc: getRuleChainTitle, | ||
88 | - itemCardTemplateUrl: ruleChainCard, | ||
89 | - parentCtl: vm, | 69 | + vm.selectedObjects = []; |
90 | 70 | ||
91 | - actionsList: ruleChainActionsList, | ||
92 | - addItemActions: ruleChainAddItemActionsList, | 71 | + vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); |
93 | 72 | ||
94 | - onGridInited: gridInited, | 73 | + vm.ctrlDown = false; |
95 | 74 | ||
96 | - addItemTemplateUrl: addRuleChainTemplate, | 75 | + vm.saveRuleChain = saveRuleChain; |
76 | + vm.revertRuleChain = revertRuleChain; | ||
97 | 77 | ||
98 | - addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, | ||
99 | - noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, | ||
100 | - itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, | ||
101 | - isSelectionEnabled: isRuleChainEditable, | ||
102 | - isDetailsReadOnly: function(ruleChain) { | ||
103 | - return !isRuleChainEditable(ruleChain); | 78 | + vm.keyDown = function (evt) { |
79 | + if (evt.keyCode === ctrlKeyCode) { | ||
80 | + vm.ctrlDown = true; | ||
81 | + evt.stopPropagation(); | ||
82 | + evt.preventDefault(); | ||
104 | } | 83 | } |
105 | }; | 84 | }; |
106 | 85 | ||
107 | - if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { | ||
108 | - vm.ruleChainGridConfig.items = $stateParams.items; | ||
109 | - } | 86 | + vm.keyUp = function (evt) { |
110 | 87 | ||
111 | - if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { | ||
112 | - vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; | ||
113 | - } | 88 | + if (evt.keyCode === deleteKeyCode) { |
89 | + vm.modelservice.deleteSelected(); | ||
90 | + } | ||
114 | 91 | ||
115 | - vm.isRuleChainEditable = isRuleChainEditable; | 92 | + if (evt.keyCode == aKeyCode && vm.ctrlDown) { |
93 | + vm.modelservice.selectAll(); | ||
94 | + } | ||
116 | 95 | ||
117 | - vm.exportRuleChain = exportRuleChain; | 96 | + if (evt.keyCode == escKeyCode) { |
97 | + vm.modelservice.deselectAll(); | ||
98 | + } | ||
118 | 99 | ||
119 | - function deleteRuleChainTitle(ruleChain) { | ||
120 | - return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); | ||
121 | - } | 100 | + if (evt.keyCode === ctrlKeyCode) { |
101 | + vm.ctrlDown = false; | ||
102 | + evt.stopPropagation(); | ||
103 | + evt.preventDefault(); | ||
104 | + } | ||
105 | + }; | ||
122 | 106 | ||
123 | - function deleteRuleChainText() { | ||
124 | - return $translate.instant('rulechain.delete-rulechain-text'); | ||
125 | - } | 107 | + vm.onEditRuleNodeClosed = function() { |
108 | + vm.editingRuleNode = null; | ||
109 | + }; | ||
110 | + | ||
111 | + vm.onEditRuleNodeLinkClosed = function() { | ||
112 | + vm.editingRuleNodeLink = null; | ||
113 | + }; | ||
114 | + | ||
115 | + vm.saveRuleNode = function(theForm) { | ||
116 | + theForm.$setPristine(); | ||
117 | + vm.isEditingRuleNode = false; | ||
118 | + vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; | ||
119 | + vm.editingRuleNode = angular.copy(vm.editingRuleNode); | ||
120 | + }; | ||
121 | + | ||
122 | + vm.saveRuleNodeLink = function(theForm) { | ||
123 | + theForm.$setPristine(); | ||
124 | + vm.isEditingRuleNodeLink = false; | ||
125 | + vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex] = vm.editingRuleNodeLink; | ||
126 | + vm.editingRuleNodeLink = angular.copy(vm.editingRuleNodeLink); | ||
127 | + }; | ||
128 | + | ||
129 | + vm.onRevertRuleNodeEdit = function(theForm) { | ||
130 | + theForm.$setPristine(); | ||
131 | + var node = vm.ruleChainModel.nodes[vm.editingRuleNodeIndex]; | ||
132 | + vm.editingRuleNode = angular.copy(node); | ||
133 | + }; | ||
134 | + | ||
135 | + vm.onRevertRuleNodeLinkEdit = function(theForm) { | ||
136 | + theForm.$setPristine(); | ||
137 | + var edge = vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex]; | ||
138 | + vm.editingRuleNodeLink = angular.copy(edge); | ||
139 | + }; | ||
140 | + | ||
141 | + vm.editCallbacks = { | ||
142 | + edgeDoubleClick: function (event, edge) { | ||
143 | + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); | ||
144 | + if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) { | ||
145 | + ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( | ||
146 | + (labels) => { | ||
147 | + vm.isEditingRuleNode = false; | ||
148 | + vm.editingRuleNode = null; | ||
149 | + vm.editingRuleNodeLinkLabels = labels; | ||
150 | + vm.isEditingRuleNodeLink = true; | ||
151 | + vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); | ||
152 | + vm.editingRuleNodeLink = angular.copy(edge); | ||
153 | + } | ||
154 | + ); | ||
155 | + } | ||
156 | + }, | ||
157 | + nodeCallbacks: { | ||
158 | + 'doubleClick': function (event, node) { | ||
159 | + if (node.nodeType != types.ruleNodeType.INPUT.value) { | ||
160 | + vm.isEditingRuleNodeLink = false; | ||
161 | + vm.editingRuleNodeLink = null; | ||
162 | + vm.isEditingRuleNode = true; | ||
163 | + vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node); | ||
164 | + vm.editingRuleNode = angular.copy(node); | ||
165 | + } | ||
166 | + } | ||
167 | + }, | ||
168 | + isValidEdge: function (source, destination) { | ||
169 | + return source.type === flowchartConstants.rightConnectorType && destination.type === flowchartConstants.leftConnectorType; | ||
170 | + }, | ||
171 | + createEdge: function (event, edge) { | ||
172 | + var deferred = $q.defer(); | ||
173 | + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); | ||
174 | + if (sourceNode.nodeType == types.ruleNodeType.INPUT.value) { | ||
175 | + var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); | ||
176 | + if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { | ||
177 | + deferred.reject(); | ||
178 | + } else { | ||
179 | + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); | ||
180 | + if (res && res.length) { | ||
181 | + vm.modelservice.edges.delete(res[0]); | ||
182 | + } | ||
183 | + deferred.resolve(edge); | ||
184 | + } | ||
185 | + } else { | ||
186 | + ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( | ||
187 | + (labels) => { | ||
188 | + addRuleNodeLink(event, edge, labels).then( | ||
189 | + (link) => { | ||
190 | + deferred.resolve(link); | ||
191 | + }, | ||
192 | + () => { | ||
193 | + deferred.reject(); | ||
194 | + } | ||
195 | + ); | ||
196 | + }, | ||
197 | + () => { | ||
198 | + deferred.reject(); | ||
199 | + } | ||
200 | + ); | ||
201 | + } | ||
202 | + return deferred.promise; | ||
203 | + }, | ||
204 | + dropNode: function (event, node) { | ||
205 | + addRuleNode(event, node); | ||
206 | + } | ||
207 | + }; | ||
126 | 208 | ||
127 | - function deleteRuleChainsTitle(selectedCount) { | ||
128 | - return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat'); | 209 | + loadRuleChainLibrary(); |
210 | + | ||
211 | + function loadRuleChainLibrary() { | ||
212 | + ruleChainService.getRuleNodeTypes().then( | ||
213 | + (ruleNodeTypes) => { | ||
214 | + for (var i=0;i<ruleNodeTypes.length;i++) { | ||
215 | + var ruleNodeType = ruleNodeTypes[i]; | ||
216 | + var nodeType = ruleNodeType.nodeType; | ||
217 | + var model = vm.ruleNodeTypesModel[nodeType].model; | ||
218 | + var node = { | ||
219 | + id: model.nodes.length, | ||
220 | + nodeType: nodeType, | ||
221 | + type: ruleNodeType.type, | ||
222 | + name: '', | ||
223 | + nodeClass: vm.types.ruleNodeType[nodeType].nodeClass, | ||
224 | + icon: vm.types.ruleNodeType[nodeType].icon, | ||
225 | + x: 30, | ||
226 | + y: 10+50*model.nodes.length, | ||
227 | + connectors: [] | ||
228 | + }; | ||
229 | + if (nodeType == types.ruleNodeType.RULE_CHAIN.value) { | ||
230 | + node.connectors.push( | ||
231 | + { | ||
232 | + type: flowchartConstants.leftConnectorType, | ||
233 | + id: model.nodes.length | ||
234 | + } | ||
235 | + ); | ||
236 | + } else { | ||
237 | + node.connectors.push( | ||
238 | + { | ||
239 | + type: flowchartConstants.leftConnectorType, | ||
240 | + id: model.nodes.length*2 | ||
241 | + } | ||
242 | + ); | ||
243 | + node.connectors.push( | ||
244 | + { | ||
245 | + type: flowchartConstants.rightConnectorType, | ||
246 | + id: model.nodes.length*2+1 | ||
247 | + } | ||
248 | + ); | ||
249 | + } | ||
250 | + model.nodes.push(node); | ||
251 | + } | ||
252 | + prepareRuleChain(); | ||
253 | + } | ||
254 | + ); | ||
129 | } | 255 | } |
130 | 256 | ||
131 | - function deleteRuleChainsActionTitle(selectedCount) { | ||
132 | - return $translate.instant('rulechain.delete-rulechains-action-title', {count: selectedCount}, 'messageformat'); | 257 | + function prepareRuleChain() { |
258 | + | ||
259 | + if (vm.ruleChainWatch) { | ||
260 | + vm.ruleChainWatch(); | ||
261 | + vm.ruleChainWatch = null; | ||
262 | + } | ||
263 | + | ||
264 | + vm.nextNodeID = 1; | ||
265 | + vm.nextConnectorID = 1; | ||
266 | + | ||
267 | + vm.selectedObjects.length = 0; | ||
268 | + vm.ruleChainModel.nodes.length = 0; | ||
269 | + vm.ruleChainModel.edges.length = 0; | ||
270 | + | ||
271 | + vm.inputConnectorId = vm.nextConnectorID++; | ||
272 | + | ||
273 | + vm.ruleChainModel.nodes.push( | ||
274 | + { | ||
275 | + id: vm.nextNodeID++, | ||
276 | + type: "Input", | ||
277 | + name: "", | ||
278 | + nodeType: types.ruleNodeType.INPUT.value, | ||
279 | + nodeClass: types.ruleNodeType.INPUT.nodeClass, | ||
280 | + icon: types.ruleNodeType.INPUT.icon, | ||
281 | + readonly: true, | ||
282 | + x: 50, | ||
283 | + y: 150, | ||
284 | + connectors: [ | ||
285 | + { | ||
286 | + type: flowchartConstants.rightConnectorType, | ||
287 | + id: vm.inputConnectorId | ||
288 | + }, | ||
289 | + ] | ||
290 | + | ||
291 | + } | ||
292 | + ); | ||
293 | + ruleChainService.resolveTargetRuleChains(vm.ruleChainMetaData.ruleChainConnections) | ||
294 | + .then((ruleChainsMap) => { | ||
295 | + createRuleChainModel(ruleChainsMap); | ||
296 | + } | ||
297 | + ); | ||
133 | } | 298 | } |
134 | 299 | ||
135 | - function deleteRuleChainsText() { | ||
136 | - return $translate.instant('rulechain.delete-rulechains-text'); | 300 | + function createRuleChainModel(ruleChainsMap) { |
301 | + var nodes = []; | ||
302 | + for (var i=0;i<vm.ruleChainMetaData.nodes.length;i++) { | ||
303 | + var ruleNode = vm.ruleChainMetaData.nodes[i]; | ||
304 | + var nodeType = ruleChainService.getRuleNodeComponentType(ruleNode.type); | ||
305 | + if (nodeType) { | ||
306 | + var node = { | ||
307 | + id: vm.nextNodeID++, | ||
308 | + ruleNodeId: ruleNode.id, | ||
309 | + additionalInfo: ruleNode.additionalInfo, | ||
310 | + configuration: ruleNode.configuration, | ||
311 | + x: ruleNode.additionalInfo.layoutX, | ||
312 | + y: ruleNode.additionalInfo.layoutY, | ||
313 | + type: ruleNode.type, | ||
314 | + name: ruleNode.name, | ||
315 | + nodeType: nodeType, | ||
316 | + nodeClass: vm.types.ruleNodeType[nodeType].nodeClass, | ||
317 | + icon: vm.types.ruleNodeType[nodeType].icon, | ||
318 | + connectors: [ | ||
319 | + { | ||
320 | + type: flowchartConstants.leftConnectorType, | ||
321 | + id: vm.nextConnectorID++ | ||
322 | + }, | ||
323 | + { | ||
324 | + type: flowchartConstants.rightConnectorType, | ||
325 | + id: vm.nextConnectorID++ | ||
326 | + } | ||
327 | + ] | ||
328 | + }; | ||
329 | + nodes.push(node); | ||
330 | + vm.ruleChainModel.nodes.push(node); | ||
331 | + } | ||
332 | + } | ||
333 | + | ||
334 | + if (vm.ruleChainMetaData.firstNodeIndex > -1) { | ||
335 | + var destNode = nodes[vm.ruleChainMetaData.firstNodeIndex]; | ||
336 | + if (destNode) { | ||
337 | + var connectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); | ||
338 | + if (connectors && connectors.length) { | ||
339 | + var edge = { | ||
340 | + source: vm.inputConnectorId, | ||
341 | + destination: connectors[0].id | ||
342 | + }; | ||
343 | + vm.ruleChainModel.edges.push(edge); | ||
344 | + } | ||
345 | + } | ||
346 | + } | ||
347 | + | ||
348 | + if (vm.ruleChainMetaData.connections) { | ||
349 | + for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) { | ||
350 | + var connection = vm.ruleChainMetaData.connections[0]; | ||
351 | + var sourceNode = nodes[connection.fromIndex]; | ||
352 | + destNode = nodes[connection.toIndex]; | ||
353 | + if (sourceNode && destNode) { | ||
354 | + var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); | ||
355 | + var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); | ||
356 | + if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { | ||
357 | + edge = { | ||
358 | + source: sourceConnectors[0].id, | ||
359 | + destination: destConnectors[0].id, | ||
360 | + label: connection.type | ||
361 | + }; | ||
362 | + vm.ruleChainModel.edges.push(edge); | ||
363 | + } | ||
364 | + } | ||
365 | + } | ||
366 | + } | ||
367 | + | ||
368 | + if (vm.ruleChainMetaData.ruleChainConnections) { | ||
369 | + var ruleChainNodesMap = {}; | ||
370 | + for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) { | ||
371 | + var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i]; | ||
372 | + var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; | ||
373 | + if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) { | ||
374 | + var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; | ||
375 | + if (!ruleChainNode) { | ||
376 | + ruleChainNode = { | ||
377 | + id: vm.nextNodeID++, | ||
378 | + additionalInfo: ruleChainConnection.additionalInfo, | ||
379 | + targetRuleChainId: ruleChainConnection.targetRuleChainId.id, | ||
380 | + x: ruleChainConnection.additionalInfo.layoutX, | ||
381 | + y: ruleChainConnection.additionalInfo.layoutY, | ||
382 | + type: 'Rule chain', | ||
383 | + name: ruleChain.name, | ||
384 | + nodeType: vm.types.ruleNodeType.RULE_CHAIN.value, | ||
385 | + nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass, | ||
386 | + icon: vm.types.ruleNodeType.RULE_CHAIN.icon, | ||
387 | + connectors: [ | ||
388 | + { | ||
389 | + type: flowchartConstants.leftConnectorType, | ||
390 | + id: vm.nextConnectorID++ | ||
391 | + } | ||
392 | + ] | ||
393 | + }; | ||
394 | + ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; | ||
395 | + vm.ruleChainModel.nodes.push(ruleChainNode); | ||
396 | + } | ||
397 | + sourceNode = nodes[ruleChainConnection.fromIndex]; | ||
398 | + if (sourceNode) { | ||
399 | + connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); | ||
400 | + if (connectors && connectors.length) { | ||
401 | + var ruleChainEdge = { | ||
402 | + source: connectors[0].id, | ||
403 | + destination: ruleChainNode.connectors[0].id, | ||
404 | + label: ruleChainConnection.type | ||
405 | + }; | ||
406 | + vm.ruleChainModel.edges.push(ruleChainEdge); | ||
407 | + } | ||
408 | + } | ||
409 | + } | ||
410 | + } | ||
411 | + } | ||
412 | + | ||
413 | + vm.canvasControl.adjustCanvasSize(); | ||
414 | + | ||
415 | + vm.isDirty = false; | ||
416 | + | ||
417 | + $mdUtil.nextTick(() => { | ||
418 | + vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', | ||
419 | + function (newVal, oldVal) { | ||
420 | + if (!vm.isDirty && !angular.equals(newVal, oldVal)) { | ||
421 | + vm.isDirty = true; | ||
422 | + } | ||
423 | + }, true | ||
424 | + ); | ||
425 | + }); | ||
137 | } | 426 | } |
138 | 427 | ||
139 | - function gridInited(grid) { | ||
140 | - vm.grid = grid; | 428 | + function saveRuleChain() { |
429 | + var ruleChainMetaData = { | ||
430 | + ruleChainId: vm.ruleChain.id, | ||
431 | + nodes: [], | ||
432 | + connections: [], | ||
433 | + ruleChainConnections: [] | ||
434 | + }; | ||
435 | + | ||
436 | + var nodes = []; | ||
437 | + | ||
438 | + for (var i=0;i<vm.ruleChainModel.nodes.length;i++) { | ||
439 | + var node = vm.ruleChainModel.nodes[i]; | ||
440 | + if (node.nodeType != types.ruleNodeType.INPUT.value && node.nodeType != types.ruleNodeType.RULE_CHAIN.value) { | ||
441 | + var ruleNode = {}; | ||
442 | + if (node.ruleNodeId) { | ||
443 | + ruleNode.id = node.ruleNodeId; | ||
444 | + } | ||
445 | + ruleNode.type = node.type; | ||
446 | + ruleNode.name = node.name; | ||
447 | + ruleNode.configuration = node.configuration; | ||
448 | + ruleNode.additionalInfo = node.additionalInfo; | ||
449 | + if (!ruleNode.additionalInfo) { | ||
450 | + ruleNode.additionalInfo = {}; | ||
451 | + } | ||
452 | + ruleNode.additionalInfo.layoutX = node.x; | ||
453 | + ruleNode.additionalInfo.layoutY = node.y; | ||
454 | + ruleChainMetaData.nodes.push(ruleNode); | ||
455 | + nodes.push(node); | ||
456 | + } | ||
457 | + } | ||
458 | + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); | ||
459 | + if (res && res.length) { | ||
460 | + var firstNodeEdge = res[0]; | ||
461 | + var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination); | ||
462 | + ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); | ||
463 | + } | ||
464 | + for (i=0;i<vm.ruleChainModel.edges.length;i++) { | ||
465 | + var edge = vm.ruleChainModel.edges[i]; | ||
466 | + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); | ||
467 | + var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); | ||
468 | + if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) { | ||
469 | + var fromIndex = nodes.indexOf(sourceNode); | ||
470 | + if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { | ||
471 | + var ruleChainConnection = { | ||
472 | + fromIndex: fromIndex, | ||
473 | + targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId}, | ||
474 | + additionalInfo: destNode.additionalInfo, | ||
475 | + type: edge.label | ||
476 | + }; | ||
477 | + if (!ruleChainConnection.additionalInfo) { | ||
478 | + ruleChainConnection.additionalInfo = {}; | ||
479 | + } | ||
480 | + ruleChainConnection.additionalInfo.layoutX = destNode.x; | ||
481 | + ruleChainConnection.additionalInfo.layoutY = destNode.y; | ||
482 | + ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; | ||
483 | + ruleChainMetaData.ruleChainConnections.push(ruleChainConnection); | ||
484 | + } else { | ||
485 | + var toIndex = nodes.indexOf(destNode); | ||
486 | + var nodeConnection = { | ||
487 | + fromIndex: fromIndex, | ||
488 | + toIndex: toIndex, | ||
489 | + type: edge.label | ||
490 | + }; | ||
491 | + ruleChainMetaData.connections.push(nodeConnection); | ||
492 | + } | ||
493 | + } | ||
494 | + } | ||
495 | + ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then( | ||
496 | + (ruleChainMetaData) => { | ||
497 | + vm.ruleChainMetaData = ruleChainMetaData; | ||
498 | + prepareRuleChain(); | ||
499 | + } | ||
500 | + ); | ||
141 | } | 501 | } |
142 | 502 | ||
143 | - function fetchRuleChains(pageLink) { | ||
144 | - return ruleChainService.getRuleChains(pageLink); | 503 | + function revertRuleChain() { |
504 | + prepareRuleChain(); | ||
145 | } | 505 | } |
146 | 506 | ||
147 | - function saveRuleChain(ruleChain) { | ||
148 | - return ruleChainService.saveRuleChain(ruleChain); | 507 | + function addRuleNode($event, ruleNode) { |
508 | + $mdDialog.show({ | ||
509 | + controller: 'AddRuleNodeController', | ||
510 | + controllerAs: 'vm', | ||
511 | + templateUrl: addRuleNodeTemplate, | ||
512 | + parent: angular.element($document[0].body), | ||
513 | + locals: {ruleNode: ruleNode, ruleChainId: vm.ruleChain.id.id}, | ||
514 | + fullscreen: true, | ||
515 | + targetEvent: $event | ||
516 | + }).then(function (ruleNode) { | ||
517 | + ruleNode.id = vm.nextNodeID++; | ||
518 | + ruleNode.connectors = []; | ||
519 | + ruleNode.connectors.push( | ||
520 | + { | ||
521 | + id: vm.nextConnectorID++, | ||
522 | + type: flowchartConstants.leftConnectorType | ||
523 | + } | ||
524 | + ); | ||
525 | + if (ruleNode.nodeType != types.ruleNodeType.RULE_CHAIN.value) { | ||
526 | + ruleNode.connectors.push( | ||
527 | + { | ||
528 | + id: vm.nextConnectorID++, | ||
529 | + type: flowchartConstants.rightConnectorType | ||
530 | + } | ||
531 | + ); | ||
532 | + } | ||
533 | + vm.ruleChainModel.nodes.push(ruleNode); | ||
534 | + }, function () { | ||
535 | + }); | ||
149 | } | 536 | } |
150 | 537 | ||
151 | - function deleteRuleChain(ruleChainId) { | ||
152 | - return ruleChainService.deleteRuleChain(ruleChainId); | 538 | + function addRuleNodeLink($event, link, labels) { |
539 | + return $mdDialog.show({ | ||
540 | + controller: 'AddRuleNodeLinkController', | ||
541 | + controllerAs: 'vm', | ||
542 | + templateUrl: addRuleNodeLinkTemplate, | ||
543 | + parent: angular.element($document[0].body), | ||
544 | + locals: {link: link, labels: labels}, | ||
545 | + fullscreen: true, | ||
546 | + targetEvent: $event | ||
547 | + }); | ||
153 | } | 548 | } |
154 | 549 | ||
155 | - function getRuleChainTitle(ruleChain) { | ||
156 | - return ruleChain ? ruleChain.name : ''; | 550 | +} |
551 | + | ||
552 | +/*@ngInject*/ | ||
553 | +export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) { | ||
554 | + | ||
555 | + var vm = this; | ||
556 | + | ||
557 | + vm.helpLinks = helpLinks; | ||
558 | + vm.ruleNode = ruleNode; | ||
559 | + vm.ruleChainId = ruleChainId; | ||
560 | + | ||
561 | + vm.add = add; | ||
562 | + vm.cancel = cancel; | ||
563 | + | ||
564 | + function cancel() { | ||
565 | + $mdDialog.cancel(); | ||
157 | } | 566 | } |
158 | 567 | ||
159 | - function isRuleChainEditable(ruleChain) { | ||
160 | - if (userService.getAuthority() === 'TENANT_ADMIN') { | ||
161 | - return ruleChain && ruleChain.tenantId.id != types.id.nullUid; | ||
162 | - } else { | ||
163 | - return userService.getAuthority() === 'SYS_ADMIN'; | ||
164 | - } | 568 | + function add() { |
569 | + $scope.theForm.$setPristine(); | ||
570 | + $mdDialog.hide(vm.ruleNode); | ||
165 | } | 571 | } |
572 | +} | ||
573 | + | ||
574 | +/*@ngInject*/ | ||
575 | +export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) { | ||
166 | 576 | ||
167 | - function exportRuleChain($event, ruleChain) { | ||
168 | - $event.stopPropagation(); | ||
169 | - importExport.exportRuleChain(ruleChain.id.id); | 577 | + var vm = this; |
578 | + | ||
579 | + vm.helpLinks = helpLinks; | ||
580 | + vm.link = link; | ||
581 | + vm.labels = labels; | ||
582 | + | ||
583 | + vm.add = add; | ||
584 | + vm.cancel = cancel; | ||
585 | + | ||
586 | + function cancel() { | ||
587 | + $mdDialog.cancel(); | ||
170 | } | 588 | } |
171 | 589 | ||
590 | + function add() { | ||
591 | + $scope.theForm.$setPristine(); | ||
592 | + $mdDialog.hide(vm.link); | ||
593 | + } | ||
172 | } | 594 | } |
@@ -15,12 +15,16 @@ | @@ -15,12 +15,16 @@ | ||
15 | */ | 15 | */ |
16 | /* eslint-disable import/no-unresolved, import/default */ | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | ||
18 | +import ruleNodeTemplate from './rulenode.tpl.html'; | ||
18 | import ruleChainsTemplate from './rulechains.tpl.html'; | 19 | import ruleChainsTemplate from './rulechains.tpl.html'; |
20 | +import ruleChainTemplate from './rulechain.tpl.html'; | ||
19 | 21 | ||
20 | /* eslint-enable import/no-unresolved, import/default */ | 22 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 23 | ||
22 | /*@ngInject*/ | 24 | /*@ngInject*/ |
23 | -export default function RuleChainRoutes($stateProvider) { | 25 | +export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider) { |
26 | + | ||
27 | + NodeTemplatePathProvider.setTemplatePath(ruleNodeTemplate); | ||
24 | 28 | ||
25 | $stateProvider | 29 | $stateProvider |
26 | .state('home.ruleChains', { | 30 | .state('home.ruleChains', { |
@@ -32,7 +36,7 @@ export default function RuleChainRoutes($stateProvider) { | @@ -32,7 +36,7 @@ export default function RuleChainRoutes($stateProvider) { | ||
32 | "content@home": { | 36 | "content@home": { |
33 | templateUrl: ruleChainsTemplate, | 37 | templateUrl: ruleChainsTemplate, |
34 | controllerAs: 'vm', | 38 | controllerAs: 'vm', |
35 | - controller: 'RuleChainController' | 39 | + controller: 'RuleChainsController' |
36 | } | 40 | } |
37 | }, | 41 | }, |
38 | data: { | 42 | data: { |
@@ -42,5 +46,36 @@ export default function RuleChainRoutes($stateProvider) { | @@ -42,5 +46,36 @@ export default function RuleChainRoutes($stateProvider) { | ||
42 | ncyBreadcrumb: { | 46 | ncyBreadcrumb: { |
43 | label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' | 47 | label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' |
44 | } | 48 | } |
45 | - }); | 49 | + }).state('home.ruleChains.ruleChain', { |
50 | + url: '/:ruleChainId', | ||
51 | + reloadOnSearch: false, | ||
52 | + module: 'private', | ||
53 | + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], | ||
54 | + views: { | ||
55 | + "content@home": { | ||
56 | + templateUrl: ruleChainTemplate, | ||
57 | + controller: 'RuleChainController', | ||
58 | + controllerAs: 'vm' | ||
59 | + } | ||
60 | + }, | ||
61 | + resolve: { | ||
62 | + ruleChain: | ||
63 | + /*@ngInject*/ | ||
64 | + function($stateParams, ruleChainService) { | ||
65 | + return ruleChainService.getRuleChain($stateParams.ruleChainId); | ||
66 | + }, | ||
67 | + ruleChainMetaData: | ||
68 | + /*@ngInject*/ | ||
69 | + function($stateParams, ruleChainService) { | ||
70 | + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); | ||
71 | + } | ||
72 | + }, | ||
73 | + data: { | ||
74 | + searchEnabled: false, | ||
75 | + pageTitle: 'rulechain.rulechain' | ||
76 | + }, | ||
77 | + ncyBreadcrumb: { | ||
78 | + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' | ||
79 | + } | ||
80 | + }); | ||
46 | } | 81 | } |
ui/src/app/rulechain/rulechain.scss
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 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 | +.tb-rulechain { | ||
18 | + .tb-fullscreen-button-style { | ||
19 | + z-index: 1; | ||
20 | + } | ||
21 | + .tb-rulechain-library { | ||
22 | + width: 250px; | ||
23 | + min-width: 250px; | ||
24 | + overflow-y: auto; | ||
25 | + overflow-x: hidden; | ||
26 | + | ||
27 | + .tb-rulechain-library-panel-group { | ||
28 | + .tb-panel-title { | ||
29 | + -webkit-user-select: none; | ||
30 | + -moz-user-select: none; | ||
31 | + -ms-user-select: none; | ||
32 | + user-select: none; | ||
33 | + min-width: 180px; | ||
34 | + } | ||
35 | + .fc-canvas { | ||
36 | + background: none; | ||
37 | + } | ||
38 | + md-icon.md-expansion-panel-icon { | ||
39 | + margin-right: 0px; | ||
40 | + } | ||
41 | + md-expansion-panel-collapsed, .md-expansion-panel-header-container { | ||
42 | + background: #e6e6e6; | ||
43 | + border-color: #909090; | ||
44 | + position: static; | ||
45 | + } | ||
46 | + md-expansion-panel { | ||
47 | + &.md-open { | ||
48 | + margin-top: 0; | ||
49 | + margin-bottom: 0; | ||
50 | + } | ||
51 | + } | ||
52 | + md-expansion-panel-content { | ||
53 | + padding: 0px; | ||
54 | + } | ||
55 | + } | ||
56 | + } | ||
57 | + .tb-rulechain-graph { | ||
58 | + overflow: auto; | ||
59 | + } | ||
60 | +} | ||
61 | + | ||
62 | +.fc-canvas { | ||
63 | + min-width: 100%; | ||
64 | + min-height: 100%; | ||
65 | + outline: none; | ||
66 | +} | ||
67 | + | ||
68 | +.tb-rule-node { | ||
69 | + display: flex; | ||
70 | + flex-direction: row; | ||
71 | + min-width: 150px; | ||
72 | + max-width: 150px; | ||
73 | + min-height: 28px; | ||
74 | + max-height: 28px; | ||
75 | + padding: 5px 10px; | ||
76 | + border-radius: 5px; | ||
77 | + background-color: #F15B26; | ||
78 | + color: #333; | ||
79 | + border: solid 1px #777; | ||
80 | + font-size: 12px; | ||
81 | + &.tb-input-type { | ||
82 | + background-color: #a3eaa9; | ||
83 | + user-select: none; | ||
84 | + } | ||
85 | + &.tb-filter-type { | ||
86 | + background-color: #f1e861; | ||
87 | + } | ||
88 | + &.tb-enrichment-type { | ||
89 | + background-color: #cdf14e; | ||
90 | + } | ||
91 | + &.tb-transformation-type { | ||
92 | + background-color: #79cef1; | ||
93 | + } | ||
94 | + &.tb-action-type { | ||
95 | + background-color: #f1928f; | ||
96 | + } | ||
97 | + &.tb-rule-chain-type { | ||
98 | + background-color: #d6c4f1; | ||
99 | + } | ||
100 | + md-icon { | ||
101 | + font-size: 20px; | ||
102 | + width: 20px; | ||
103 | + height: 20px; | ||
104 | + min-height: 20px; | ||
105 | + min-width: 20px; | ||
106 | + padding-right: 4px; | ||
107 | + } | ||
108 | + .tb-node-type { | ||
109 | + | ||
110 | + } | ||
111 | + .tb-node-title { | ||
112 | + font-weight: 600; | ||
113 | + } | ||
114 | + .tb-node-type, .tb-node-title { | ||
115 | + overflow: hidden; | ||
116 | + white-space: nowrap; | ||
117 | + text-overflow: ellipsis; | ||
118 | + } | ||
119 | +} | ||
120 | + | ||
121 | +.fc-node { | ||
122 | + z-index: 1; | ||
123 | + outline: none; | ||
124 | + &.fc-hover, &.fc-selected { | ||
125 | + -webkit-filter: brightness(70%); | ||
126 | + filter: brightness(70%); | ||
127 | + } | ||
128 | + &.fc-dragging { | ||
129 | + z-index: 10; | ||
130 | + } | ||
131 | + p { | ||
132 | + padding: 0 15px; | ||
133 | + text-align: center; | ||
134 | + } | ||
135 | +} | ||
136 | + | ||
137 | +.fc-leftConnectors, .fc-rightConnectors { | ||
138 | + position: absolute; | ||
139 | + top: 0; | ||
140 | + height: 100%; | ||
141 | + | ||
142 | + display: flex; | ||
143 | + flex-direction: column; | ||
144 | + | ||
145 | + z-index: 0; | ||
146 | + .fc-magnet { | ||
147 | + align-items: center; | ||
148 | + } | ||
149 | +} | ||
150 | + | ||
151 | +.fc-leftConnectors { | ||
152 | + left: -20px; | ||
153 | +} | ||
154 | + | ||
155 | +.fc-rightConnectors { | ||
156 | + right: -20px; | ||
157 | +} | ||
158 | + | ||
159 | +.fc-magnet { | ||
160 | + display: flex; | ||
161 | + flex-grow: 1; | ||
162 | + height: 60px; | ||
163 | + justify-content: center; | ||
164 | +} | ||
165 | + | ||
166 | +.fc-connector { | ||
167 | + width: 14px; | ||
168 | + height: 14px; | ||
169 | + border: 1px solid #333; | ||
170 | + margin: 10px; | ||
171 | + border-radius: 5px; | ||
172 | + background-color: #ccc; | ||
173 | +} | ||
174 | + | ||
175 | +.fc-connector.fc-hover { | ||
176 | + background-color: #000; | ||
177 | +} | ||
178 | + | ||
179 | +.fc-edge { | ||
180 | + outline: none; | ||
181 | + stroke: gray; | ||
182 | + stroke-width: 4; | ||
183 | + fill: transparent; | ||
184 | + &.fc-selected { | ||
185 | + stroke: red; | ||
186 | + stroke-width: 4; | ||
187 | + fill: transparent; | ||
188 | + } | ||
189 | + &.fc-active { | ||
190 | + animation: dash 3s linear infinite; | ||
191 | + stroke-dasharray: 20; | ||
192 | + } | ||
193 | + &.fc-hover { | ||
194 | + stroke: gray; | ||
195 | + stroke-width: 6; | ||
196 | + fill: transparent; | ||
197 | + } | ||
198 | + &.fc-dragging { | ||
199 | + pointer-events: none; | ||
200 | + } | ||
201 | +} | ||
202 | + | ||
203 | +.edge-endpoint { | ||
204 | + fill: gray; | ||
205 | +} | ||
206 | + | ||
207 | +.fc-nodedelete { | ||
208 | + display: none; | ||
209 | +} | ||
210 | + | ||
211 | +.fc-selected .fc-nodedelete { | ||
212 | + outline: none; | ||
213 | + display: block; | ||
214 | + position: absolute; | ||
215 | + right: -13px; | ||
216 | + top: -16px; | ||
217 | + border: solid 2px white; | ||
218 | + border-radius: 50%; | ||
219 | + font-weight: 600; | ||
220 | + font-size: 18px; | ||
221 | + line-height: 18px; | ||
222 | + height: 20px; | ||
223 | + padding-top: 2px; | ||
224 | + width: 22px; | ||
225 | + background: #494949; | ||
226 | + color: #fff; | ||
227 | + text-align: center; | ||
228 | + vertical-align: bottom; | ||
229 | + cursor: pointer; | ||
230 | +} | ||
231 | + | ||
232 | +.fc-edge-label { | ||
233 | + position: absolute; | ||
234 | + user-select: none; | ||
235 | + pointer-events: none; | ||
236 | + opacity: 0.8; | ||
237 | +} | ||
238 | + | ||
239 | +.fc-edge-label-text { | ||
240 | + position: absolute; | ||
241 | + left: 50%; | ||
242 | + -webkit-transform: translateX(-50%); | ||
243 | + transform: translateX(-50%); | ||
244 | + white-space: nowrap; | ||
245 | + text-align: center; | ||
246 | + font-size: 14px; | ||
247 | + font-weight: 600; | ||
248 | + top: 5px; | ||
249 | + span { | ||
250 | + border: solid 2px #003a79; | ||
251 | + border-radius: 10px; | ||
252 | + color: #003a79; | ||
253 | + background-color: #fff; | ||
254 | + padding: 3px 5px; | ||
255 | + } | ||
256 | +} | ||
257 | + | ||
258 | +@keyframes dash { | ||
259 | + from { | ||
260 | + stroke-dashoffset: 500; | ||
261 | + } | ||
262 | +} |
ui/src/app/rulechain/rulechain.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | + | ||
19 | +<md-content flex tb-expand-fullscreen | ||
20 | + expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"> | ||
21 | + <section class="tb-rulechain-container" flex layout="column"> | ||
22 | + <div class="tb-rulechain-layout" flex layout="row"> | ||
23 | + <div class="tb-rulechain-library"> | ||
24 | + <md-expansion-panel-group class="tb-rulechain-library-panel-group" md-component-id="libraryPanelGroup" auto-expand="true" multiple> | ||
25 | + <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel"> | ||
26 | + <md-expansion-panel-collapsed> | ||
27 | + <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> | ||
28 | + <md-expansion-panel-icon></md-expansion-panel-icon> | ||
29 | + </md-expansion-panel-collapsed> | ||
30 | + <md-expansion-panel-expanded> | ||
31 | + <md-expansion-panel-header ng-click="vm.$mdExpansionPanel(typeId).collapse()"> | ||
32 | + <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> | ||
33 | + <md-expansion-panel-icon></md-expansion-panel-icon> | ||
34 | + </md-expansion-panel-header> | ||
35 | + <md-expansion-panel-content> | ||
36 | + <fc-canvas id="tb-rulechain-{{typeId}}" | ||
37 | + model="vm.ruleNodeTypesModel[typeId].model" selected-objects="vm.ruleNodeTypesModel[typeId].selectedObjects" | ||
38 | + automatic-resize="false" | ||
39 | + node-width="170" | ||
40 | + node-height="50" | ||
41 | + drop-target-id="'tb-rulchain-canvas'"></fc-canvas> | ||
42 | + </md-expansion-panel-content> | ||
43 | + </md-expansion-panel-expanded> | ||
44 | + </md-expansion-panel> | ||
45 | + </md-expansion-panel-group> | ||
46 | + </div> | ||
47 | + <div flex class="tb-rulechain-graph"> | ||
48 | + <fc-canvas id="tb-rulchain-canvas" | ||
49 | + ng-keydown="vm.keyDown($event)" | ||
50 | + ng-keyup="vm.keyUp($event)" | ||
51 | + model="vm.ruleChainModel" | ||
52 | + selected-objects="vm.selectedObjects" | ||
53 | + edge-style="curved" | ||
54 | + node-width="170" | ||
55 | + node-height="50" | ||
56 | + automatic-resize="true" | ||
57 | + control="vm.canvasControl" | ||
58 | + callbacks="vm.editCallbacks"> | ||
59 | + </fc-canvas> | ||
60 | + </div> | ||
61 | + </div> | ||
62 | + <tb-details-sidenav class="tb-rulenode-details-sidenav" | ||
63 | + header-title="{{vm.editingRuleNode.name}}" | ||
64 | + header-subtitle="{{'rulenode.rulenode-details' | translate}}" | ||
65 | + is-read-only="false" | ||
66 | + is-open="vm.isEditingRuleNode" | ||
67 | + is-always-edit="true" | ||
68 | + on-close-details="vm.onEditRuleNodeClosed()" | ||
69 | + on-toggle-details-edit-mode="vm.onRevertRuleNodeEdit(vm.ruleNodeForm)" | ||
70 | + on-apply-details="vm.saveRuleNode(vm.ruleNodeForm)" | ||
71 | + the-form="vm.ruleNodeForm"> | ||
72 | + <details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container"> | ||
73 | + <div id="help-container"></div> | ||
74 | + </details-buttons> | ||
75 | + <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode"> | ||
76 | + <tb-rule-node | ||
77 | + rule-node="vm.editingRuleNode" | ||
78 | + rule-chain-id="vm.ruleChain.id.id" | ||
79 | + is-edit="true" | ||
80 | + is-read-only="false" | ||
81 | + on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)" | ||
82 | + the-form="vm.ruleNodeForm"> | ||
83 | + </tb-rule-node> | ||
84 | + </form> | ||
85 | + </tb-details-sidenav> | ||
86 | + <tb-details-sidenav class="tb-rulenode-link-details-sidenav" | ||
87 | + header-title="{{vm.editingRuleNodeLink.label}}" | ||
88 | + header-subtitle="{{'rulenode.link-details' | translate}}" | ||
89 | + is-read-only="false" | ||
90 | + is-open="vm.isEditingRuleNodeLink" | ||
91 | + is-always-edit="true" | ||
92 | + on-close-details="vm.onEditRuleNodeLinkClosed()" | ||
93 | + on-toggle-details-edit-mode="vm.onRevertRuleNodeLinkEdit(vm.ruleNodeLinkForm)" | ||
94 | + on-apply-details="vm.saveRuleNodeLink(vm.ruleNodeLinkForm)" | ||
95 | + the-form="vm.ruleNodeLinkForm"> | ||
96 | + <details-buttons tb-help="vm.helpLinkIdForRuleNodeLink()" help-container-id="link-help-container"> | ||
97 | + <div id="link-help-container"></div> | ||
98 | + </details-buttons> | ||
99 | + <form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink"> | ||
100 | + <tb-rule-node-link | ||
101 | + link="vm.editingRuleNodeLink" | ||
102 | + labels="vm.editingRuleNodeLinkLabels" | ||
103 | + is-edit="true" | ||
104 | + is-read-only="false" | ||
105 | + the-form="vm.ruleNodeLinkForm"> | ||
106 | + </tb-rule-node-link> | ||
107 | + </form> | ||
108 | + </tb-details-sidenav> | ||
109 | + </section> | ||
110 | + <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end"> | ||
111 | + <md-button ng-disabled="$root.loading || !vm.isDirty" | ||
112 | + class="tb-btn-footer md-accent md-hue-2 md-fab" | ||
113 | + aria-label="{{ 'action.apply' | translate }}" | ||
114 | + ng-click="vm.saveRuleChain()"> | ||
115 | + <md-tooltip md-direction="top"> | ||
116 | + {{ 'action.apply-changes' | translate }} | ||
117 | + </md-tooltip> | ||
118 | + <ng-md-icon icon="done"></ng-md-icon> | ||
119 | + </md-button> | ||
120 | + <md-button ng-disabled="$root.loading || !vm.isDirty" | ||
121 | + class="tb-btn-footer md-accent md-hue-2 md-fab" | ||
122 | + aria-label="{{ 'action.decline-changes' | translate }}" | ||
123 | + ng-click="vm.revertRuleChain()"> | ||
124 | + <md-tooltip md-direction="top"> | ||
125 | + {{ 'action.decline-changes' | translate }} | ||
126 | + </md-tooltip> | ||
127 | + <ng-md-icon icon="close"></ng-md-icon> | ||
128 | + </md-button> | ||
129 | + </section> | ||
130 | +</md-content> |
1 | +/* | ||
2 | + * Copyright © 2016-2018 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 | +/* eslint-disable import/no-unresolved, import/default */ | ||
17 | + | ||
18 | +import addRuleChainTemplate from './add-rulechain.tpl.html'; | ||
19 | +import ruleChainCard from './rulechain-card.tpl.html'; | ||
20 | + | ||
21 | +/* eslint-enable import/no-unresolved, import/default */ | ||
22 | + | ||
23 | +/*@ngInject*/ | ||
24 | +export default function RuleChainsController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { | ||
25 | + | ||
26 | + var ruleChainActionsList = [ | ||
27 | + { | ||
28 | + onAction: function ($event, item) { | ||
29 | + vm.grid.openItem($event, item); | ||
30 | + }, | ||
31 | + name: function() { return $translate.instant('rulechain.details') }, | ||
32 | + details: function() { return $translate.instant('rulechain.rulechain-details') }, | ||
33 | + icon: "edit" | ||
34 | + }, | ||
35 | + { | ||
36 | + onAction: function ($event, item) { | ||
37 | + exportRuleChain($event, item); | ||
38 | + }, | ||
39 | + name: function() { $translate.instant('action.export') }, | ||
40 | + details: function() { return $translate.instant('rulechain.export') }, | ||
41 | + icon: "file_download" | ||
42 | + }, | ||
43 | + { | ||
44 | + onAction: function ($event, item) { | ||
45 | + vm.grid.deleteItem($event, item); | ||
46 | + }, | ||
47 | + name: function() { return $translate.instant('action.delete') }, | ||
48 | + details: function() { return $translate.instant('rulechain.delete') }, | ||
49 | + icon: "delete", | ||
50 | + isEnabled: isRuleChainEditable | ||
51 | + } | ||
52 | + ]; | ||
53 | + | ||
54 | + var ruleChainAddItemActionsList = [ | ||
55 | + { | ||
56 | + onAction: function ($event) { | ||
57 | + vm.grid.addItem($event); | ||
58 | + }, | ||
59 | + name: function() { return $translate.instant('action.create') }, | ||
60 | + details: function() { return $translate.instant('rulechain.create-new-rulechain') }, | ||
61 | + icon: "insert_drive_file" | ||
62 | + }, | ||
63 | + { | ||
64 | + onAction: function ($event) { | ||
65 | + importExport.importRuleChain($event).then( | ||
66 | + function() { | ||
67 | + vm.grid.refreshList(); | ||
68 | + } | ||
69 | + ); | ||
70 | + }, | ||
71 | + name: function() { return $translate.instant('action.import') }, | ||
72 | + details: function() { return $translate.instant('rulechain.import') }, | ||
73 | + icon: "file_upload" | ||
74 | + } | ||
75 | + ]; | ||
76 | + | ||
77 | + var vm = this; | ||
78 | + | ||
79 | + vm.types = types; | ||
80 | + | ||
81 | + vm.ruleChainGridConfig = { | ||
82 | + | ||
83 | + refreshParamsFunc: null, | ||
84 | + | ||
85 | + deleteItemTitleFunc: deleteRuleChainTitle, | ||
86 | + deleteItemContentFunc: deleteRuleChainText, | ||
87 | + deleteItemsTitleFunc: deleteRuleChainsTitle, | ||
88 | + deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, | ||
89 | + deleteItemsContentFunc: deleteRuleChainsText, | ||
90 | + | ||
91 | + fetchItemsFunc: fetchRuleChains, | ||
92 | + saveItemFunc: saveRuleChain, | ||
93 | + clickItemFunc: openRuleChain, | ||
94 | + deleteItemFunc: deleteRuleChain, | ||
95 | + | ||
96 | + getItemTitleFunc: getRuleChainTitle, | ||
97 | + itemCardTemplateUrl: ruleChainCard, | ||
98 | + parentCtl: vm, | ||
99 | + | ||
100 | + actionsList: ruleChainActionsList, | ||
101 | + addItemActions: ruleChainAddItemActionsList, | ||
102 | + | ||
103 | + onGridInited: gridInited, | ||
104 | + | ||
105 | + addItemTemplateUrl: addRuleChainTemplate, | ||
106 | + | ||
107 | + addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, | ||
108 | + noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, | ||
109 | + itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, | ||
110 | + isSelectionEnabled: isRuleChainEditable, | ||
111 | + isDetailsReadOnly: function(ruleChain) { | ||
112 | + return !isRuleChainEditable(ruleChain); | ||
113 | + } | ||
114 | + }; | ||
115 | + | ||
116 | + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { | ||
117 | + vm.ruleChainGridConfig.items = $stateParams.items; | ||
118 | + } | ||
119 | + | ||
120 | + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { | ||
121 | + vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; | ||
122 | + } | ||
123 | + | ||
124 | + vm.isRuleChainEditable = isRuleChainEditable; | ||
125 | + | ||
126 | + vm.exportRuleChain = exportRuleChain; | ||
127 | + | ||
128 | + function deleteRuleChainTitle(ruleChain) { | ||
129 | + return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); | ||
130 | + } | ||
131 | + | ||
132 | + function deleteRuleChainText() { | ||
133 | + return $translate.instant('rulechain.delete-rulechain-text'); | ||
134 | + } | ||
135 | + | ||
136 | + function deleteRuleChainsTitle(selectedCount) { | ||
137 | + return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat'); | ||
138 | + } | ||
139 | + | ||
140 | + function deleteRuleChainsActionTitle(selectedCount) { | ||
141 | + return $translate.instant('rulechain.delete-rulechains-action-title', {count: selectedCount}, 'messageformat'); | ||
142 | + } | ||
143 | + | ||
144 | + function deleteRuleChainsText() { | ||
145 | + return $translate.instant('rulechain.delete-rulechains-text'); | ||
146 | + } | ||
147 | + | ||
148 | + function gridInited(grid) { | ||
149 | + vm.grid = grid; | ||
150 | + } | ||
151 | + | ||
152 | + function fetchRuleChains(pageLink) { | ||
153 | + return ruleChainService.getRuleChains(pageLink); | ||
154 | + } | ||
155 | + | ||
156 | + function saveRuleChain(ruleChain) { | ||
157 | + return ruleChainService.saveRuleChain(ruleChain); | ||
158 | + } | ||
159 | + | ||
160 | + function openRuleChain($event, ruleChain) { | ||
161 | + if ($event) { | ||
162 | + $event.stopPropagation(); | ||
163 | + } | ||
164 | + $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id}); | ||
165 | + } | ||
166 | + | ||
167 | + function deleteRuleChain(ruleChainId) { | ||
168 | + return ruleChainService.deleteRuleChain(ruleChainId); | ||
169 | + } | ||
170 | + | ||
171 | + function getRuleChainTitle(ruleChain) { | ||
172 | + return ruleChain ? ruleChain.name : ''; | ||
173 | + } | ||
174 | + | ||
175 | + function isRuleChainEditable(ruleChain) { | ||
176 | + if (userService.getAuthority() === 'TENANT_ADMIN') { | ||
177 | + return ruleChain && ruleChain.tenantId.id != types.id.nullUid; | ||
178 | + } else { | ||
179 | + return userService.getAuthority() === 'SYS_ADMIN'; | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + function exportRuleChain($event, ruleChain) { | ||
184 | + $event.stopPropagation(); | ||
185 | + importExport.exportRuleChain(ruleChain.id.id); | ||
186 | + } | ||
187 | + | ||
188 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-button ng-click="onDeleteRuleNode({event: $event})" | ||
19 | + ng-show="!isEdit && !isReadOnly" | ||
20 | + class="md-raised md-primary">{{ 'rulenode.delete' | translate }}</md-button> | ||
21 | + | ||
22 | +<md-content class="md-padding tb-rulenode" layout="column"> | ||
23 | + <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> | ||
24 | + <md-input-container class="md-block"> | ||
25 | + <label translate>rulenode.type</label> | ||
26 | + <input readonly name="type" ng-model="ruleNode.type"> | ||
27 | + </md-input-container> | ||
28 | + <section ng-if="ruleNode.nodeType != types.ruleNodeType.RULE_CHAIN.value"> | ||
29 | + <md-input-container class="md-block"> | ||
30 | + <label translate>rulenode.name</label> | ||
31 | + <input required name="name" ng-model="ruleNode.name"> | ||
32 | + <div ng-messages="theForm.name.$error"> | ||
33 | + <div translate ng-message="required">rulenode.name-required</div> | ||
34 | + </div> | ||
35 | + </md-input-container> | ||
36 | + <md-input-container class="md-block"> | ||
37 | + <label translate>rulenode.description</label> | ||
38 | + <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> | ||
39 | + </md-input-container> | ||
40 | + </section> | ||
41 | + <section ng-if="ruleNode.nodeType == types.ruleNodeType.RULE_CHAIN.value"> | ||
42 | + <tb-entity-autocomplete the-form="theForm" | ||
43 | + ng-disabled="$root.loading || !isEdit || isReadOnly" | ||
44 | + tb-required="true" | ||
45 | + exclude-entity-ids="[ruleChainId]" | ||
46 | + entity-type="types.entityType.rulechain" | ||
47 | + ng-model="params.targetRuleChainId"> | ||
48 | + </tb-entity-autocomplete> | ||
49 | + <md-input-container class="md-block"> | ||
50 | + <label translate>rulenode.description</label> | ||
51 | + <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> | ||
52 | + </md-input-container> | ||
53 | + </section> | ||
54 | + </fieldset> | ||
55 | +</md-content> |
ui/src/app/rulechain/rulenode.directive.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2018 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 | +/* eslint-disable import/no-unresolved, import/default */ | ||
18 | + | ||
19 | +import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html'; | ||
20 | + | ||
21 | +/* eslint-enable import/no-unresolved, import/default */ | ||
22 | + | ||
23 | +/*@ngInject*/ | ||
24 | +export default function RuleNodeDirective($compile, $templateCache, ruleChainService, types) { | ||
25 | + var linker = function (scope, element) { | ||
26 | + var template = $templateCache.get(ruleNodeFieldsetTemplate); | ||
27 | + element.html(template); | ||
28 | + | ||
29 | + scope.types = types; | ||
30 | + | ||
31 | + scope.params = { | ||
32 | + targetRuleChainId: null | ||
33 | + }; | ||
34 | + | ||
35 | + scope.$watch('ruleNode', function() { | ||
36 | + if (scope.ruleNode && scope.ruleNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { | ||
37 | + scope.params.targetRuleChainId = scope.ruleNode.targetRuleChainId; | ||
38 | + watchTargetRuleChain(); | ||
39 | + } else { | ||
40 | + if (scope.targetRuleChainWatch) { | ||
41 | + scope.targetRuleChainWatch(); | ||
42 | + scope.targetRuleChainWatch = null; | ||
43 | + } | ||
44 | + } | ||
45 | + }); | ||
46 | + | ||
47 | + function watchTargetRuleChain() { | ||
48 | + scope.targetRuleChainWatch = scope.$watch('params.targetRuleChainId', | ||
49 | + function(targetRuleChainId) { | ||
50 | + if (scope.ruleNode.targetRuleChainId != targetRuleChainId) { | ||
51 | + scope.ruleNode.targetRuleChainId = targetRuleChainId; | ||
52 | + if (targetRuleChainId) { | ||
53 | + ruleChainService.getRuleChain(targetRuleChainId).then( | ||
54 | + (ruleChain) => { | ||
55 | + scope.ruleNode.name = ruleChain.name; | ||
56 | + } | ||
57 | + ); | ||
58 | + } else { | ||
59 | + scope.ruleNode.name = ""; | ||
60 | + } | ||
61 | + } | ||
62 | + } | ||
63 | + ); | ||
64 | + } | ||
65 | + $compile(element.contents())(scope); | ||
66 | + } | ||
67 | + return { | ||
68 | + restrict: "E", | ||
69 | + link: linker, | ||
70 | + scope: { | ||
71 | + ruleChainId: '=', | ||
72 | + ruleNode: '=', | ||
73 | + isEdit: '=', | ||
74 | + isReadOnly: '=', | ||
75 | + theForm: '=', | ||
76 | + onDeleteRuleNode: '&' | ||
77 | + } | ||
78 | + }; | ||
79 | +} |
ui/src/app/rulechain/rulenode.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div | ||
19 | + id="{{node.id}}" | ||
20 | + ng-attr-style="position: absolute; top: {{ node.y }}px; left: {{ node.x }}px;" | ||
21 | + ng-dblclick="callbacks.doubleClick($event, node)"> | ||
22 | + <div class="tb-rule-node {{node.nodeClass}}"> | ||
23 | + <md-icon aria-label="node-type-icon" flex="15" | ||
24 | + class="material-icons">{{node.icon}}</md-icon> | ||
25 | + <div layout="column" flex="85" layout-align="center"> | ||
26 | + <span class="tb-node-type">{{ node.type }}</span> | ||
27 | + <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span> | ||
28 | + </div> | ||
29 | + <div class="{{flowchartConstants.leftConnectorClass}}"> | ||
30 | + <div fc-magnet | ||
31 | + ng-repeat="connector in modelservice.nodes.getConnectorsByType(node, flowchartConstants.leftConnectorType)"> | ||
32 | + <div fc-connector></div> | ||
33 | + </div> | ||
34 | + </div> | ||
35 | + <div class="{{flowchartConstants.rightConnectorClass}}"> | ||
36 | + <div fc-magnet | ||
37 | + ng-repeat="connector in modelservice.nodes.getConnectorsByType(node, flowchartConstants.rightConnectorType)"> | ||
38 | + <div fc-connector></div> | ||
39 | + </div> | ||
40 | + </div> | ||
41 | + </div> | ||
42 | + <div ng-if="modelservice.isEditable() && !node.readonly" class="fc-nodedelete" ng-click="modelservice.nodes.delete(node)"> | ||
43 | + × | ||
44 | + </div> | ||
45 | +</div> |