Commit adb71ecfe7b2a87e372d207837c769017f49d18a

Authored by Igor Kulikov
1 parent 73473e3b

Rule Node Configuration

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
... ...
... ... @@ -234,7 +234,7 @@ caffeine:
234 234 specs:
235 235 relations:
236 236 timeToLiveInMinutes: 1440
237   - maxSize: 100000
  237 + maxSize: 0
238 238 deviceCredentials:
239 239 timeToLiveInMinutes: 1440
240 240 maxSize: 100000
... ...
  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 }
... ...
... ... @@ -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 +}
... ...
  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>
... ...
... ... @@ -14,6 +14,8 @@
14 14 * limitations under the License.
15 15 */
16 16
  17 +import './rulenode.scss';
  18 +
17 19 /* eslint-disable import/no-unresolved, import/default */
18 20
19 21 import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html';
... ...
  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
... ...