Showing
35 changed files
with
483 additions
and
72 deletions
... | ... | @@ -180,7 +180,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe |
180 | 180 | return scannedComponent; |
181 | 181 | } |
182 | 182 | |
183 | - private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException { | |
183 | + private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception { | |
184 | 184 | NodeDefinition nodeDefinition = new NodeDefinition(); |
185 | 185 | nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); |
186 | 186 | nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); |
... | ... | @@ -188,9 +188,10 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe |
188 | 188 | nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); |
189 | 189 | nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes()); |
190 | 190 | nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); |
191 | - String defaultConfigResourceName = nodeAnnotation.defaultConfigResource(); | |
192 | - nodeDefinition.setDefaultConfiguration(mapper.readTree( | |
193 | - Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8))); | |
191 | + Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz(); | |
192 | + NodeConfiguration config = configClazz.newInstance(); | |
193 | + NodeConfiguration defaultConfiguration = config.defaultConfiguration(); | |
194 | + nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); | |
194 | 195 | return nodeDefinition; |
195 | 196 | } |
196 | 197 | ... | ... |
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
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 | +package org.thingsboard.rule.engine.api; | |
17 | + | |
18 | +public interface NodeConfiguration { | |
19 | + | |
20 | + NodeConfiguration defaultConfiguration(); | |
21 | + | |
22 | +} | ... | ... |
... | ... | @@ -35,15 +35,16 @@ public @interface RuleNode { |
35 | 35 | |
36 | 36 | String nodeDetails(); |
37 | 37 | |
38 | + Class<? extends NodeConfiguration> configClazz(); | |
39 | + | |
38 | 40 | boolean inEnabled() default true; |
39 | 41 | |
40 | 42 | boolean outEnabled() default true; |
41 | 43 | |
42 | 44 | ComponentScope scope() default ComponentScope.TENANT; |
43 | 45 | |
44 | - String defaultConfigResource() default "EmptyNodeConfig.json"; | |
45 | - | |
46 | 46 | String[] relationTypes() default {"Success", "Failure"}; |
47 | 47 | |
48 | 48 | boolean customRelations() default false; |
49 | + | |
49 | 50 | } | ... | ... |
rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json
deleted
100644 → 0
rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json
deleted
100644 → 0
... | ... | @@ -30,6 +30,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
30 | 30 | @RuleNode( |
31 | 31 | type = ComponentType.FILTER, |
32 | 32 | name = "script", relationTypes = {"True", "False", "Failure"}, |
33 | + configClazz = TbJsFilterNodeConfiguration.class, | |
33 | 34 | nodeDescription = "Filter incoming messages using JS script", |
34 | 35 | nodeDetails = "Evaluate incoming Message with configured JS condition. " + |
35 | 36 | "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + | ... | ... |
... | ... | @@ -16,9 +16,17 @@ |
16 | 16 | package org.thingsboard.rule.engine.filter; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 20 | |
20 | 21 | @Data |
21 | -public class TbJsFilterNodeConfiguration { | |
22 | +public class TbJsFilterNodeConfiguration implements NodeConfiguration { | |
22 | 23 | |
23 | 24 | private String jsScript; |
25 | + | |
26 | + @Override | |
27 | + public TbJsFilterNodeConfiguration defaultConfiguration() { | |
28 | + TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); | |
29 | + configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); | |
30 | + return configuration; | |
31 | + } | |
24 | 32 | } | ... | ... |
... | ... | @@ -31,6 +31,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
31 | 31 | @RuleNode( |
32 | 32 | type = ComponentType.FILTER, |
33 | 33 | name = "switch", customRelations = true, |
34 | + configClazz = TbJsSwitchNodeConfiguration.class, | |
34 | 35 | nodeDescription = "Route incoming Message to one or multiple output chains", |
35 | 36 | nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + |
36 | 37 | "If Array is empty - message not routed to next Node. " + | ... | ... |
... | ... | @@ -15,14 +15,29 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.filter; |
17 | 17 | |
18 | +import com.google.common.collect.Sets; | |
18 | 19 | import lombok.Data; |
20 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 21 | |
20 | 22 | import java.util.Set; |
21 | 23 | |
22 | 24 | @Data |
23 | -public class TbJsSwitchNodeConfiguration { | |
25 | +public class TbJsSwitchNodeConfiguration implements NodeConfiguration { | |
24 | 26 | |
25 | 27 | private String jsScript; |
26 | 28 | private Set<String> allowedRelations; |
27 | 29 | private boolean routeToAllWithNoCheck; |
30 | + | |
31 | + @Override | |
32 | + public TbJsSwitchNodeConfiguration defaultConfiguration() { | |
33 | + TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); | |
34 | + configuration.setJsScript("function nextRelation(meta, msg) {\n" + | |
35 | + " return ['one','nine'];" + | |
36 | + "};\n" + | |
37 | + "\n" + | |
38 | + "nextRelation(meta, msg);"); | |
39 | + configuration.setAllowedRelations(Sets.newHashSet("one", "two")); | |
40 | + configuration.setRouteToAllWithNoCheck(false); | |
41 | + return configuration; | |
42 | + } | |
28 | 43 | } | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg; |
28 | 28 | @RuleNode( |
29 | 29 | type = ComponentType.FILTER, |
30 | 30 | name = "message type", |
31 | + configClazz = TbMsgTypeFilterNodeConfiguration.class, | |
31 | 32 | nodeDescription = "Filter incoming messages by Message Type", |
32 | 33 | nodeDetails = "Evaluate incoming Message with configured JS condition. " + |
33 | 34 | "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.") | ... | ... |
... | ... | @@ -16,15 +16,24 @@ |
16 | 16 | package org.thingsboard.rule.engine.filter; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 20 | |
21 | +import java.util.Arrays; | |
22 | +import java.util.Collections; | |
20 | 23 | import java.util.List; |
21 | 24 | |
22 | 25 | /** |
23 | 26 | * Created by ashvayka on 19.01.18. |
24 | 27 | */ |
25 | 28 | @Data |
26 | -public class TbMsgTypeFilterNodeConfiguration { | |
29 | +public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration { | |
27 | 30 | |
28 | 31 | private List<String> messageTypes; |
29 | 32 | |
33 | + @Override | |
34 | + public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { | |
35 | + TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); | |
36 | + configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); | |
37 | + return configuration; | |
38 | + } | |
30 | 39 | } | ... | ... |
... | ... | @@ -38,6 +38,7 @@ import static org.thingsboard.server.common.data.DataConstants.*; |
38 | 38 | @Slf4j |
39 | 39 | @RuleNode(type = ComponentType.ENRICHMENT, |
40 | 40 | name = "originator attributes", |
41 | + configClazz = TbGetAttributesNodeConfiguration.class, | |
41 | 42 | nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", |
42 | 43 | nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + |
43 | 44 | "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " + | ... | ... |
... | ... | @@ -16,14 +16,16 @@ |
16 | 16 | package org.thingsboard.rule.engine.metadata; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 20 | |
21 | +import java.util.Collections; | |
20 | 22 | import java.util.List; |
21 | 23 | |
22 | 24 | /** |
23 | 25 | * Created by ashvayka on 19.01.18. |
24 | 26 | */ |
25 | 27 | @Data |
26 | -public class TbGetAttributesNodeConfiguration { | |
28 | +public class TbGetAttributesNodeConfiguration implements NodeConfiguration { | |
27 | 29 | |
28 | 30 | private List<String> clientAttributeNames; |
29 | 31 | private List<String> sharedAttributeNames; |
... | ... | @@ -31,4 +33,13 @@ public class TbGetAttributesNodeConfiguration { |
31 | 33 | |
32 | 34 | private List<String> latestTsKeyNames; |
33 | 35 | |
36 | + @Override | |
37 | + public TbGetAttributesNodeConfiguration defaultConfiguration() { | |
38 | + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); | |
39 | + configuration.setClientAttributeNames(Collections.emptyList()); | |
40 | + configuration.setSharedAttributeNames(Collections.emptyList()); | |
41 | + configuration.setServerAttributeNames(Collections.emptyList()); | |
42 | + configuration.setLatestTsKeyNames(Collections.emptyList()); | |
43 | + return configuration; | |
44 | + } | |
34 | 45 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
26 | 26 | @RuleNode( |
27 | 27 | type = ComponentType.ENRICHMENT, |
28 | 28 | name="customer attributes", |
29 | + configClazz = TbGetEntityAttrNodeConfiguration.class, | |
29 | 30 | nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", |
30 | 31 | nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + |
31 | 32 | "To access those attributes in other nodes this template can be used " + | ... | ... |
... | ... | @@ -16,13 +16,25 @@ |
16 | 16 | package org.thingsboard.rule.engine.metadata; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 20 | |
21 | +import java.util.HashMap; | |
20 | 22 | import java.util.Map; |
21 | 23 | import java.util.Optional; |
22 | 24 | |
23 | 25 | @Data |
24 | -public class TbGetEntityAttrNodeConfiguration { | |
26 | +public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration { | |
25 | 27 | |
26 | 28 | private Map<String, String> attrMapping; |
27 | 29 | private boolean isTelemetry = false; |
30 | + | |
31 | + @Override | |
32 | + public TbGetEntityAttrNodeConfiguration defaultConfiguration() { | |
33 | + TbGetEntityAttrNodeConfiguration configuration = new TbGetEntityAttrNodeConfiguration(); | |
34 | + Map<String, String> attrMapping = new HashMap<>(); | |
35 | + attrMapping.putIfAbsent("temperature", "tempo"); | |
36 | + configuration.setAttrMapping(attrMapping); | |
37 | + configuration.setTelemetry(true); | |
38 | + return configuration; | |
39 | + } | |
28 | 40 | } | ... | ... |
... | ... | @@ -16,11 +16,28 @@ |
16 | 16 | package org.thingsboard.rule.engine.metadata; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
20 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
19 | 21 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
20 | 22 | |
23 | +import java.util.HashMap; | |
24 | +import java.util.Map; | |
25 | + | |
21 | 26 | @Data |
22 | -public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { | |
27 | +public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { | |
23 | 28 | |
24 | 29 | private String relationType; |
25 | 30 | private EntitySearchDirection direction; |
31 | + | |
32 | + @Override | |
33 | + public TbGetRelatedAttrNodeConfiguration defaultConfiguration() { | |
34 | + TbGetRelatedAttrNodeConfiguration configuration = new TbGetRelatedAttrNodeConfiguration(); | |
35 | + Map<String, String> attrMapping = new HashMap<>(); | |
36 | + attrMapping.putIfAbsent("temperature", "tempo"); | |
37 | + configuration.setAttrMapping(attrMapping); | |
38 | + configuration.setTelemetry(true); | |
39 | + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); | |
40 | + configuration.setDirection(EntitySearchDirection.FROM); | |
41 | + return configuration; | |
42 | + } | |
26 | 43 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
26 | 26 | @RuleNode( |
27 | 27 | type = ComponentType.ENRICHMENT, |
28 | 28 | name="related attributes", |
29 | + configClazz = TbGetRelatedAttrNodeConfiguration.class, | |
29 | 30 | nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", |
30 | 31 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
31 | 32 | "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
28 | 28 | @RuleNode( |
29 | 29 | type = ComponentType.ENRICHMENT, |
30 | 30 | name="tenant attributes", |
31 | + configClazz = TbGetEntityAttrNodeConfiguration.class, | |
31 | 32 | nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", |
32 | 33 | nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + |
33 | 34 | "To access those attributes in other nodes this template can be used " + | ... | ... |
... | ... | @@ -36,6 +36,7 @@ import java.util.HashSet; |
36 | 36 | @RuleNode( |
37 | 37 | type = ComponentType.TRANSFORMATION, |
38 | 38 | name="change originator", |
39 | + configClazz = TbChangeOriginatorNodeConfiguration.class, | |
39 | 40 | nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", |
40 | 41 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
41 | 42 | "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") | ... | ... |
... | ... | @@ -16,12 +16,24 @@ |
16 | 16 | package org.thingsboard.rule.engine.transform; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
20 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
19 | 21 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
20 | 22 | |
21 | 23 | @Data |
22 | -public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{ | |
24 | +public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { | |
23 | 25 | |
24 | 26 | private String originatorSource; |
25 | 27 | private EntitySearchDirection direction; |
26 | 28 | private String relationType; |
29 | + | |
30 | + @Override | |
31 | + public TbChangeOriginatorNodeConfiguration defaultConfiguration() { | |
32 | + TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration(); | |
33 | + configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); | |
34 | + configuration.setDirection(EntitySearchDirection.FROM); | |
35 | + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); | |
36 | + configuration.setStartNewChain(false); | |
37 | + return configuration; | |
38 | + } | |
27 | 39 | } | ... | ... |
... | ... | @@ -27,6 +27,7 @@ import javax.script.Bindings; |
27 | 27 | @RuleNode( |
28 | 28 | type = ComponentType.TRANSFORMATION, |
29 | 29 | name = "script", |
30 | + configClazz = TbTransformMsgNodeConfiguration.class, | |
30 | 31 | nodeDescription = "Change Message payload and Metadata using JavaScript", |
31 | 32 | nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " + |
32 | 33 | "<code>meta</code> - is a Message metadata.<br/>" + | ... | ... |
... | ... | @@ -16,9 +16,18 @@ |
16 | 16 | package org.thingsboard.rule.engine.transform; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | 20 | |
20 | 21 | @Data |
21 | -public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration { | |
22 | +public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { | |
22 | 23 | |
23 | 24 | private String jsScript; |
25 | + | |
26 | + @Override | |
27 | + public TbTransformMsgNodeConfiguration defaultConfiguration() { | |
28 | + TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); | |
29 | + configuration.setStartNewChain(false); | |
30 | + configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); | |
31 | + return configuration; | |
32 | + } | |
24 | 33 | } | ... | ... |
... | ... | @@ -153,16 +153,21 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) |
153 | 153 | return deferred.promise; |
154 | 154 | } |
155 | 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; | |
156 | + function getRuleNodeSupportedLinks(component) { | |
157 | + var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; | |
158 | + var customRelations = component.configurationDescriptor.nodeDefinition.customRelations; | |
159 | + var linkLabels = []; | |
160 | + for (var i=0;i<relationTypes.length;i++) { | |
161 | + linkLabels.push({ | |
162 | + name: relationTypes[i], custom: false | |
163 | + }); | |
164 | + } | |
165 | + if (customRelations) { | |
166 | + linkLabels.push( | |
167 | + { name: 'Custom', custom: true } | |
168 | + ); | |
169 | + } | |
170 | + return linkLabels; | |
166 | 171 | } |
167 | 172 | |
168 | 173 | function getRuleNodeComponents() { | ... | ... |
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 | +import './json-object-edit.scss'; | |
17 | + | |
18 | +import 'brace/ext/language_tools'; | |
19 | +import 'brace/mode/json'; | |
20 | +import 'ace-builds/src-min-noconflict/snippets/json'; | |
21 | + | |
22 | +/* eslint-disable import/no-unresolved, import/default */ | |
23 | + | |
24 | +import jsonObjectEditTemplate from './json-object-edit.tpl.html'; | |
25 | + | |
26 | +/* eslint-enable import/no-unresolved, import/default */ | |
27 | + | |
28 | +export default angular.module('thingsboard.directives.jsonObjectEdit', []) | |
29 | + .directive('tbJsonObjectEdit', JsonObjectEdit) | |
30 | + .name; | |
31 | + | |
32 | +/*@ngInject*/ | |
33 | +function JsonObjectEdit($compile, $templateCache, toast, utils) { | |
34 | + | |
35 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
36 | + var template = $templateCache.get(jsonObjectEditTemplate); | |
37 | + element.html(template); | |
38 | + | |
39 | + scope.label = attrs.label; | |
40 | + | |
41 | + scope.objectValid = true; | |
42 | + scope.validationError = ''; | |
43 | + | |
44 | + scope.json_editor; | |
45 | + | |
46 | + scope.onFullscreenChanged = function () { | |
47 | + updateEditorSize(); | |
48 | + }; | |
49 | + | |
50 | + function updateEditorSize() { | |
51 | + if (scope.json_editor) { | |
52 | + scope.json_editor.resize(); | |
53 | + scope.json_editor.renderer.updateFull(); | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + scope.jsonEditorOptions = { | |
58 | + useWrapMode: true, | |
59 | + mode: 'json', | |
60 | + advanced: { | |
61 | + enableSnippets: true, | |
62 | + enableBasicAutocompletion: true, | |
63 | + enableLiveAutocompletion: true | |
64 | + }, | |
65 | + onLoad: function (_ace) { | |
66 | + scope.json_editor = _ace; | |
67 | + scope.json_editor.session.on("change", function () { | |
68 | + scope.cleanupJsonErrors(); | |
69 | + }); | |
70 | + } | |
71 | + }; | |
72 | + | |
73 | + scope.cleanupJsonErrors = function () { | |
74 | + toast.hide(); | |
75 | + }; | |
76 | + | |
77 | + scope.updateValidity = function () { | |
78 | + ngModelCtrl.$setValidity('objectValid', scope.objectValid); | |
79 | + }; | |
80 | + | |
81 | + scope.$watch('contentBody', function (newVal, prevVal) { | |
82 | + if (!angular.equals(newVal, prevVal)) { | |
83 | + var object = scope.validate(); | |
84 | + ngModelCtrl.$setViewValue(object); | |
85 | + scope.updateValidity(); | |
86 | + } | |
87 | + }); | |
88 | + | |
89 | + ngModelCtrl.$render = function () { | |
90 | + var object = ngModelCtrl.$viewValue; | |
91 | + var content = ''; | |
92 | + try { | |
93 | + if (object) { | |
94 | + content = angular.toJson(object, true); | |
95 | + } | |
96 | + } catch (e) { | |
97 | + // | |
98 | + } | |
99 | + scope.contentBody = content; | |
100 | + }; | |
101 | + | |
102 | + scope.showError = function (error) { | |
103 | + var toastParent = angular.element('#tb-json-panel', element); | |
104 | + toast.showError(error, toastParent, 'bottom left'); | |
105 | + }; | |
106 | + | |
107 | + scope.validate = function () { | |
108 | + if (!scope.contentBody || !scope.contentBody.length) { | |
109 | + if (scope.required) { | |
110 | + scope.validationError = 'Json object is required.'; | |
111 | + scope.objectValid = false; | |
112 | + } else { | |
113 | + scope.validationError = ''; | |
114 | + scope.objectValid = true; | |
115 | + } | |
116 | + return null; | |
117 | + } else { | |
118 | + try { | |
119 | + var object = angular.fromJson(scope.contentBody); | |
120 | + scope.validationError = ''; | |
121 | + scope.objectValid = true; | |
122 | + return object; | |
123 | + } catch (e) { | |
124 | + var details = utils.parseException(e); | |
125 | + var errorInfo = 'Error:'; | |
126 | + if (details.name) { | |
127 | + errorInfo += ' ' + details.name + ':'; | |
128 | + } | |
129 | + if (details.message) { | |
130 | + errorInfo += ' ' + details.message; | |
131 | + } | |
132 | + scope.validationError = errorInfo; | |
133 | + scope.objectValid = false; | |
134 | + return null; | |
135 | + } | |
136 | + } | |
137 | + }; | |
138 | + | |
139 | + scope.$on('form-submit', function () { | |
140 | + if (!scope.readonly) { | |
141 | + scope.cleanupJsonErrors(); | |
142 | + if (!scope.objectValid) { | |
143 | + scope.showError(scope.validationError); | |
144 | + } | |
145 | + } | |
146 | + }); | |
147 | + | |
148 | + scope.$on('update-ace-editor-size', function () { | |
149 | + updateEditorSize(); | |
150 | + }); | |
151 | + | |
152 | + $compile(element.contents())(scope); | |
153 | + } | |
154 | + | |
155 | + return { | |
156 | + restrict: "E", | |
157 | + require: "^ngModel", | |
158 | + scope: { | |
159 | + required:'=ngRequired', | |
160 | + readonly:'=ngReadonly', | |
161 | + fillHeight:'=?' | |
162 | + }, | |
163 | + link: linker | |
164 | + }; | |
165 | +} | ... | ... |
ui/src/app/components/json-object-edit.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 | +tb-json-object-edit { | |
17 | + position: relative; | |
18 | + .fill-height { | |
19 | + height: 100%; | |
20 | + } | |
21 | +} | |
22 | + | |
23 | +.tb-json-object-panel { | |
24 | + margin-left: 15px; | |
25 | + border: 1px solid #C0C0C0; | |
26 | + height: 100%; | |
27 | + #tb-json-input { | |
28 | + min-width: 200px; | |
29 | + width: 100%; | |
30 | + height: 100%; | |
31 | + &:not(.fill-height) { | |
32 | + min-height: 200px; | |
33 | + } | |
34 | + } | |
35 | +} | ... | ... |
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 style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> | |
19 | + <div layout="row" layout-align="start center"> | |
20 | + <label class="tb-title no-padding" | |
21 | + ng-class="{'tb-required': required, | |
22 | + 'tb-readonly': readonly, | |
23 | + 'tb-error': !objectValid}">{{ label }}</label> | |
24 | + <span flex></span> | |
25 | + <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button> | |
26 | + </div> | |
27 | + <div flex id="tb-json-panel" class="tb-json-object-panel" layout="column"> | |
28 | + <div flex id="tb-json-input" ng-class="{'fill-height': fillHeight}" | |
29 | + ng-readonly="readonly" | |
30 | + ui-ace="jsonEditorOptions" | |
31 | + ng-model="contentBody"> | |
32 | + </div> | |
33 | + </div> | |
34 | +</div> | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive'; |
29 | 29 | import thingsboardOnFinishRender from '../components/finish-render.directive'; |
30 | 30 | import thingsboardSideMenu from '../components/side-menu.directive'; |
31 | 31 | import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; |
32 | +import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; | |
32 | 33 | |
33 | 34 | import thingsboardUserMenu from './user-menu.directive'; |
34 | 35 | |
... | ... | @@ -90,7 +91,8 @@ export default angular.module('thingsboard.home', [ |
90 | 91 | thingsboardNoAnimate, |
91 | 92 | thingsboardOnFinishRender, |
92 | 93 | thingsboardSideMenu, |
93 | - thingsboardDashboardAutocomplete | |
94 | + thingsboardDashboardAutocomplete, | |
95 | + thingsboardJsonObjectEdit | |
94 | 96 | ]) |
95 | 97 | .config(HomeRoutes) |
96 | 98 | .controller('HomeController', HomeController) | ... | ... |
... | ... | @@ -1179,6 +1179,7 @@ export default angular.module('thingsboard.locale', []) |
1179 | 1179 | "delete": "Delete rule node", |
1180 | 1180 | "rulenode-details": "Rule node details", |
1181 | 1181 | "debug-mode": "Debug mode", |
1182 | + "configuration": "Configuration", | |
1182 | 1183 | "link-details": "Rule node link details", |
1183 | 1184 | "add-link": "Add link", |
1184 | 1185 | "link-label": "Link label", | ... | ... |
... | ... | @@ -151,6 +151,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
151 | 151 | }, |
152 | 152 | 'mouseLeave': function () { |
153 | 153 | destroyTooltips(); |
154 | + }, | |
155 | + 'mouseDown': function () { | |
156 | + destroyTooltips(); | |
154 | 157 | } |
155 | 158 | } |
156 | 159 | }; |
... | ... | @@ -226,16 +229,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
226 | 229 | edgeDoubleClick: function (event, edge) { |
227 | 230 | var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); |
228 | 231 | if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { |
229 | - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( | |
230 | - (labels) => { | |
231 | - vm.isEditingRuleNode = false; | |
232 | - vm.editingRuleNode = null; | |
233 | - vm.editingRuleNodeLinkLabels = labels; | |
234 | - vm.isEditingRuleNodeLink = true; | |
235 | - vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); | |
236 | - vm.editingRuleNodeLink = angular.copy(edge); | |
237 | - } | |
238 | - ); | |
232 | + vm.isEditingRuleNode = false; | |
233 | + vm.editingRuleNode = null; | |
234 | + vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); | |
235 | + vm.isEditingRuleNodeLink = true; | |
236 | + vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); | |
237 | + vm.editingRuleNodeLink = angular.copy(edge); | |
239 | 238 | } |
240 | 239 | }, |
241 | 240 | nodeCallbacks: { |
... | ... | @@ -267,16 +266,10 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
267 | 266 | deferred.resolve(edge); |
268 | 267 | } |
269 | 268 | } else { |
270 | - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( | |
271 | - (labels) => { | |
272 | - addRuleNodeLink(event, edge, labels).then( | |
273 | - (link) => { | |
274 | - deferred.resolve(link); | |
275 | - }, | |
276 | - () => { | |
277 | - deferred.reject(); | |
278 | - } | |
279 | - ); | |
269 | + var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); | |
270 | + addRuleNodeLink(event, edge, labels).then( | |
271 | + (link) => { | |
272 | + deferred.resolve(link); | |
280 | 273 | }, |
281 | 274 | () => { |
282 | 275 | deferred.reject(); |
... | ... | @@ -309,24 +302,19 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
309 | 302 | y: 10+50*model.nodes.length, |
310 | 303 | connectors: [] |
311 | 304 | }; |
312 | - if (componentType == types.ruleNodeType.RULE_CHAIN.value) { | |
313 | - node.connectors.push( | |
314 | - { | |
315 | - type: flowchartConstants.leftConnectorType, | |
316 | - id: model.nodes.length | |
317 | - } | |
318 | - ); | |
319 | - } else { | |
305 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { | |
320 | 306 | node.connectors.push( |
321 | 307 | { |
322 | 308 | type: flowchartConstants.leftConnectorType, |
323 | - id: model.nodes.length*2 | |
309 | + id: model.nodes.length * 2 | |
324 | 310 | } |
325 | 311 | ); |
312 | + } | |
313 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { | |
326 | 314 | node.connectors.push( |
327 | 315 | { |
328 | 316 | type: flowchartConstants.rightConnectorType, |
329 | - id: model.nodes.length*2+1 | |
317 | + id: model.nodes.length * 2 + 1 | |
330 | 318 | } |
331 | 319 | ); |
332 | 320 | } |
... | ... | @@ -398,17 +386,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
398 | 386 | name: ruleNode.name, |
399 | 387 | nodeClass: vm.types.ruleNodeType[component.type].nodeClass, |
400 | 388 | icon: vm.types.ruleNodeType[component.type].icon, |
401 | - connectors: [ | |
389 | + connectors: [] | |
390 | + }; | |
391 | + if (component.configurationDescriptor.nodeDefinition.inEnabled) { | |
392 | + node.connectors.push( | |
402 | 393 | { |
403 | 394 | type: flowchartConstants.leftConnectorType, |
404 | 395 | id: vm.nextConnectorID++ |
405 | - }, | |
396 | + } | |
397 | + ); | |
398 | + } | |
399 | + if (component.configurationDescriptor.nodeDefinition.outEnabled) { | |
400 | + node.connectors.push( | |
406 | 401 | { |
407 | 402 | type: flowchartConstants.rightConnectorType, |
408 | 403 | id: vm.nextConnectorID++ |
409 | 404 | } |
410 | - ] | |
411 | - }; | |
405 | + ); | |
406 | + } | |
412 | 407 | nodes.push(node); |
413 | 408 | vm.ruleChainModel.nodes.push(node); |
414 | 409 | } |
... | ... | @@ -590,6 +585,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
590 | 585 | } |
591 | 586 | |
592 | 587 | function addRuleNode($event, ruleNode) { |
588 | + | |
589 | + ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); | |
590 | + | |
593 | 591 | $mdDialog.show({ |
594 | 592 | controller: 'AddRuleNodeController', |
595 | 593 | controllerAs: 'vm', |
... | ... | @@ -601,13 +599,15 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
601 | 599 | }).then(function (ruleNode) { |
602 | 600 | ruleNode.id = vm.nextNodeID++; |
603 | 601 | ruleNode.connectors = []; |
604 | - ruleNode.connectors.push( | |
605 | - { | |
606 | - id: vm.nextConnectorID++, | |
607 | - type: flowchartConstants.leftConnectorType | |
608 | - } | |
609 | - ); | |
610 | - if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) { | |
602 | + if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { | |
603 | + ruleNode.connectors.push( | |
604 | + { | |
605 | + id: vm.nextConnectorID++, | |
606 | + type: flowchartConstants.leftConnectorType | |
607 | + } | |
608 | + ); | |
609 | + } | |
610 | + if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) { | |
611 | 611 | ruleNode.connectors.push( |
612 | 612 | { |
613 | 613 | id: vm.nextConnectorID++, | ... | ... |
... | ... | @@ -38,6 +38,11 @@ |
38 | 38 | ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} |
39 | 39 | </md-checkbox> |
40 | 40 | </md-input-container> |
41 | + <tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration" | |
42 | + label="{{ 'rulenode.configuration' | translate }}" | |
43 | + ng-required="true" | |
44 | + fill-height="true"> | |
45 | + </tb-json-object-edit> | |
41 | 46 | <md-input-container class="md-block"> |
42 | 47 | <label translate>rulenode.description</label> |
43 | 48 | <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> | ... | ... |
ui/src/app/rulechain/rulenode.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-rulenode { | |
18 | + tb-json-object-edit.tb-rule-node-configuration-json { | |
19 | + height: 300px; | |
20 | + display: block; | |
21 | + } | |
22 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | id="{{node.id}}" |
20 | 20 | ng-attr-style="position: absolute; top: {{ node.y }}px; left: {{ node.x }}px;" |
21 | 21 | ng-dblclick="callbacks.doubleClick($event, node)" |
22 | - ng-mouseover="callbacks.mouseOver($event, node)" | |
22 | + ng-mousedown="callbacks.mouseDown($event, node)" | |
23 | 23 | ng-mouseenter="callbacks.mouseEnter($event, node)" |
24 | 24 | ng-mouseleave="callbacks.mouseLeave($event, node)"> |
25 | 25 | <div class="tb-rule-node {{node.nodeClass}}"> | ... | ... |
... | ... | @@ -203,6 +203,12 @@ md-sidenav { |
203 | 203 | * THINGSBOARD SPECIFIC |
204 | 204 | ***********************/ |
205 | 205 | |
206 | +$swift-ease-out-duration: 0.4s !default; | |
207 | +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; | |
208 | + | |
209 | +$input-label-float-offset: 6px !default; | |
210 | +$input-label-float-scale: 0.75 !default; | |
211 | + | |
206 | 212 | label { |
207 | 213 | &.tb-title { |
208 | 214 | pointer-events: none; |
... | ... | @@ -213,6 +219,18 @@ label { |
213 | 219 | &.no-padding { |
214 | 220 | padding-bottom: 0px; |
215 | 221 | } |
222 | + &.tb-required:after { | |
223 | + content: ' *'; | |
224 | + font-size: 13px; | |
225 | + vertical-align: top; | |
226 | + color: rgba(0,0,0,0.54); | |
227 | + } | |
228 | + &.tb-error { | |
229 | + color: rgb(221,44,0); | |
230 | + &.tb-required:after { | |
231 | + color: rgb(221,44,0); | |
232 | + } | |
233 | + } | |
216 | 234 | } |
217 | 235 | } |
218 | 236 | ... | ... |