Rule Node Configuration
This commit is contained in:
parent
73473e3beb
commit
adb71ecfe7
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -234,7 +234,7 @@ caffeine:
|
||||
specs:
|
||||
relations:
|
||||
timeToLiveInMinutes: 1440
|
||||
maxSize: 100000
|
||||
maxSize: 0
|
||||
deviceCredentials:
|
||||
timeToLiveInMinutes: 1440
|
||||
maxSize: 100000
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
{
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
{
|
||||
}
|
||||
@ -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." +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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. " +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.")
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 " +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 " +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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. " +
|
||||
|
||||
@ -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 " +
|
||||
|
||||
@ -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. ")
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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/>" +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
Normal file
165
ui/src/app/components/json-object-edit.directive.js
Normal file
@ -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
Normal file
35
ui/src/app/components/json-object-edit.scss
Normal file
@ -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
Normal file
34
ui/src/app/components/json-object-edit.tpl.html
Normal file
@ -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>
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -151,6 +151,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
|
||||
},
|
||||
'mouseLeave': function () {
|
||||
destroyTooltips();
|
||||
},
|
||||
'mouseDown': function () {
|
||||
destroyTooltips();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -226,17 +229,13 @@ 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.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
|
||||
vm.isEditingRuleNodeLink = true;
|
||||
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
|
||||
vm.editingRuleNodeLink = angular.copy(edge);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
nodeCallbacks: {
|
||||
'doubleClick': function (event, node) {
|
||||
@ -267,8 +266,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
|
||||
deferred.resolve(edge);
|
||||
}
|
||||
} else {
|
||||
ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then(
|
||||
(labels) => {
|
||||
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
|
||||
addRuleNodeLink(event, edge, labels).then(
|
||||
(link) => {
|
||||
deferred.resolve(link);
|
||||
@ -277,11 +275,6 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
|
||||
deferred.reject();
|
||||
}
|
||||
);
|
||||
},
|
||||
() => {
|
||||
deferred.reject();
|
||||
}
|
||||
);
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
@ -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) {
|
||||
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
|
||||
node.connectors.push(
|
||||
{
|
||||
type: flowchartConstants.leftConnectorType,
|
||||
id: model.nodes.length
|
||||
id: model.nodes.length * 2
|
||||
}
|
||||
);
|
||||
} else {
|
||||
node.connectors.push(
|
||||
{
|
||||
type: flowchartConstants.leftConnectorType,
|
||||
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 = [];
|
||||
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
|
||||
ruleNode.connectors.push(
|
||||
{
|
||||
id: vm.nextConnectorID++,
|
||||
type: flowchartConstants.leftConnectorType
|
||||
}
|
||||
);
|
||||
if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) {
|
||||
}
|
||||
if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) {
|
||||
ruleNode.connectors.push(
|
||||
{
|
||||
id: vm.nextConnectorID++,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
Normal file
22
ui/src/app/rulechain/rulenode.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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}}">
|
||||
|
||||
@ -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…
x
Reference in New Issue
Block a user