Browse Source

Rule Node Configuration

pull/725/head
Igor Kulikov 8 years ago
parent
commit
adb71ecfe7
  1. 9
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  2. 2
      application/src/main/resources/thingsboard.yml
  3. 22
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
  4. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  5. 2
      rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json
  6. 2
      rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json
  7. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
  8. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
  9. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
  10. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
  11. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
  12. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
  13. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
  14. 13
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java
  15. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  16. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java
  17. 19
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java
  18. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  19. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  20. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  21. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java
  22. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  23. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
  24. 25
      ui/src/app/api/rule-chain.service.js
  25. 165
      ui/src/app/components/json-object-edit.directive.js
  26. 35
      ui/src/app/components/json-object-edit.scss
  27. 34
      ui/src/app/components/json-object-edit.tpl.html
  28. 4
      ui/src/app/layout/index.js
  29. 1
      ui/src/app/locale/locale.constant.js
  30. 82
      ui/src/app/rulechain/rulechain.controller.js
  31. 5
      ui/src/app/rulechain/rulenode-fieldset.tpl.html
  32. 2
      ui/src/app/rulechain/rulenode.directive.js
  33. 22
      ui/src/app/rulechain/rulenode.scss
  34. 2
      ui/src/app/rulechain/rulenode.tpl.html
  35. 18
      ui/src/scss/main.scss

9
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -180,7 +180,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
return scannedComponent;
}
private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException {
private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception {
NodeDefinition nodeDefinition = new NodeDefinition();
nodeDefinition.setDetails(nodeAnnotation.nodeDetails());
nodeDefinition.setDescription(nodeAnnotation.nodeDescription());
@ -188,9 +188,10 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes());
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
String defaultConfigResourceName = nodeAnnotation.defaultConfigResource();
nodeDefinition.setDefaultConfiguration(mapper.readTree(
Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8)));
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration();
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
return nodeDefinition;
}

2
application/src/main/resources/thingsboard.yml

@ -234,7 +234,7 @@ caffeine:
specs:
relations:
timeToLiveInMinutes: 1440
maxSize: 100000
maxSize: 0
deviceCredentials:
timeToLiveInMinutes: 1440
maxSize: 100000

22
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
public interface NodeConfiguration {
NodeConfiguration defaultConfiguration();
}

5
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -35,15 +35,16 @@ public @interface RuleNode {
String nodeDetails();
Class<? extends NodeConfiguration> configClazz();
boolean inEnabled() default true;
boolean outEnabled() default true;
ComponentScope scope() default ComponentScope.TENANT;
String defaultConfigResource() default "EmptyNodeConfig.json";
String[] relationTypes() default {"Success", "Failure"};
boolean customRelations() default false;
}

2
rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json

@ -1,2 +0,0 @@
{
}

2
rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json

@ -1,2 +0,0 @@
{
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java

@ -30,6 +30,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@RuleNode(
type = ComponentType.FILTER,
name = "script", relationTypes = {"True", "False", "Failure"},
configClazz = TbJsFilterNodeConfiguration.class,
nodeDescription = "Filter incoming messages using JS script",
nodeDetails = "Evaluate incoming Message with configured JS condition. " +
"If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java

@ -16,9 +16,17 @@
package org.thingsboard.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data
public class TbJsFilterNodeConfiguration {
public class TbJsFilterNodeConfiguration implements NodeConfiguration {
private String jsScript;
@Override
public TbJsFilterNodeConfiguration defaultConfiguration() {
TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;");
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java

@ -31,6 +31,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@RuleNode(
type = ComponentType.FILTER,
name = "switch", customRelations = true,
configClazz = TbJsSwitchNodeConfiguration.class,
nodeDescription = "Route incoming Message to one or multiple output chains",
nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +
"If Array is empty - message not routed to next Node. " +

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java

@ -15,14 +15,29 @@
*/
package org.thingsboard.rule.engine.filter;
import com.google.common.collect.Sets;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.Set;
@Data
public class TbJsSwitchNodeConfiguration {
public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
private String jsScript;
private Set<String> allowedRelations;
private boolean routeToAllWithNoCheck;
@Override
public TbJsSwitchNodeConfiguration defaultConfiguration() {
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
configuration.setJsScript("function nextRelation(meta, msg) {\n" +
" return ['one','nine'];" +
"};\n" +
"\n" +
"nextRelation(meta, msg);");
configuration.setAllowedRelations(Sets.newHashSet("one", "two"));
configuration.setRouteToAllWithNoCheck(false);
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg;
@RuleNode(
type = ComponentType.FILTER,
name = "message type",
configClazz = TbMsgTypeFilterNodeConfiguration.class,
nodeDescription = "Filter incoming messages by Message Type",
nodeDetails = "Evaluate incoming Message with configured JS condition. " +
"If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.")

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java

@ -16,15 +16,24 @@
package org.thingsboard.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by ashvayka on 19.01.18.
*/
@Data
public class TbMsgTypeFilterNodeConfiguration {
public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration {
private List<String> messageTypes;
@Override
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java

@ -38,6 +38,7 @@ import static org.thingsboard.server.common.data.DataConstants.*;
@Slf4j
@RuleNode(type = ComponentType.ENRICHMENT,
name = "originator attributes",
configClazz = TbGetAttributesNodeConfiguration.class,
nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
"with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +

13
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java

@ -16,14 +16,16 @@
package org.thingsboard.rule.engine.metadata;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.Collections;
import java.util.List;
/**
* Created by ashvayka on 19.01.18.
*/
@Data
public class TbGetAttributesNodeConfiguration {
public class TbGetAttributesNodeConfiguration implements NodeConfiguration {
private List<String> clientAttributeNames;
private List<String> sharedAttributeNames;
@ -31,4 +33,13 @@ public class TbGetAttributesNodeConfiguration {
private List<String> latestTsKeyNames;
@Override
public TbGetAttributesNodeConfiguration defaultConfiguration() {
TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration();
configuration.setClientAttributeNames(Collections.emptyList());
configuration.setSharedAttributeNames(Collections.emptyList());
configuration.setServerAttributeNames(Collections.emptyList());
configuration.setLatestTsKeyNames(Collections.emptyList());
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
@RuleNode(
type = ComponentType.ENRICHMENT,
name="customer attributes",
configClazz = TbGetEntityAttrNodeConfiguration.class,
nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"To access those attributes in other nodes this template can be used " +

14
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java

@ -16,13 +16,25 @@
package org.thingsboard.rule.engine.metadata;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Data
public class TbGetEntityAttrNodeConfiguration {
public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration {
private Map<String, String> attrMapping;
private boolean isTelemetry = false;
@Override
public TbGetEntityAttrNodeConfiguration defaultConfiguration() {
TbGetEntityAttrNodeConfiguration configuration = new TbGetEntityAttrNodeConfiguration();
Map<String, String> attrMapping = new HashMap<>();
attrMapping.putIfAbsent("temperature", "tempo");
configuration.setAttrMapping(attrMapping);
configuration.setTelemetry(true);
return configuration;
}
}

19
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java

@ -16,11 +16,28 @@
package org.thingsboard.rule.engine.metadata;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import java.util.HashMap;
import java.util.Map;
@Data
public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration {
public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration {
private String relationType;
private EntitySearchDirection direction;
@Override
public TbGetRelatedAttrNodeConfiguration defaultConfiguration() {
TbGetRelatedAttrNodeConfiguration configuration = new TbGetRelatedAttrNodeConfiguration();
Map<String, String> attrMapping = new HashMap<>();
attrMapping.putIfAbsent("temperature", "tempo");
configuration.setAttrMapping(attrMapping);
configuration.setTelemetry(true);
configuration.setRelationType(EntityRelation.CONTAINS_TYPE);
configuration.setDirection(EntitySearchDirection.FROM);
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
@RuleNode(
type = ComponentType.ENRICHMENT,
name="related attributes",
configClazz = TbGetRelatedAttrNodeConfiguration.class,
nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
@RuleNode(
type = ComponentType.ENRICHMENT,
name="tenant attributes",
configClazz = TbGetEntityAttrNodeConfiguration.class,
nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"To access those attributes in other nodes this template can be used " +

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -36,6 +36,7 @@ import java.util.HashSet;
@RuleNode(
type = ComponentType.TRANSFORMATION,
name="change originator",
configClazz = TbChangeOriginatorNodeConfiguration.class,
nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity",
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ")

14
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java

@ -16,12 +16,24 @@
package org.thingsboard.rule.engine.transform;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
@Data
public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{
public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration {
private String originatorSource;
private EntitySearchDirection direction;
private String relationType;
@Override
public TbChangeOriginatorNodeConfiguration defaultConfiguration() {
TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration();
configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE);
configuration.setDirection(EntitySearchDirection.FROM);
configuration.setRelationType(EntityRelation.CONTAINS_TYPE);
configuration.setStartNewChain(false);
return configuration;
}
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java

@ -27,6 +27,7 @@ import javax.script.Bindings;
@RuleNode(
type = ComponentType.TRANSFORMATION,
name = "script",
configClazz = TbTransformMsgNodeConfiguration.class,
nodeDescription = "Change Message payload and Metadata using JavaScript",
nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " +
"<code>meta</code> - is a Message metadata.<br/>" +

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java

@ -16,9 +16,18 @@
package org.thingsboard.rule.engine.transform;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data
public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration {
public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration {
private String jsScript;
@Override
public TbTransformMsgNodeConfiguration defaultConfiguration() {
TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
configuration.setStartNewChain(false);
configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
return configuration;
}
}

25
ui/src/app/api/rule-chain.service.js

@ -153,16 +153,21 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
return deferred.promise;
}
function getRuleNodeSupportedLinks(nodeType) { //eslint-disable-line
//TODO:
var deferred = $q.defer();
var linkLabels = [
{ name: 'Success', custom: false },
{ name: 'Fail', custom: false },
{ name: 'Custom', custom: true },
];
deferred.resolve(linkLabels);
return deferred.promise;
function getRuleNodeSupportedLinks(component) {
var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
var customRelations = component.configurationDescriptor.nodeDefinition.customRelations;
var linkLabels = [];
for (var i=0;i<relationTypes.length;i++) {
linkLabels.push({
name: relationTypes[i], custom: false
});
}
if (customRelations) {
linkLabels.push(
{ name: 'Custom', custom: true }
);
}
return linkLabels;
}
function getRuleNodeComponents() {

165
ui/src/app/components/json-object-edit.directive.js

@ -0,0 +1,165 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './json-object-edit.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'ace-builds/src-min-noconflict/snippets/json';
/* eslint-disable import/no-unresolved, import/default */
import jsonObjectEditTemplate from './json-object-edit.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.jsonObjectEdit', [])
.directive('tbJsonObjectEdit', JsonObjectEdit)
.name;
/*@ngInject*/
function JsonObjectEdit($compile, $templateCache, toast, utils) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(jsonObjectEditTemplate);
element.html(template);
scope.label = attrs.label;
scope.objectValid = true;
scope.validationError = '';
scope.json_editor;
scope.onFullscreenChanged = function () {
updateEditorSize();
};
function updateEditorSize() {
if (scope.json_editor) {
scope.json_editor.resize();
scope.json_editor.renderer.updateFull();
}
}
scope.jsonEditorOptions = {
useWrapMode: true,
mode: 'json',
advanced: {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
scope.json_editor = _ace;
scope.json_editor.session.on("change", function () {
scope.cleanupJsonErrors();
});
}
};
scope.cleanupJsonErrors = function () {
toast.hide();
};
scope.updateValidity = function () {
ngModelCtrl.$setValidity('objectValid', scope.objectValid);
};
scope.$watch('contentBody', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
var object = scope.validate();
ngModelCtrl.$setViewValue(object);
scope.updateValidity();
}
});
ngModelCtrl.$render = function () {
var object = ngModelCtrl.$viewValue;
var content = '';
try {
if (object) {
content = angular.toJson(object, true);
}
} catch (e) {
//
}
scope.contentBody = content;
};
scope.showError = function (error) {
var toastParent = angular.element('#tb-json-panel', element);
toast.showError(error, toastParent, 'bottom left');
};
scope.validate = function () {
if (!scope.contentBody || !scope.contentBody.length) {
if (scope.required) {
scope.validationError = 'Json object is required.';
scope.objectValid = false;
} else {
scope.validationError = '';
scope.objectValid = true;
}
return null;
} else {
try {
var object = angular.fromJson(scope.contentBody);
scope.validationError = '';
scope.objectValid = true;
return object;
} catch (e) {
var details = utils.parseException(e);
var errorInfo = 'Error:';
if (details.name) {
errorInfo += ' ' + details.name + ':';
}
if (details.message) {
errorInfo += ' ' + details.message;
}
scope.validationError = errorInfo;
scope.objectValid = false;
return null;
}
}
};
scope.$on('form-submit', function () {
if (!scope.readonly) {
scope.cleanupJsonErrors();
if (!scope.objectValid) {
scope.showError(scope.validationError);
}
}
});
scope.$on('update-ace-editor-size', function () {
updateEditorSize();
});
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
scope: {
required:'=ngRequired',
readonly:'=ngReadonly',
fillHeight:'=?'
},
link: linker
};
}

35
ui/src/app/components/json-object-edit.scss

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-json-object-edit {
position: relative;
.fill-height {
height: 100%;
}
}
.tb-json-object-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
#tb-json-input {
min-width: 200px;
width: 100%;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
}
}

34
ui/src/app/components/json-object-edit.tpl.html

@ -0,0 +1,34 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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">
<div layout="row" layout-align="start center">
<label class="tb-title no-padding"
ng-class="{'tb-required': required,
'tb-readonly': readonly,
'tb-error': !objectValid}">{{ label }}</label>
<span flex></span>
<md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
</div>
<div flex id="tb-json-panel" class="tb-json-object-panel" layout="column">
<div flex id="tb-json-input" ng-class="{'fill-height': fillHeight}"
ng-readonly="readonly"
ui-ace="jsonEditorOptions"
ng-model="contentBody">
</div>
</div>
</div>

4
ui/src/app/layout/index.js

@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive';
import thingsboardOnFinishRender from '../components/finish-render.directive';
import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
import thingsboardUserMenu from './user-menu.directive';
@ -90,7 +91,8 @@ export default angular.module('thingsboard.home', [
thingsboardNoAnimate,
thingsboardOnFinishRender,
thingsboardSideMenu,
thingsboardDashboardAutocomplete
thingsboardDashboardAutocomplete,
thingsboardJsonObjectEdit
])
.config(HomeRoutes)
.controller('HomeController', HomeController)

1
ui/src/app/locale/locale.constant.js

@ -1179,6 +1179,7 @@ export default angular.module('thingsboard.locale', [])
"delete": "Delete rule node",
"rulenode-details": "Rule node details",
"debug-mode": "Debug mode",
"configuration": "Configuration",
"link-details": "Rule node link details",
"add-link": "Add link",
"link-label": "Link label",

82
ui/src/app/rulechain/rulechain.controller.js

@ -151,6 +151,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
},
'mouseLeave': function () {
destroyTooltips();
},
'mouseDown': function () {
destroyTooltips();
}
}
};
@ -226,16 +229,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
edgeDoubleClick: function (event, edge) {
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then(
(labels) => {
vm.isEditingRuleNode = false;
vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = labels;
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
}
);
vm.isEditingRuleNode = false;
vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
}
},
nodeCallbacks: {
@ -267,16 +266,10 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
deferred.resolve(edge);
}
} else {
ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then(
(labels) => {
addRuleNodeLink(event, edge, labels).then(
(link) => {
deferred.resolve(link);
},
() => {
deferred.reject();
}
);
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
addRuleNodeLink(event, edge, labels).then(
(link) => {
deferred.resolve(link);
},
() => {
deferred.reject();
@ -309,24 +302,19 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
y: 10+50*model.nodes.length,
connectors: []
};
if (componentType == types.ruleNodeType.RULE_CHAIN.value) {
node.connectors.push(
{
type: flowchartConstants.leftConnectorType,
id: model.nodes.length
}
);
} else {
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
node.connectors.push(
{
type: flowchartConstants.leftConnectorType,
id: model.nodes.length*2
id: model.nodes.length * 2
}
);
}
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {
node.connectors.push(
{
type: flowchartConstants.rightConnectorType,
id: model.nodes.length*2+1
id: model.nodes.length * 2 + 1
}
);
}
@ -398,17 +386,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
name: ruleNode.name,
nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
icon: vm.types.ruleNodeType[component.type].icon,
connectors: [
connectors: []
};
if (component.configurationDescriptor.nodeDefinition.inEnabled) {
node.connectors.push(
{
type: flowchartConstants.leftConnectorType,
id: vm.nextConnectorID++
},
}
);
}
if (component.configurationDescriptor.nodeDefinition.outEnabled) {
node.connectors.push(
{
type: flowchartConstants.rightConnectorType,
id: vm.nextConnectorID++
}
]
};
);
}
nodes.push(node);
vm.ruleChainModel.nodes.push(node);
}
@ -590,6 +585,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}
function addRuleNode($event, ruleNode) {
ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration);
$mdDialog.show({
controller: 'AddRuleNodeController',
controllerAs: 'vm',
@ -601,13 +599,15 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}).then(function (ruleNode) {
ruleNode.id = vm.nextNodeID++;
ruleNode.connectors = [];
ruleNode.connectors.push(
{
id: vm.nextConnectorID++,
type: flowchartConstants.leftConnectorType
}
);
if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) {
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
ruleNode.connectors.push(
{
id: vm.nextConnectorID++,
type: flowchartConstants.leftConnectorType
}
);
}
if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) {
ruleNode.connectors.push(
{
id: vm.nextConnectorID++,

5
ui/src/app/rulechain/rulenode-fieldset.tpl.html

@ -38,6 +38,11 @@
ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
</md-checkbox>
</md-input-container>
<tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
label="{{ 'rulenode.configuration' | translate }}"
ng-required="true"
fill-height="true">
</tb-json-object-edit>
<md-input-container class="md-block">
<label translate>rulenode.description</label>
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>

2
ui/src/app/rulechain/rulenode.directive.js

@ -14,6 +14,8 @@
* limitations under the License.
*/
import './rulenode.scss';
/* eslint-disable import/no-unresolved, import/default */
import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html';

22
ui/src/app/rulechain/rulenode.scss

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-rulenode {
tb-json-object-edit.tb-rule-node-configuration-json {
height: 300px;
display: block;
}
}

2
ui/src/app/rulechain/rulenode.tpl.html

@ -19,7 +19,7 @@
id="{{node.id}}"
ng-attr-style="position: absolute; top: {{ node.y }}px; left: {{ node.x }}px;"
ng-dblclick="callbacks.doubleClick($event, node)"
ng-mouseover="callbacks.mouseOver($event, node)"
ng-mousedown="callbacks.mouseDown($event, node)"
ng-mouseenter="callbacks.mouseEnter($event, node)"
ng-mouseleave="callbacks.mouseLeave($event, node)">
<div class="tb-rule-node {{node.nodeClass}}">

18
ui/src/scss/main.scss

@ -203,6 +203,12 @@ md-sidenav {
* THINGSBOARD SPECIFIC
***********************/
$swift-ease-out-duration: 0.4s !default;
$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
$input-label-float-offset: 6px !default;
$input-label-float-scale: 0.75 !default;
label {
&.tb-title {
pointer-events: none;
@ -213,6 +219,18 @@ label {
&.no-padding {
padding-bottom: 0px;
}
&.tb-required:after {
content: ' *';
font-size: 13px;
vertical-align: top;
color: rgba(0,0,0,0.54);
}
&.tb-error {
color: rgb(221,44,0);
&.tb-required:after {
color: rgb(221,44,0);
}
}
}
}

Loading…
Cancel
Save