Browse Source

Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5

pull/725/head
Andrew Shvayka 8 years ago
parent
commit
4ecb106a56
  1. 9
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  2. 22
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
  3. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  4. 2
      rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json
  5. 2
      rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json
  6. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
  7. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
  8. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
  9. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
  10. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
  11. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
  12. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
  13. 13
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java
  14. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  15. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java
  16. 19
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java
  17. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  18. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  19. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  20. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java
  21. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  22. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
  23. 25
      ui/src/app/api/rule-chain.service.js
  24. 45
      ui/src/app/components/ace-editor-fix.js
  25. 3
      ui/src/app/components/js-func.directive.js
  26. 168
      ui/src/app/components/json-object-edit.directive.js
  27. 35
      ui/src/app/components/json-object-edit.scss
  28. 34
      ui/src/app/components/json-object-edit.tpl.html
  29. 8
      ui/src/app/components/react/json-form-ace-editor.jsx
  30. 5
      ui/src/app/components/widget/widget-config.directive.js
  31. 4
      ui/src/app/layout/index.js
  32. 1
      ui/src/app/locale/locale.constant.js
  33. 82
      ui/src/app/rulechain/rulechain.controller.js
  34. 5
      ui/src/app/rulechain/rulenode-fieldset.tpl.html
  35. 2
      ui/src/app/rulechain/rulenode.directive.js
  36. 22
      ui/src/app/rulechain/rulenode.scss
  37. 2
      ui/src/app/rulechain/rulenode.tpl.html
  38. 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;
}

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() {

45
ui/src/app/components/ace-editor-fix.js

@ -0,0 +1,45 @@
/*
* 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.
*/
export default function fixAceEditor(aceEditor) {
aceEditor.$blockScrolling = Infinity;
aceEditor.on("showGutterTooltip", function (tooltip) {
if (!tooltip.isAttachedToBody) {
document.body.appendChild(tooltip.$element); //eslint-disable-line
tooltip.isAttachedToBody = true;
onElementRemoved(tooltip.$parentNode, () => {
if (tooltip.$element.parentNode != null) {
tooltip.$element.parentNode.removeChild(tooltip.$element);
}
});
}
});
}
function onElementRemoved(element, callback) {
if (!document.body.contains(element)) { //eslint-disable-line
callback();
} else {
var observer;
observer = new MutationObserver(function(mutations) { //eslint-disable-line
if (!document.body.contains(element)) { //eslint-disable-line
callback();
observer.disconnect();
}
});
observer.observe(document.body, {childList: true}); //eslint-disable-line
}
}

3
ui/src/app/components/js-func.directive.js

@ -22,6 +22,8 @@ import thingsboardToast from '../services/toast';
import thingsboardUtils from '../common/utils.service';
import thingsboardExpandFullscreen from './expand-fullscreen.directive';
import fixAceEditor from './ace-editor-fix';
/* eslint-disable import/no-unresolved, import/default */
import jsFuncTemplate from './js-func.tpl.html';
@ -83,6 +85,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.js_editor.session.on("change", function () {
scope.cleanupJsErrors();
});
fixAceEditor(_ace);
}
};

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

@ -0,0 +1,168 @@
/*
* 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';
import fixAceEditor from './ace-editor-fix';
/* 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, $document, 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();
});
fixAceEditor(_ace);
}
};
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>

8
ui/src/app/components/react/json-form-ace-editor.jsx

@ -23,6 +23,8 @@ import FlatButton from 'material-ui/FlatButton';
import 'brace/ext/language_tools';
import 'brace/theme/github';
import fixAceEditor from './../ace-editor-fix';
class ThingsboardAceEditor extends React.Component {
constructor(props) {
@ -31,6 +33,7 @@ class ThingsboardAceEditor extends React.Component {
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onTidy = this.onTidy.bind(this);
this.onLoad = this.onLoad.bind(this);
var value = props.value ? props.value + '' : '';
this.state = {
value: value,
@ -72,6 +75,10 @@ class ThingsboardAceEditor extends React.Component {
}
}
onLoad(editor) {
fixAceEditor(editor);
}
render() {
const styles = reactCSS({
@ -117,6 +124,7 @@ class ThingsboardAceEditor extends React.Component {
onChange={this.onValueChanged}
onFocus={this.onFocus}
onBlur={this.onBlur}
onLoad={this.onLoad}
name={this.props.form.title}
value={this.state.value}
readOnly={this.props.form.readonly}

5
ui/src/app/components/widget/widget-config.directive.js

@ -23,6 +23,8 @@ import thingsboardJsonForm from '../json-form.directive';
import thingsboardManageWidgetActions from './action/manage-widget-actions.directive';
import 'angular-ui-ace';
import fixAceEditor from './../ace-editor-fix';
import './widget-config.scss';
/* eslint-disable import/no-unresolved, import/default */
@ -72,6 +74,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
fixAceEditor(_ace);
}
};

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