thingsboard-memoizeit
Changes
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java 18(+15 -3)
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java 2(+2 -0)
application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java 2(+1 -1)
pom.xml 1(+1 -0)
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java 4(+2 -2)
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java 2(+2 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java 83(+83 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java 40(+40 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java 11(+7 -4)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java 4(+2 -2)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java 29(+6 -23)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java 10(+3 -7)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java 7(+5 -2)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java 4(+2 -2)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java 18(+12 -6)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java 4(+2 -2)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java 4(+2 -2)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java 6(+3 -3)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css 2(+2 -0)
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js 2(+2 -0)
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java 16(+8 -8)
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java 54(+10 -44)
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java 4(+2 -2)
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java 2(+1 -1)
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java 8(+4 -4)
ui/package.json 2(+1 -1)
ui/server.js 20(+20 -0)
ui/src/app/api/rule-chain.service.js 59(+54 -5)
ui/src/app/common/types.constant.js 30(+29 -1)
ui/src/app/components/js-func.directive.js 34(+29 -5)
ui/src/app/components/js-func.scss 10(+9 -1)
ui/src/app/components/js-func.tpl.html 15(+8 -7)
ui/src/app/event/event-row.directive.js 16(+14 -2)
ui/src/app/event/event-table.directive.js 18(+15 -3)
ui/src/app/locale/locale.constant.js 24(+23 -1)
ui/src/app/rulechain/index.js 4(+4 -0)
ui/src/app/rulechain/rulechain.controller.js 197(+118 -79)
ui/src/app/rulechain/rulechain.scss 143(+129 -14)
ui/src/app/rulechain/rulechain.tpl.html 108(+89 -19)
ui/src/app/rulechain/rulenode.scss 6(+6 -0)
ui/src/app/rulechain/rulenode-fieldset.tpl.html 38(+18 -20)
Details
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index a5a20b8..9e02946 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -25,6 +25,7 @@ import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
@@ -60,11 +62,13 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
+import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
+@Slf4j
@Component
public class ActorSystemContext {
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
@@ -292,38 +296,49 @@ public class ActorSystemContext {
}
private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) {
- Event event = new Event();
- event.setTenantId(tenantId);
- event.setEntityId(entityId);
- event.setType(DataConstants.DEBUG);
-
- ObjectNode node = mapper.createObjectNode()
- .put("type", type)
- .put("server", getServerAddress())
- .put("entityId", tbMsg.getOriginator().getId().toString())
- .put("entityName", tbMsg.getOriginator().getEntityType().name())
- .put("msgId", tbMsg.getId().toString())
- .put("msgType", tbMsg.getType())
- .put("dataType", tbMsg.getDataType().name());
-
- ObjectNode mdNode = node.putObject("metadata");
- tbMsg.getMetaData().getData().forEach(mdNode::put);
+ try {
+ Event event = new Event();
+ event.setTenantId(tenantId);
+ event.setEntityId(entityId);
+ event.setType(DataConstants.DEBUG_RULE_NODE);
+
+ String metadata = mapper.writeValueAsString(tbMsg.getMetaData().getData());
+
+ ObjectNode node = mapper.createObjectNode()
+ .put("type", type)
+ .put("server", getServerAddress())
+ .put("entityId", tbMsg.getOriginator().getId().toString())
+ .put("entityName", tbMsg.getOriginator().getEntityType().name())
+ .put("msgId", tbMsg.getId().toString())
+ .put("msgType", tbMsg.getType())
+ .put("dataType", tbMsg.getDataType().name())
+ .put("data", convertToString(tbMsg.getDataType(), tbMsg.getData()))
+ .put("metadata", metadata);
+
+ if (error != null) {
+ node = node.put("error", toString(error));
+ }
+
+ event.setBody(node);
+ eventService.save(event);
+ } catch (IOException ex) {
+ log.warn("Failed to persist rule node debug message", ex);
+ }
+ }
- switch (tbMsg.getDataType()) {
+ private String convertToString(TbMsgDataType messageType, byte[] data) {
+ if (data == null) {
+ return null;
+ }
+ switch (messageType) {
+ case JSON:
+ case TEXT:
+ return new String(data, StandardCharsets.UTF_8);
case BINARY:
- node.put("data", Base64Utils.encodeUrlSafe(tbMsg.getData()));
- break;
+ return Base64Utils.encodeToString(data);
default:
- node.put("data", new String(tbMsg.getData(), StandardCharsets.UTF_8));
- break;
- }
-
- if (error != null) {
- node = node.put("error", toString(error));
+ throw new RuntimeException("Message type: " + messageType + " is not supported!");
}
-
- event.setBody(node);
- eventService.save(event);
}
public static Exception toException(Throwable error) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 012a09f..1d45b61 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -15,9 +15,12 @@
*/
package org.thingsboard.server.actors.ruleChain;
+import akka.actor.ActorContext;
+import akka.actor.ActorRef;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.alarm.AlarmService;
@@ -30,8 +33,10 @@ import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
+import scala.concurrent.duration.Duration;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Created by ashvayka on 19.03.18.
@@ -61,7 +66,12 @@ class DefaultTbContext implements TbContext {
@Override
public void tellSelf(TbMsg msg, long delayMs) {
- throw new RuntimeException("Not Implemented!");
+ //TODO: add persistence layer
+ scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor());
+ }
+
+ private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
+ mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor());
}
@Override
@@ -93,6 +103,11 @@ class DefaultTbContext implements TbContext {
}
@Override
+ public RuleNodeId getSelfId() {
+ return nodeCtx.getSelf().getId();
+ }
+
+ @Override
public void tellNext(TbMsg msg, Set<String> relationTypes) {
relationTypes.forEach(type -> tellNext(msg, type));
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
index 268c597..f7ca0d8 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
@@ -47,12 +47,25 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
case RULE_TO_SELF_ERROR_MSG:
onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg);
break;
+ case RULE_TO_SELF_MSG:
+ onRuleNodeToSelfMsg((RuleNodeToSelfMsg) msg);
+ break;
default:
return false;
}
return true;
}
+ private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) {
+ logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
+ try {
+ processor.onRuleToSelfMsg(msg);
+ increaseMessagesProcessedCount();
+ } catch (Exception e) {
+ logAndPersist("onRuleMsg", e);
+ }
+ }
+
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
index a86e2b9..a08dc34 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
@@ -18,9 +18,10 @@ package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
+import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
-import org.thingsboard.rule.engine.api.TbNodeState;
+import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.RuleChainId;
@@ -31,6 +32,8 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
+import java.util.concurrent.ExecutionException;
+
/**
* @author Andrew Shvayka
*/
@@ -41,6 +44,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
private final RuleChainService service;
private RuleNode ruleNode;
private TbNode tbNode;
+ private TbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
@@ -49,6 +53,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
this.self = self;
this.service = systemContext.getRuleChainService();
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
+ this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
}
@Override
@@ -80,6 +85,14 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
+ public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
+ checkActive();
+ if (ruleNode.isDebugMode()) {
+ systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
+ }
+ tbNode.onMsg(defaultCtx, msg.getMsg());
+ }
+
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
checkActive();
if (ruleNode.isDebugMode()) {
@@ -91,9 +104,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
private TbNode initComponent(RuleNode ruleNode) throws Exception {
Class<?> componentClazz = Class.forName(ruleNode.getType());
TbNode tbNode = (TbNode) (componentClazz.newInstance());
- tbNode.init(new TbNodeConfiguration(ruleNode.getConfiguration()), new TbNodeState());
+ tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
return tbNode;
}
-
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java
new file mode 100644
index 0000000..5c5af42
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java
@@ -0,0 +1,37 @@
+/**
+ * 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.server.actors.ruleChain;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.RuleNodeId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.TbMsg;
+
+/**
+ * Created by ashvayka on 19.03.18.
+ */
+@Data
+final class RuleNodeToSelfMsg implements TbActorMsg {
+
+ private final TbMsg msg;
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.RULE_TO_SELF_MSG;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
index 479f424..9377756 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
@@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
NodeConfiguration config = configClazz.newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration();
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
+ nodeDefinition.setUiResources(nodeAnnotation.uiResources());
+ nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
return nodeDefinition;
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
index bbcb98f..93fe767 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
@@ -51,6 +51,6 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
TimePageLink pageLink = new TimePageLink(limit);
return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
new TypeReference<TimePageData<Event>>() {
- }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG, tenantId.getId());
+ }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId());
}
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
index 659a242..7d4e480 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
@@ -37,7 +37,7 @@ public class DataConstants {
public static final String ERROR = "ERROR";
public static final String LC_EVENT = "LC_EVENT";
public static final String STATS = "STATS";
- public static final String DEBUG = "DEBUG";
+ public static final String DEBUG_RULE_NODE = "DEBUG_RULE_NODE";
public static final String ONEWAY = "ONEWAY";
public static final String TWOWAY = "TWOWAY";
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
index 7c00ee6..f8f2044 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
@@ -54,4 +54,9 @@ public enum MsgType {
*/
RULE_TO_SELF_ERROR_MSG,
+ /**
+ * Message that is sent by RuleActor implementation to RuleActor itself to process the message.
+ */
+ RULE_TO_SELF_MSG,
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/BufferLimitException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/BufferLimitException.java
new file mode 100644
index 0000000..3334dc6
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/exception/BufferLimitException.java
@@ -0,0 +1,25 @@
+/**
+ * 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.server.dao.exception;
+
+public class BufferLimitException extends RuntimeException {
+
+ private static final long serialVersionUID = 4513762009041887588L;
+
+ public BufferLimitException() {
+ super("Rate Limit Buffer is full");
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java
index 2674c6d..d250563 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java
@@ -24,6 +24,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
+import org.thingsboard.server.dao.exception.BufferLimitException;
import org.thingsboard.server.dao.util.AsyncRateLimiter;
import javax.annotation.Nullable;
@@ -35,9 +36,15 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
private final ListenableFuture<Void> rateLimitFuture;
public RateLimitedResultSetFuture(Session session, AsyncRateLimiter rateLimiter, Statement statement) {
- this.rateLimitFuture = rateLimiter.acquireAsync();
+ this.rateLimitFuture = Futures.withFallback(rateLimiter.acquireAsync(), t -> {
+ if (!(t instanceof BufferLimitException)) {
+ rateLimiter.release();
+ }
+ return Futures.immediateFailedFuture(t);
+ });
this.originalFuture = Futures.transform(rateLimitFuture,
(Function<Void, ResultSetFuture>) i -> executeAsyncWithRelease(rateLimiter, session, statement));
+
}
@Override
@@ -108,10 +115,7 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
try {
ResultSetFuture resultSetFuture = Uninterruptibles.getUninterruptibly(originalFuture);
resultSetFuture.addListener(listener, executor);
- } catch (CancellationException e) {
- cancel(false);
- return;
- } catch (ExecutionException e) {
+ } catch (CancellationException | ExecutionException e) {
Futures.immediateFailedFuture(e).addListener(listener, executor);
}
}, executor);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
index 2acd623..0419668 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
+import org.thingsboard.server.dao.exception.BufferLimitException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -41,6 +42,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
private final AtomicInteger maxQueueSize = new AtomicInteger();
private final AtomicInteger maxGrantedPermissions = new AtomicInteger();
+ private final AtomicInteger totalGranted = new AtomicInteger();
+ private final AtomicInteger totalReleased = new AtomicInteger();
+ private final AtomicInteger totalRequested = new AtomicInteger();
public BufferedRateLimiter(@Value("${cassandra.query.buffer_size}") int queueLimit,
@Value("${cassandra.query.concurrent_limit}") int permitsLimit,
@@ -53,11 +57,13 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
@Override
public ListenableFuture<Void> acquireAsync() {
+ totalRequested.incrementAndGet();
if (queue.isEmpty()) {
if (permits.incrementAndGet() <= permitsLimit) {
if (permits.get() > maxGrantedPermissions.get()) {
maxGrantedPermissions.set(permits.get());
}
+ totalGranted.incrementAndGet();
return Futures.immediateFuture(null);
}
permits.decrementAndGet();
@@ -69,6 +75,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
@Override
public void release() {
permits.decrementAndGet();
+ totalReleased.incrementAndGet();
reprocessQueue();
}
@@ -80,6 +87,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
}
LockedFuture lockedFuture = queue.poll();
if (lockedFuture != null) {
+ totalGranted.incrementAndGet();
lockedFuture.latch.countDown();
} else {
permits.decrementAndGet();
@@ -112,17 +120,20 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
LockedFuture lockedFuture = createLockedFuture();
if (!queue.offer(lockedFuture, 1, TimeUnit.SECONDS)) {
lockedFuture.cancelFuture();
- return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject"));
+ return Futures.immediateFailedFuture(new BufferLimitException());
+ }
+ if(permits.get() < permitsLimit) {
+ reprocessQueue();
}
if(permits.get() < permitsLimit) {
reprocessQueue();
}
return lockedFuture.future;
} catch (InterruptedException e) {
- return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject"));
+ return Futures.immediateFailedFuture(new BufferLimitException());
}
}
- return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject"));
+ return Futures.immediateFailedFuture(new BufferLimitException());
}
@Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
@@ -134,8 +145,11 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
expiredCount++;
}
}
- log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}] current granted [{}]", maxQueueSize.getAndSet(0),
- maxGrantedPermissions.getAndSet(0), expiredCount, permits.get());
+ log.info("Permits maxBuffer [{}] maxPermits [{}] expired [{}] currPermits [{}] currBuffer [{}] " +
+ "totalPermits [{}] totalRequests [{}] totalReleased [{}]",
+ maxQueueSize.getAndSet(0), maxGrantedPermissions.getAndSet(0), expiredCount,
+ permits.get(), queue.size(),
+ totalGranted.getAndSet(0), totalRequested.getAndSet(0), totalReleased.getAndSet(0));
}
private class LockedFuture {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java
index fa62c2b..f49668d 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java
@@ -19,16 +19,17 @@ import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.UnsupportedFeatureException;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
+import org.thingsboard.server.dao.exception.BufferLimitException;
import org.thingsboard.server.dao.util.AsyncRateLimiter;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -53,7 +54,7 @@ public class RateLimitedResultSetFutureTest {
@Test
public void doNotReleasePermissionIfRateLimitFutureFailed() throws InterruptedException {
- when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFailedFuture(new IllegalArgumentException()));
+ when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFailedFuture(new BufferLimitException()));
resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
Thread.sleep(1000L);
verify(rateLimiter).acquireAsync();
@@ -153,4 +154,29 @@ public class RateLimitedResultSetFutureTest {
verify(rateLimiter, times(1)).release();
}
+ @Test
+ public void expiredQueryReturnPermit() throws InterruptedException, ExecutionException {
+ CountDownLatch latch = new CountDownLatch(1);
+ ListenableFuture<Void> future = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)).submit(() -> {
+ latch.await();
+ return null;
+ });
+ when(rateLimiter.acquireAsync()).thenReturn(future);
+ resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
+
+ ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
+// TimeUnit.MILLISECONDS.sleep(200);
+ future.cancel(false);
+ latch.countDown();
+
+ try {
+ transform.get();
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof ExecutionException);
+ }
+ verify(rateLimiter, times(1)).acquireAsync();
+ verify(rateLimiter, times(1)).release();
+ }
+
}
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java b/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java
index 5bfc3b6..67c3ce8 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.util;
import com.google.common.util.concurrent.*;
import org.junit.Test;
+import org.thingsboard.server.dao.exception.BufferLimitException;
import javax.annotation.Nullable;
import java.util.concurrent.ExecutionException;
@@ -61,8 +62,8 @@ public class BufferedRateLimiterTest {
} catch (Exception e) {
assertTrue(e instanceof ExecutionException);
Throwable actualCause = e.getCause();
- assertTrue(actualCause instanceof IllegalStateException);
- assertEquals("Rate Limit Buffer is full. Reject", actualCause.getMessage());
+ assertTrue(actualCause instanceof BufferLimitException);
+ assertEquals("Rate Limit Buffer is full", actualCause.getMessage());
}
}
pom.xml 1(+1 -0)
diff --git a/pom.xml b/pom.xml
index f0c915a..a90a8aa 100755
--- a/pom.xml
+++ b/pom.xml
@@ -284,6 +284,7 @@
<exclude>src/sh/**</exclude>
<exclude>src/main/scripts/control/**</exclude>
<exclude>src/main/scripts/windows/**</exclude>
+ <exclude>src/main/resources/public/static/rulenode/**</exclude>
</excludes>
<mapping>
<proto>JAVADOC_STYLE</proto>
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
index 5e4c4b5..25c1401 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java
@@ -15,8 +15,8 @@
*/
package org.thingsboard.rule.engine.api;
-public interface NodeConfiguration {
+public interface NodeConfiguration<T extends NodeConfiguration> {
- NodeConfiguration defaultConfiguration();
+ T defaultConfiguration();
}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
index 6c57d92..18b2b94 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
@@ -29,5 +29,7 @@ public class NodeDefinition {
String[] relationTypes;
boolean customRelations;
JsonNode defaultConfiguration;
+ String[] uiResources;
+ String configDirective;
}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
index 1617034..eea92ed 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
@@ -45,6 +45,10 @@ public @interface RuleNode {
String[] relationTypes() default {"Success", "Failure"};
+ String[] uiResources() default {};
+
+ String configDirective() default "";
+
boolean customRelations() default false;
}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
index 260a51f..46fa1d2 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.rule.engine.api;
+import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.alarm.AlarmService;
@@ -55,6 +56,8 @@ public interface TbContext {
void tellError(TbMsg msg, Throwable th);
+ RuleNodeId getSelfId();
+
AttributesService getAttributesService();
CustomerService getCustomerService();
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java
index 89442bb..2555c99 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java
@@ -24,7 +24,7 @@ import java.util.concurrent.ExecutionException;
*/
public interface TbNode {
- void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException;
+ void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException;
void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
new file mode 100644
index 0000000..43851a3
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
@@ -0,0 +1,83 @@
+/**
+ * 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.debug;
+
+import com.datastax.driver.core.utils.UUIDs;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.ListeningExecutor;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.filter.TbJsFilterNodeConfiguration;
+import org.thingsboard.rule.engine.js.NashornJsEngine;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import javax.script.Bindings;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
+
+@Slf4j
+@RuleNode(
+ type = ComponentType.ACTION,
+ name = "generator",
+ configClazz = TbMsgGeneratorNodeConfiguration.class,
+ nodeDescription = "Periodically generates messages",
+ nodeDetails = "Generates messages with configurable period. ",
+ inEnabled = false
+)
+
+public class TbMsgGeneratorNode implements TbNode {
+
+ public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
+
+ private TbMsgGeneratorNodeConfiguration config;
+ private long delay;
+
+ @Override
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+ this.config = TbNodeUtils.convert(configuration, TbMsgGeneratorNodeConfiguration.class);
+ this.delay = TimeUnit.SECONDS.toMillis(config.getPeriodInSeconds());
+ ctx.tellSelf(newTickMsg(ctx), delay);
+ }
+
+ @Override
+ public void onMsg(TbContext ctx, TbMsg msg) {
+ if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG)) {
+ TbMsgMetaData metaData = new TbMsgMetaData();
+ if (config.getMsgMetaData() != null) {
+ config.getMsgMetaData().forEach(metaData::putValue);
+ }
+ ctx.tellNext(new TbMsg(UUIDs.timeBased(), config.getMsgType(), ctx.getSelfId(), metaData, config.getMsgBody().getBytes(StandardCharsets.UTF_8)));
+ ctx.tellSelf(newTickMsg(ctx), delay);
+ }
+ }
+
+ private TbMsg newTickMsg(TbContext ctx) {
+ return new TbMsg(UUIDs.timeBased(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), new byte[]{});
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
new file mode 100644
index 0000000..e15b13f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
@@ -0,0 +1,40 @@
+/**
+ * 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.debug;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import java.util.Map;
+
+@Data
+public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgGeneratorNodeConfiguration> {
+
+ private int msgCount;
+ private int periodInSeconds;
+ private String msgType;
+ private String msgBody;
+ private Map<String, String> msgMetaData;
+
+ @Override
+ public TbMsgGeneratorNodeConfiguration defaultConfiguration() {
+ TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration();
+ configuration.setMsgCount(0);
+ configuration.setPeriodInSeconds(1);
+ configuration.setMsgType("DebugMsg");
+ configuration.setMsgBody("{}");
+ return configuration;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
index 07b166d..044a8b8 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
@@ -29,22 +29,25 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j
@RuleNode(
type = ComponentType.FILTER,
- name = "script", relationTypes = {"True", "False", "Failure"},
+ name = "script", relationTypes = {"True", "False"},
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." +
"Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
- "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
+ "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbFilterNodeScriptConfig")
+
public class TbJsFilterNode implements TbNode {
private TbJsFilterNodeConfiguration config;
private NashornJsEngine jsEngine;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
- this.jsEngine = new NashornJsEngine(config.getJsScript());
+ this.jsEngine = new NashornJsEngine(config.getJsScript(), "Filter");
}
@Override
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
index 3b19c7c..9ab74e8 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
@@ -19,14 +19,14 @@ import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data
-public class TbJsFilterNodeConfiguration implements NodeConfiguration {
+public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilterNodeConfiguration> {
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;");
+ configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
return configuration;
}
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
index c1236a4..d04b58a 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
@@ -36,31 +36,22 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
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. " +
"Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " +
- "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
+ "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbFilterNodeSwitchConfig")
public class TbJsSwitchNode implements TbNode {
private TbJsSwitchNodeConfiguration config;
private NashornJsEngine jsEngine;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
- if (config.getAllowedRelations().size() < 1) {
- String message = "Switch node should have at least 1 relation";
- log.error(message);
- throw new IllegalStateException(message);
- }
- if (!config.isRouteToAllWithNoCheck()) {
- this.jsEngine = new NashornJsEngine(config.getJsScript());
- }
+ this.jsEngine = new NashornJsEngine(config.getJsScript(), "Switch");
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
- if (config.isRouteToAllWithNoCheck()) {
- ctx.tellNext(msg, config.getAllowedRelations());
- return;
- }
ListeningExecutor jsExecutor = ctx.getJsExecutor();
withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
result -> processSwitch(ctx, msg, result),
@@ -68,15 +59,7 @@ public class TbJsSwitchNode implements TbNode {
}
private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
- if (validateRelations(nextRelations)) {
- ctx.tellNext(msg, nextRelations);
- } else {
- ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelations));
- }
- }
-
- private boolean validateRelations(Set<String> nextRelations) {
- return config.getAllowedRelations().containsAll(nextRelations);
+ ctx.tellNext(msg, nextRelations);
}
private Bindings toBindings(TbMsg msg) {
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
index b354c71..79b0912 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
@@ -22,22 +22,18 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.Set;
@Data
-public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
+public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitchNodeConfiguration> {
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" +
+ configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
" return ['one','nine'];" +
"};\n" +
"\n" +
- "nextRelation(meta, msg);");
- configuration.setAllowedRelations(Sets.newHashSet("one", "two"));
- configuration.setRouteToAllWithNoCheck(false);
+ "return nextRelation(metadata, msg);");
return configuration;
}
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
index 3a86c25..225cd99 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
@@ -29,15 +29,18 @@ import org.thingsboard.server.common.msg.TbMsg;
type = ComponentType.FILTER,
name = "message type",
configClazz = TbMsgTypeFilterNodeConfiguration.class,
+ relationTypes = {"True", "False"},
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.")
+ "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.",
+ uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
+ configDirective = "tbFilterNodeMessageTypeConfig")
public class TbMsgTypeFilterNode implements TbNode {
TbMsgTypeFilterNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgTypeFilterNodeConfiguration.class);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
index a2e1b17..ae88aa8 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
@@ -26,14 +26,14 @@ import java.util.List;
* Created by ashvayka on 19.01.18.
*/
@Data
-public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration {
+public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsgTypeFilterNodeConfiguration> {
private List<String> messageTypes;
@Override
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
- configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
+ configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
return configuration;
}
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java
index 082535f..a4add40 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java
@@ -34,14 +34,20 @@ import java.util.Set;
@Slf4j
public class NashornJsEngine {
- public static final String METADATA = "meta";
+ public static final String METADATA = "metadata";
public static final String DATA = "msg";
+
+ private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msg, metadata) { ";
+ private static final String JS_WRAPPER_SUFFIX_TEMPLATE = "}\n %s(msg, metadata);";
+
private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
private CompiledScript engine;
- public NashornJsEngine(String script) {
- engine = compileScript(script);
+ public NashornJsEngine(String script, String functionName) {
+ String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, functionName);
+ String jsWrapperSuffix = String.format(JS_WRAPPER_SUFFIX_TEMPLATE, functionName);
+ engine = compileScript(jsWrapperPrefix + script + jsWrapperSuffix);
}
private static CompiledScript compileScript(String script) {
@@ -58,15 +64,15 @@ public class NashornJsEngine {
public static Bindings bindMsg(TbMsg msg) {
try {
Bindings bindings = new SimpleBindings();
- bindings.put(METADATA, msg.getMetaData().getData());
-
if (ArrayUtils.isNotEmpty(msg.getData())) {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(msg.getData());
Map map = mapper.treeToValue(jsonNode, Map.class);
bindings.put(DATA, map);
+ } else {
+ bindings.put(DATA, Collections.emptyMap());
}
-
+ bindings.put(METADATA, msg.getMetaData().getData());
return bindings;
} catch (Throwable th) {
throw new IllegalArgumentException("Cannot bind js args", th);
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java
index 269e40f..3bb1cff 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java
@@ -37,7 +37,7 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode
private TbGetEntityAttrNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
index 69ee9d7..7bf70f4 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
@@ -42,14 +42,14 @@ import static org.thingsboard.server.common.data.DataConstants.*;
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 " +
- "<code>meta.cs.temperature</code> or <code>meta.shared.limit</code> " +
+ "<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " +
"If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.")
public class TbGetAttributesNode implements TbNode {
private TbGetAttributesNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java
index 103b4de..6cd2247 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java
@@ -25,7 +25,7 @@ import java.util.List;
* Created by ashvayka on 19.01.18.
*/
@Data
-public class TbGetAttributesNodeConfiguration implements NodeConfiguration {
+public class TbGetAttributesNodeConfiguration implements NodeConfiguration<TbGetAttributesNodeConfiguration> {
private List<String> clientAttributeNames;
private List<String> sharedAttributeNames;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
index cc6d6a1..c59a65e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
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 " +
- "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
+ "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
@Override
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java
index 5195115..0bcefae 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java
@@ -23,7 +23,7 @@ import java.util.Map;
import java.util.Optional;
@Data
-public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration {
+public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration<TbGetEntityAttrNodeConfiguration> {
private Map<String, String> attrMapping;
private boolean isTelemetry = false;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
index 22c0b9f..603746e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
@@ -32,13 +32,13 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
"If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
"If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"To access those attributes in other nodes this template can be used " +
- "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
+ "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
private TbGetRelatedAttrNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext context, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class);
setConfig(config);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
index b5f5e02..3165385 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
@@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
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 " +
- "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
+ "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
@Override
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
index 426a327..d6fcc5e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
@@ -32,7 +32,7 @@ public abstract class TbAbstractTransformNode implements TbNode {
private TbTransformNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext context, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbTransformNodeConfiguration.class);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
index 40a647a..cf761f2 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
@@ -49,7 +49,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
private TbChangeOriginatorNodeConfiguration config;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbChangeOriginatorNodeConfiguration.class);
validateConfig(config);
setConfig(config);
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
index 626790f..27e46fe 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
@@ -30,7 +30,7 @@ import javax.script.Bindings;
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/>" +
+ "<code>metadata</code> - is a Message metadata.<br/>" +
"<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.")
public class TbTransformMsgNode extends TbAbstractTransformNode {
@@ -38,9 +38,9 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
private NashornJsEngine jsEngine;
@Override
- public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
- this.jsEngine = new NashornJsEngine(config.getJsScript());
+ this.jsEngine = new NashornJsEngine(config.getJsScript(), "Transform");
setConfig(config);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
index 4f9e9eb..09d5ac4 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
@@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
public TbTransformMsgNodeConfiguration defaultConfiguration() {
TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
configuration.setStartNewChain(false);
- configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
+ configuration.setJsScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
return configuration;
}
}
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
new file mode 100644
index 0000000..a6103c1
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
@@ -0,0 +1,2 @@
+.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}
+/*# sourceMappingURL=rulenode-core-config.css.map*/
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
new file mode 100644
index 0000000..3fe859d
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
@@ -0,0 +1,2 @@
+!function(e){function t(s){if(a[s])return a[s].exports;var n=a[s]={exports:{},id:s,loaded:!1};return e[s].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a={};return t.m=e,t.c=a,t.p="/static/",t(0)}([function(e,t,a){e.exports=a(8)},function(e,t){},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t,a){var s=function(s,n,r,l){function u(){if(l.$viewValue){for(var e=[],t=0;t<s.messageTypes.length;t++)e.push(s.messageTypes[t].value);l.$viewValue.messageTypes=e,o()}}function o(){if(s.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var c=i.default;n.html(c),s.selectedMessageType=null,s.messageTypeSearchText=null,s.ngModelCtrl=l;var d=[];for(var p in a.messageType){var m={name:a.messageType[p].name,value:a.messageType[p].value};d.push(m)}s.transformMessageTypeChip=function(e){var a,s=t("filter")(d,{name:e},!0);return a=s&&s.length?angular.copy(s[0]):{name:e,value:e}},s.messageTypesSearch=function(e){var a=e?t("filter")(d,{name:e}):d;return a.map(function(e){return e.name})},s.createMessageType=function(e,t){var a=angular.element(t,n)[0].firstElementChild,s=angular.element(a),r=s.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),s.scope().$mdChipsCtrl.appendChip(r.trim()),s.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var n=0;n<e.messageTypes.length;n++){var r=e.messageTypes[n];a.messageType[r]?t.push(angular.copy(a.messageType[r])):t.push({name:r,value:r})}s.messageTypes=t,s.$watch("messageTypes",function(e,t){angular.equals(e,t)||u()},!0)},e(n.contents())(s)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:s}}n.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,a(1);var r=a(2),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(3),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(4),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(11),r=s(n),i=a(6),l=s(i),u=a(5),o=s(u),c=a(7),d=s(c),p=a(10),m=s(p);t.default=angular.module("thingsboard.ruleChain.config",[r.default]).directive("tbFilterNodeScriptConfig",l.default).directive("tbFilterNodeMessageTypeConfig",o.default).directive("tbFilterNodeSwitchConfig",d.default).config(m.default).name},function(e,t){"use strict";function a(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required."}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t){(0,i.default)(t);for(var a in t){var s=t[a];e.translations(a,s)}}n.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(9),i=s(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}}}).name}]);
+//# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
index 96f7032..de9d9c4 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
@@ -51,7 +51,7 @@ public class TbJsFilterNodeTest {
@Test
public void falseEvaluationDoNotSendMsg() throws TbNodeException {
- initWithScript("10 > 15;");
+ initWithScript("return 10 > 15;");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
mockJsExecutor();
@@ -64,7 +64,7 @@ public class TbJsFilterNodeTest {
@Test
public void notValidMsgDataThrowsException() throws TbNodeException {
- initWithScript("10 > 15;");
+ initWithScript("return 10 > 15;");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]);
when(ctx.getJsExecutor()).thenReturn(executor);
@@ -77,7 +77,7 @@ public class TbJsFilterNodeTest {
@Test
public void exceptionInJsThrowsException() throws TbNodeException {
- initWithScript("meta.temp.curr < 15;");
+ initWithScript("return metadata.temp.curr < 15;");
TbMsgMetaData metaData = new TbMsgMetaData();
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
mockJsExecutor();
@@ -89,12 +89,12 @@ public class TbJsFilterNodeTest {
@Test(expected = IllegalArgumentException.class)
public void notValidScriptThrowsException() throws TbNodeException {
- initWithScript("10 > 15 asdq out");
+ initWithScript("return 10 > 15 asdq out");
}
@Test
public void metadataConditionCanBeFalse() throws TbNodeException {
- initWithScript("meta.humidity < 15;");
+ initWithScript("return metadata.humidity < 15;");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -109,7 +109,7 @@ public class TbJsFilterNodeTest {
@Test
public void metadataConditionCanBeTrue() throws TbNodeException {
- initWithScript("meta.temp < 15;");
+ initWithScript("return metadata.temp < 15;");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -123,7 +123,7 @@ public class TbJsFilterNodeTest {
@Test
public void msgJsonParsedAndBinded() throws TbNodeException {
- initWithScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;");
+ initWithScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -144,7 +144,7 @@ public class TbJsFilterNodeTest {
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbJsFilterNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
}
private void mockJsExecutor() {
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
index e70d4e1..f1f2d73 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
@@ -53,27 +53,16 @@ public class TbJsSwitchNodeTest {
private ListeningExecutor executor;
@Test
- public void routeToAllDoNotEvaluatesJs() throws TbNodeException {
- HashSet<String> relations = Sets.newHashSet("one", "two");
- initWithScript("test qwerty", relations, true);
- TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
-
- node.onMsg(ctx, msg);
- verify(ctx).tellNext(msg, relations);
- verifyNoMoreInteractions(ctx, executor);
- }
-
- @Test
public void multipleRoutesAreAllowed() throws TbNodeException {
- String jsCode = "function nextRelation(meta, msg) {\n" +
- " if(msg.passed == 5 && meta.temp == 10)\n" +
+ String jsCode = "function nextRelation(metadata, msg) {\n" +
+ " if(msg.passed == 5 && metadata.temp == 10)\n" +
" return ['three', 'one']\n" +
" else\n" +
" return 'two';\n" +
"};\n" +
"\n" +
- "nextRelation(meta, msg);";
- initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false);
+ "return nextRelation(metadata, msg);";
+ initWithScript(jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -89,15 +78,15 @@ public class TbJsSwitchNodeTest {
@Test
public void allowedRelationPassed() throws TbNodeException {
- String jsCode = "function nextRelation(meta, msg) {\n" +
- " if(msg.passed == 5 && meta.temp == 10)\n" +
+ String jsCode = "function nextRelation(metadata, msg) {\n" +
+ " if(msg.passed == 5 && metadata.temp == 10)\n" +
" return 'one'\n" +
" else\n" +
" return 'two';\n" +
"};\n" +
"\n" +
- "nextRelation(meta, msg);";
- initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
+ "return nextRelation(metadata, msg);";
+ initWithScript(jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -111,37 +100,14 @@ public class TbJsSwitchNodeTest {
verify(ctx).tellNext(msg, Sets.newHashSet("one"));
}
- @Test
- public void unknownRelationThrowsException() throws TbNodeException {
- String jsCode = "function nextRelation(meta, msg) {\n" +
- " return ['one','nine'];" +
- "};\n" +
- "\n" +
- "nextRelation(meta, msg);";
- initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
- TbMsgMetaData metaData = new TbMsgMetaData();
- metaData.putValue("temp", "10");
- metaData.putValue("humidity", "99");
- String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
-
- TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
- mockJsExecutor();
-
- node.onMsg(ctx, msg);
- verify(ctx).getJsExecutor();
- verifyError(msg, "Unsupported relation for switch [nine, one]", IllegalStateException.class);
- }
-
- private void initWithScript(String script, Set<String> relations, boolean routeToAll) throws TbNodeException {
+ private void initWithScript(String script) throws TbNodeException {
TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
config.setJsScript(script);
- config.setAllowedRelations(relations);
- config.setRouteToAllWithNoCheck(routeToAll);
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbJsSwitchNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
}
private void mockJsExecutor() {
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
index ad40f03..2218749 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
@@ -88,7 +88,7 @@ public class TbGetCustomerAttributeNodeTest {
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbGetCustomerAttributeNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
}
@Test
@@ -226,7 +226,7 @@ public class TbGetCustomerAttributeNodeTest {
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbGetCustomerAttributeNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
index 190692c..39e0121 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
@@ -119,6 +119,6 @@ public class TbChangeOriginatorNodeTest {
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbChangeOriginatorNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
}
}
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
index d69bad8..6df9830 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
@@ -51,7 +51,7 @@ public class TbTransformMsgNodeTest {
@Test
public void metadataCanBeUpdated() throws TbNodeException {
- initWithScript("meta.temp = meta.temp * 10;");
+ initWithScript("return metadata.temp = metadata.temp * 10;");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -70,7 +70,7 @@ public class TbTransformMsgNodeTest {
@Test
public void metadataCanBeAdded() throws TbNodeException {
- initWithScript("meta.newAttr = meta.humidity - msg.passed;");
+ initWithScript("return metadata.newAttr = metadata.humidity - msg.passed;");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -89,7 +89,7 @@ public class TbTransformMsgNodeTest {
@Test
public void payloadCanBeUpdated() throws TbNodeException {
- initWithScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
+ initWithScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -114,7 +114,7 @@ public class TbTransformMsgNodeTest {
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
node = new TbTransformMsgNode();
- node.init(nodeConfiguration, null);
+ node.init(null, nodeConfiguration);
}
private void mockJsExecutor() {
ui/package.json 2(+1 -1)
diff --git a/ui/package.json b/ui/package.json
index e68aa34..ec3f880 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -15,7 +15,7 @@
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
- "ace-builds": "^1.2.5",
+ "ace-builds": "1.3.1",
"angular": "1.5.8",
"angular-animate": "1.5.8",
"angular-aria": "1.5.8",
ui/server.js 20(+20 -0)
diff --git a/ui/server.js b/ui/server.js
index fae132f..65a2bc7 100644
--- a/ui/server.js
+++ b/ui/server.js
@@ -30,6 +30,9 @@ const httpProxy = require('http-proxy');
const forwardHost = 'localhost';
const forwardPort = 8080;
+const ruleNodeUiforwardHost = 'localhost';
+const ruleNodeUiforwardPort = 8080;
+
const app = express();
const server = http.createServer(app);
@@ -52,17 +55,34 @@ const apiProxy = httpProxy.createProxyServer({
}
});
+const ruleNodeUiApiProxy = httpProxy.createProxyServer({
+ target: {
+ host: ruleNodeUiforwardHost,
+ port: ruleNodeUiforwardPort
+ }
+});
+
apiProxy.on('error', function (err, req, res) {
console.warn('API proxy error: ' + err);
res.end('Error.');
});
+ruleNodeUiApiProxy.on('error', function (err, req, res) {
+ console.warn('RuleNode UI API proxy error: ' + err);
+ res.end('Error.');
+});
+
console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
+console.info(`Forwarding Rule Node UI requests to http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`);
app.all('/api/*', (req, res) => {
apiProxy.web(req, res);
});
+app.all('/static/rulenode/*', (req, res) => {
+ ruleNodeUiApiProxy.web(req, res);
+});
+
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'src/index.html'));
});
ui/src/app/api/rule-chain.service.js 59(+54 -5)
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index ebc48fa..af14a3f 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', [])
.factory('ruleChainService', RuleChainService).name;
/*@ngInject*/
-function RuleChainService($http, $q, $filter, types, componentDescriptorService) {
+function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) {
var ruleNodeComponents = null;
@@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
} else {
loadRuleNodeComponents().then(
(components) => {
- ruleNodeComponents = components;
- ruleNodeComponents.push(
- types.ruleChainNodeComponent
+ resolveRuleNodeComponentsUiResources(components).then(
+ (components) => {
+ ruleNodeComponents = components;
+ ruleNodeComponents.push(
+ types.ruleChainNodeComponent
+ );
+ deferred.resolve(ruleNodeComponents);
+ },
+ () => {
+ deferred.reject();
+ }
);
- deferred.resolve(ruleNodeComponents);
},
() => {
deferred.reject();
@@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
return deferred.promise;
}
+ function resolveRuleNodeComponentsUiResources(components) {
+ var deferred = $q.defer();
+ var tasks = [];
+ for (var i=0;i<components.length;i++) {
+ var component = components[i];
+ tasks.push(resolveRuleNodeComponentUiResources(component));
+ }
+ $q.all(tasks).then(
+ (components) => {
+ deferred.resolve(components);
+ },
+ () => {
+ deferred.resolve(components);
+ }
+ );
+ return deferred.promise;
+ }
+
+ function resolveRuleNodeComponentUiResources(component) {
+ var deferred = $q.defer();
+ var uiResources = component.configurationDescriptor.nodeDefinition.uiResources;
+ if (uiResources && uiResources.length) {
+ var tasks = [];
+ for (var i=0;i<uiResources.length;i++) {
+ var uiResource = uiResources[i];
+ tasks.push($ocLazyLoad.load(uiResource));
+ }
+ $q.all(tasks).then(
+ () => {
+ deferred.resolve(component);
+ },
+ () => {
+ component.configurationDescriptor.nodeDefinition.uiResourceLoadError = $translate.instant('rulenode.ui-resources-load-error');
+ deferred.resolve(component);
+ }
+ )
+ } else {
+ deferred.resolve(component);
+ }
+ return deferred.promise;
+ }
+
function getRuleNodeComponentByClazz(clazz) {
var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
if (res && res.length) {
ui/src/app/common/types.constant.js 30(+29 -1)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 8026115..2186508 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -279,6 +279,23 @@ export default angular.module('thingsboard.types', [])
function: "function",
alarm: "alarm"
},
+ contentType: {
+ "JSON": {
+ value: "JSON",
+ name: "content-type.json",
+ code: "json"
+ },
+ "TEXT": {
+ value: "TEXT",
+ name: "content-type.text",
+ code: "text"
+ },
+ "BINARY": {
+ value: "BINARY",
+ name: "content-type.binary",
+ code: "text"
+ }
+ },
componentType: {
filter: "FILTER",
processor: "PROCESSOR",
@@ -295,7 +312,8 @@ export default angular.module('thingsboard.types', [])
user: "USER",
dashboard: "DASHBOARD",
alarm: "ALARM",
- rulechain: "RULE_CHAIN"
+ rulechain: "RULE_CHAIN",
+ rulenode: "RULE_NODE"
},
aliasEntityType: {
current_customer: "CURRENT_CUSTOMER"
@@ -388,6 +406,16 @@ export default angular.module('thingsboard.types', [])
name: "event.type-stats"
}
},
+ debugEventType: {
+ debugRuleNode: {
+ value: "DEBUG_RULE_NODE",
+ name: "event.type-debug-rule-node"
+ },
+ debugRuleChain: {
+ value: "DEBUG_RULE_CHAIN",
+ name: "event.type-debug-rule-chain"
+ }
+ },
extensionType: {
http: "HTTP",
mqtt: "MQTT",
diff --git a/ui/src/app/components/confirm-on-exit.directive.js b/ui/src/app/components/confirm-on-exit.directive.js
index fe9a9bd..e04e110 100644
--- a/ui/src/app/components/confirm-on-exit.directive.js
+++ b/ui/src/app/components/confirm-on-exit.directive.js
@@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', [])
.name;
/*@ngInject*/
-function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
+function ConfirmOnExit($state, $mdDialog, $window, $filter, $parse, userService) {
return {
- link: function ($scope) {
-
+ link: function ($scope, $element, $attributes) {
+ $scope.confirmForm = $scope.$eval($attributes.confirmForm);
$window.onbeforeunload = function () {
- if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) {
+ if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) {
return $filter('translate')('confirm-on-exit.message');
}
}
$scope.$on('$stateChangeStart', function (event, next, current, params) {
- if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) {
+ if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) {
event.preventDefault();
var confirm = $mdDialog.confirm()
.title($filter('translate')('confirm-on-exit.title'))
@@ -40,7 +40,9 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
if ($scope.confirmForm) {
$scope.confirmForm.$setPristine();
} else {
- $scope.isDirty = false;
+ var remoteSetter = $parse($attributes.isDirty).assign;
+ remoteSetter($scope, false);
+ //$scope.isDirty = false;
}
$state.go(next.name, params);
}, function () {
@@ -48,9 +50,6 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
}
});
},
- scope: {
- confirmForm: '=',
- isDirty: '='
- }
+ scope: false
};
}
\ No newline at end of file
diff --git a/ui/src/app/components/details-sidenav.directive.js b/ui/src/app/components/details-sidenav.directive.js
index e455a80..2516134 100644
--- a/ui/src/app/components/details-sidenav.directive.js
+++ b/ui/src/app/components/details-sidenav.directive.js
@@ -26,7 +26,7 @@ export default angular.module('thingsboard.directives.detailsSidenav', [])
.name;
/*@ngInject*/
-function DetailsSidenav($timeout) {
+function DetailsSidenav($timeout, $mdUtil, $q, $animate) {
var linker = function (scope, element, attrs) {
@@ -42,6 +42,63 @@ function DetailsSidenav($timeout) {
scope.isEdit = true;
}
+ var backdrop;
+ var previousContainerStyles;
+
+ if (attrs.hasOwnProperty('tbEnableBackdrop')) {
+ backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
+ element.on('$destroy', function() {
+ backdrop && backdrop.remove();
+ });
+ scope.$on('$destroy', function(){
+ backdrop && backdrop.remove();
+ });
+ scope.$watch('isOpen', updateIsOpen);
+ }
+
+ function updateIsOpen(isOpen) {
+ backdrop[isOpen ? 'on' : 'off']('click', (ev)=>{
+ ev.preventDefault();
+ scope.isOpen = false;
+ scope.$apply();
+ });
+ var parent = element.parent();
+ var restorePositioning = updateContainerPositions(parent, isOpen);
+
+ return $q.all([
+ isOpen && backdrop ? $animate.enter(backdrop, parent) : backdrop ?
+ $animate.leave(backdrop) : $q.when(true)
+ ]).then(function() {
+ restorePositioning && restorePositioning();
+ });
+ }
+
+ function updateContainerPositions(parent, willOpen) {
+ var drawerEl = element[0];
+ var scrollTop = parent[0].scrollTop;
+ if (willOpen && scrollTop) {
+ previousContainerStyles = {
+ top: drawerEl.style.top,
+ bottom: drawerEl.style.bottom,
+ height: drawerEl.style.height
+ };
+ var positionStyle = {
+ top: scrollTop + 'px',
+ bottom: 'auto',
+ height: parent[0].clientHeight + 'px'
+ };
+ backdrop.css(positionStyle);
+ }
+ if (!willOpen && previousContainerStyles) {
+ return function() {
+ backdrop[0].style.top = null;
+ backdrop[0].style.bottom = null;
+ backdrop[0].style.height = null;
+ previousContainerStyles = null;
+ };
+ }
+ }
+
scope.toggleDetailsEditMode = function () {
if (!scope.isAlwaysEdit) {
if (!scope.isEdit) {
diff --git a/ui/src/app/components/details-sidenav.tpl.html b/ui/src/app/components/details-sidenav.tpl.html
index c504a24..763bc22 100644
--- a/ui/src/app/components/details-sidenav.tpl.html
+++ b/ui/src/app/components/details-sidenav.tpl.html
@@ -16,7 +16,7 @@
-->
<md-sidenav class="md-sidenav-right md-whiteframe-4dp tb-sidenav-details"
- md-disable-backdrop="true"
+ md-disable-backdrop
md-is-open="isOpen"
md-component-id="right"
layout="column">
ui/src/app/components/js-func.directive.js 34(+29 -5)
diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js
index 33cebde..deb5626 100644
--- a/ui/src/app/components/js-func.directive.js
+++ b/ui/src/app/components/js-func.directive.js
@@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
var template = $templateCache.get(jsFuncTemplate);
element.html(template);
+ scope.functionName = attrs.functionName;
scope.functionArgs = scope.$eval(attrs.functionArgs);
scope.validationArgs = scope.$eval(attrs.validationArgs);
scope.resultType = attrs.resultType;
@@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.resultType = "nocheck";
}
+ scope.validationTriggerArg = attrs.validationTriggerArg;
+
scope.functionValid = true;
var Range = ace.acequire("ace/range").Range;
@@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
}
scope.onFullscreenChanged = function () {
+ updateEditorSize();
+ };
+
+ function updateEditorSize() {
if (scope.js_editor) {
scope.js_editor.resize();
scope.js_editor.renderer.updateFull();
}
- };
+ }
scope.jsEditorOptions = {
useWrapMode: true,
@@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.validate = function () {
try {
var toValidate = new Function(scope.functionArgsString, scope.functionBody);
+ if (scope.noValidate) {
+ return true;
+ }
var res;
var validationError;
for (var i=0;i<scope.validationArgs.length;i++) {
@@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
}
};
- scope.$on('form-submit', function () {
- scope.functionValid = scope.validate();
- scope.updateValidity();
+ scope.$on('form-submit', function (event, args) {
+ if (!args || scope.validationTriggerArg && scope.validationTriggerArg == args) {
+ scope.validationArgs = scope.$eval(attrs.validationArgs);
+ scope.cleanupJsErrors();
+ scope.functionValid = true;
+ scope.updateValidity();
+ scope.functionValid = scope.validate();
+ scope.updateValidity();
+ }
+ });
+
+ scope.$on('update-ace-editor-size', function () {
+ updateEditorSize();
});
$compile(element.contents())(scope);
@@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
return {
restrict: "E",
require: "^ngModel",
- scope: {},
+ scope: {
+ disabled:'=ngDisabled',
+ noValidate: '=?',
+ fillHeight:'=?'
+ },
link: linker
};
}
ui/src/app/components/js-func.scss 10(+9 -1)
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
index 2bd5df1..e1072be 100644
--- a/ui/src/app/components/js-func.scss
+++ b/ui/src/app/components/js-func.scss
@@ -15,6 +15,12 @@
*/
tb-js-func {
position: relative;
+ .tb-disabled {
+ color: rgba(0,0,0,0.38);
+ }
+ .fill-height {
+ height: 100%;
+ }
}
.tb-js-func-panel {
@@ -23,8 +29,10 @@ tb-js-func {
height: 100%;
#tb-javascript-input {
min-width: 200px;
- min-height: 200px;
width: 100%;
height: 100%;
+ &:not(.fill-height) {
+ min-height: 200px;
+ }
}
}
ui/src/app/components/js-func.tpl.html 15(+8 -7)
diff --git a/ui/src/app/components/js-func.tpl.html b/ui/src/app/components/js-func.tpl.html
index 806de4a..93043d4 100644
--- a/ui/src/app/components/js-func.tpl.html
+++ b/ui/src/app/components/js-func.tpl.html
@@ -15,19 +15,20 @@
limitations under the License.
-->
-<div style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
+<div style="background: #fff;" ng-class="{'tb-disabled': disabled, '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" style="height: 40px;">
- <span style="font-style: italic;">function({{ functionArgsString }}) {</span>
+ <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
<span flex></span>
<div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
</div>
<div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column">
- <div flex id="tb-javascript-input"
- ui-ace="jsEditorOptions"
+ <div flex id="tb-javascript-input" ng-class="{'fill-height': fillHeight}"
+ ui-ace="jsEditorOptions"
+ ng-readonly="disabled"
ng-model="functionBody">
</div>
</div>
<div layout="row" layout-align="start center" style="height: 40px;">
- <span style="font-style: italic;">}</span>
- </div>
-</div>
\ No newline at end of file
+ <label class="tb-title no-padding">}</label>
+ </div>
+</div>
diff --git a/ui/src/app/components/json-object-edit.directive.js b/ui/src/app/components/json-object-edit.directive.js
index db0aa60..215b7b9 100644
--- a/ui/src/app/components/json-object-edit.directive.js
+++ b/ui/src/app/components/json-object-edit.directive.js
@@ -84,17 +84,32 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
scope.$watch('contentBody', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
var object = scope.validate();
- ngModelCtrl.$setViewValue(object);
+ if (scope.objectValid) {
+ if (object == null) {
+ scope.object = null;
+ } else {
+ if (scope.object == null) {
+ scope.object = {};
+ }
+ Object.keys(scope.object).forEach(function (key) {
+ delete scope.object[key];
+ });
+ Object.keys(object).forEach(function (key) {
+ scope.object[key] = object[key];
+ });
+ }
+ ngModelCtrl.$setViewValue(scope.object);
+ }
scope.updateValidity();
}
});
ngModelCtrl.$render = function () {
- var object = ngModelCtrl.$viewValue;
+ scope.object = ngModelCtrl.$viewValue;
var content = '';
try {
- if (object) {
- content = angular.toJson(object, true);
+ if (scope.object) {
+ content = angular.toJson(scope.object, true);
}
} catch (e) {
//
diff --git a/ui/src/app/event/event-content-dialog.controller.js b/ui/src/app/event/event-content-dialog.controller.js
index 108f95e..8d13f96 100644
--- a/ui/src/app/event/event-content-dialog.controller.js
+++ b/ui/src/app/event/event-content-dialog.controller.js
@@ -17,11 +17,14 @@ import $ from 'jquery';
import 'brace/ext/language_tools';
import 'brace/mode/java';
import 'brace/theme/github';
+import beautify from 'js-beautify';
/* eslint-disable angular/angularelement */
+const js_beautify = beautify.js;
+
/*@ngInject*/
-export default function EventContentDialogController($mdDialog, content, title, showingCallback) {
+export default function EventContentDialogController($mdDialog, types, content, contentType, title, showingCallback) {
var vm = this;
@@ -32,9 +35,19 @@ export default function EventContentDialogController($mdDialog, content, title,
vm.content = content;
vm.title = title;
+ var mode;
+ if (contentType) {
+ mode = types.contentType[contentType].code;
+ if (contentType == types.contentType.JSON.value && vm.content) {
+ vm.content = js_beautify(vm.content, {indent_size: 4});
+ }
+ } else {
+ mode = 'java';
+ }
+
vm.contentOptions = {
useWrapMode: false,
- mode: 'java',
+ mode: mode,
showGutter: false,
showPrintMargin: false,
theme: 'github',
diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js
index afac804..bc4cdbe 100644
--- a/ui/src/app/event/event-header.directive.js
+++ b/ui/src/app/event/event-header.directive.js
@@ -18,6 +18,7 @@
import eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html';
import eventHeaderStatsTemplate from './event-header-stats.tpl.html';
import eventHeaderErrorTemplate from './event-header-error.tpl.html';
+import eventHeaderDebugRuleNodeTemplate from './event-header-debug-rulenode.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -38,6 +39,12 @@ export default function EventHeaderDirective($compile, $templateCache, types) {
case types.eventType.error.value:
template = eventHeaderErrorTemplate;
break;
+ case types.debugEventType.debugRuleNode.value:
+ template = eventHeaderDebugRuleNodeTemplate;
+ break;
+ case types.debugEventType.debugRuleChain.value:
+ template = eventHeaderDebugRuleNodeTemplate;
+ break;
}
return $templateCache.get(template);
}
diff --git a/ui/src/app/event/event-header-debug-rulenode.tpl.html b/ui/src/app/event/event-header-debug-rulenode.tpl.html
new file mode 100644
index 0000000..b412a0c
--- /dev/null
+++ b/ui/src/app/event/event-header-debug-rulenode.tpl.html
@@ -0,0 +1,27 @@
+<!--
+
+ 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 hide-xs hide-sm translate class="tb-cell" flex="30">event.event-time</div>
+<div translate class="tb-cell" flex="20">event.server</div>
+<div translate class="tb-cell" flex="20">event.type</div>
+<div translate class="tb-cell" flex="20">event.entity</div>
+<div translate class="tb-cell" flex="20">event.message-id</div>
+<div translate class="tb-cell" flex="20">event.message-type</div>
+<div translate class="tb-cell" flex="20">event.data-type</div>
+<div translate class="tb-cell" flex="20">event.data</div>
+<div translate class="tb-cell" flex="20">event.metadata</div>
+<div translate class="tb-cell" flex="20">event.error</div>
ui/src/app/event/event-row.directive.js 16(+14 -2)
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js
index f005542..4643761 100644
--- a/ui/src/app/event/event-row.directive.js
+++ b/ui/src/app/event/event-row.directive.js
@@ -20,6 +20,7 @@ import eventErrorDialogTemplate from './event-content-dialog.tpl.html';
import eventRowLcEventTemplate from './event-row-lc-event.tpl.html';
import eventRowStatsTemplate from './event-row-stats.tpl.html';
import eventRowErrorTemplate from './event-row-error.tpl.html';
+import eventRowDebugRuleNodeTemplate from './event-row-debug-rulenode.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -40,6 +41,12 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
case types.eventType.error.value:
template = eventRowErrorTemplate;
break;
+ case types.debugEventType.debugRuleNode.value:
+ template = eventRowDebugRuleNodeTemplate;
+ break;
+ case types.debugEventType.debugRuleChain.value:
+ template = eventRowDebugRuleNodeTemplate;
+ break;
}
return $templateCache.get(template);
}
@@ -53,17 +60,22 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
scope.loadTemplate();
});
+ scope.types = types;
+
scope.event = attrs.event;
- scope.showContent = function($event, content, title) {
+ scope.showContent = function($event, content, title, contentType) {
var onShowingCallback = {
onShowing: function(){}
}
+ if (!contentType) {
+ contentType = null;
+ }
$mdDialog.show({
controller: 'EventContentDialogController',
controllerAs: 'vm',
templateUrl: eventErrorDialogTemplate,
- locals: {content: content, title: title, showingCallback: onShowingCallback},
+ locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback},
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event,
diff --git a/ui/src/app/event/event-row-debug-rulenode.tpl.html b/ui/src/app/event/event-row-debug-rulenode.tpl.html
new file mode 100644
index 0000000..5b96baf
--- /dev/null
+++ b/ui/src/app/event/event-row-debug-rulenode.tpl.html
@@ -0,0 +1,63 @@
+<!--
+
+ 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 hide-xs hide-sm class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
+<div class="tb-cell" flex="20">{{event.body.server}}</div>
+<div class="tb-cell" flex="20">{{event.body.type}}</div>
+<div class="tb-cell" flex="20">{{event.body.entityName}}</div>
+<div class="tb-cell" flex="20">{{event.body.msgId}}</div>
+<div class="tb-cell" flex="20">{{event.body.msgType}}</div>
+<div class="tb-cell" flex="20">{{event.body.dataType}}</div>
+<div class="tb-cell" flex="20">
+ <md-button ng-if="event.body.data" class="md-icon-button md-primary"
+ ng-click="showContent($event, event.body.data, 'event.data', event.body.dataType)"
+ aria-label="{{ 'action.view' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'action.view' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.view' | translate }}"
+ class="material-icons">
+ more_horiz
+ </md-icon>
+ </md-button>
+</div>
+<div class="tb-cell" flex="20">
+ <md-button ng-if="event.body.metadata" class="md-icon-button md-primary"
+ ng-click="showContent($event, event.body.metadata, 'event.metadata', 'JSON')"
+ aria-label="{{ 'action.view' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'action.view' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.view' | translate }}"
+ class="material-icons">
+ more_horiz
+ </md-icon>
+ </md-button>
+</div>
+<div class="tb-cell" flex="20">
+ <md-button ng-if="event.body.error" class="md-icon-button md-primary"
+ ng-click="showContent($event, event.body.error, 'event.error')"
+ aria-label="{{ 'action.view' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'action.view' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.view' | translate }}"
+ class="material-icons">
+ more_horiz
+ </md-icon>
+ </md-button>
+</div>
ui/src/app/event/event-table.directive.js 18(+15 -3)
diff --git a/ui/src/app/event/event-table.directive.js b/ui/src/app/event/event-table.directive.js
index 4291014..c61078d 100644
--- a/ui/src/app/event/event-table.directive.js
+++ b/ui/src/app/event/event-table.directive.js
@@ -36,8 +36,8 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
for (var type in types.eventType) {
var eventType = types.eventType[type];
var enabled = true;
- for (var disabledType in disabledEventTypes) {
- if (eventType.value === disabledEventTypes[disabledType]) {
+ for (var i=0;i<disabledEventTypes.length;i++) {
+ if (eventType.value === disabledEventTypes[i]) {
enabled = false;
break;
}
@@ -47,7 +47,19 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
}
}
} else {
- scope.eventTypes = types.eventType;
+ scope.eventTypes = angular.copy(types.eventType);
+ }
+
+ if (attrs.debugEventTypes) {
+ var debugEventTypes = attrs.debugEventTypes.split(',');
+ for (i=0;i<debugEventTypes.length;i++) {
+ for (type in types.debugEventType) {
+ eventType = types.debugEventType[type];
+ if (eventType.value === debugEventTypes[i]) {
+ scope.eventTypes[type] = eventType;
+ }
+ }
+ }
}
scope.eventType = attrs.defaultEventType;
ui/src/app/locale/locale.constant.js 24(+23 -1)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index cca2a11..f616fec 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -341,6 +341,11 @@ export default angular.module('thingsboard.locale', [])
"enter-password": "Enter password",
"enter-search": "Enter search"
},
+ "content-type": {
+ "json": "Json",
+ "text": "Text",
+ "binary": "Binary (Base64)"
+ },
"customer": {
"customer": "Customer",
"customers": "Customers",
@@ -762,6 +767,8 @@ export default angular.module('thingsboard.locale', [])
"type-error": "Error",
"type-lc-event": "Lifecycle event",
"type-stats": "Statistics",
+ "type-debug-rule-node": "Debug",
+ "type-debug-rule-chain": "Debug",
"no-events-prompt": "No events found",
"error": "Error",
"alarm": "Alarm",
@@ -769,6 +776,13 @@ export default angular.module('thingsboard.locale', [])
"server": "Server",
"body": "Body",
"method": "Method",
+ "type": "Type",
+ "entity": "Entity",
+ "message-id": "Message Id",
+ "message-type": "Message Type",
+ "data-type": "Data Type",
+ "metadata": "Metadata",
+ "data": "Data",
"event": "Event",
"status": "Status",
"success": "Success",
@@ -1171,12 +1185,18 @@ export default angular.module('thingsboard.locale', [])
"debug-mode": "Debug mode"
},
"rulenode": {
+ "details": "Details",
+ "events": "Events",
+ "search": "Search nodes",
"add": "Add rule node",
"name": "Name",
"name-required": "Name is required.",
"type": "Type",
"description": "Description",
"delete": "Delete rule node",
+ "select-all": "Select all nodes and connections",
+ "deselect-all": "Deselect all nodes and connections",
+ "delete-selected-objects": "Delete selected nodes and connections",
"rulenode-details": "Rule node details",
"debug-mode": "Debug mode",
"configuration": "Configuration",
@@ -1195,7 +1215,9 @@ export default angular.module('thingsboard.locale', [])
"type-action": "Action",
"type-action-details": "Perform special action",
"type-rule-chain": "Rule Chain",
- "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain"
+ "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
+ "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
+ "ui-resources-load-error": "Failed to load configuration ui resources."
},
"rule-plugin": {
"management": "Rules and plugins management"
ui/src/app/rulechain/index.js 4(+4 -0)
diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js
index 7306762..c674467 100644
--- a/ui/src/app/rulechain/index.js
+++ b/ui/src/app/rulechain/index.js
@@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes';
import RuleChainsController from './rulechains.controller';
import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
import RuleChainDirective from './rulechain.directive';
+import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
+import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive';
@@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', [])
.controller('AddRuleNodeController', AddRuleNodeController)
.controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
.directive('tbRuleChain', RuleChainDirective)
+ .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
+ .directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective)
.name;
ui/src/app/rulechain/rulechain.controller.js 197(+118 -79)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index b792f13..7de72c3 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -27,15 +27,10 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
-
-const deleteKeyCode = 46;
-const ctrlKeyCode = 17;
-const aKeyCode = 65;
-const escKeyCode = 27;
-
/*@ngInject*/
-export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog,
- $filter, $translate, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) {
+export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
+ $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants,
+ ruleChain, ruleChainMetaData, ruleNodeComponents) {
var vm = this;
@@ -48,6 +43,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.editingRuleNodeLink = null;
vm.isEditingRuleNodeLink = false;
+ vm.isLibraryOpen = true;
+ vm.ruleNodeSearch = '';
+
vm.ruleChain = ruleChain;
vm.ruleChainMetaData = ruleChainMetaData;
@@ -76,39 +74,64 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects);
- vm.ctrlDown = false;
-
vm.saveRuleChain = saveRuleChain;
vm.revertRuleChain = revertRuleChain;
- vm.keyDown = function (evt) {
- if (evt.keyCode === ctrlKeyCode) {
- vm.ctrlDown = true;
- evt.stopPropagation();
- evt.preventDefault();
- }
- };
-
- vm.keyUp = function (evt) {
+ vm.objectsSelected = objectsSelected;
+ vm.deleteSelected = deleteSelected;
- if (evt.keyCode === deleteKeyCode) {
- vm.modelservice.deleteSelected();
- }
-
- if (evt.keyCode == aKeyCode && vm.ctrlDown) {
- vm.modelservice.selectAll();
- }
+ vm.triggerResize = triggerResize;
- if (evt.keyCode == escKeyCode) {
- vm.modelservice.deselectAll();
- }
+ initHotKeys();
- if (evt.keyCode === ctrlKeyCode) {
- vm.ctrlDown = false;
- evt.stopPropagation();
- evt.preventDefault();
- }
- };
+ function initHotKeys() {
+ hotkeys.bindTo($scope)
+ .add({
+ combo: 'ctrl+a',
+ description: $translate.instant('rulenode.select-all'),
+ allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+ callback: function (event) {
+ event.preventDefault();
+ vm.modelservice.selectAll();
+ }
+ })
+ .add({
+ combo: 'esc',
+ description: $translate.instant('rulenode.deselect-all'),
+ allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+ callback: function (event) {
+ event.preventDefault();
+ vm.modelservice.deselectAll();
+ }
+ })
+ .add({
+ combo: 'ctrl+s',
+ description: $translate.instant('action.apply'),
+ allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+ callback: function (event) {
+ event.preventDefault();
+ vm.saveRuleChain();
+ }
+ })
+ .add({
+ combo: 'ctrl+z',
+ description: $translate.instant('action.decline-changes'),
+ allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+ callback: function (event) {
+ event.preventDefault();
+ vm.revertRuleChain();
+ }
+ })
+ .add({
+ combo: 'del',
+ description: $translate.instant('rulenode.delete-selected-objects'),
+ allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+ callback: function (event) {
+ event.preventDefault();
+ vm.modelservice.deleteSelected();
+ }
+ })
+ }
vm.onEditRuleNodeClosed = function() {
vm.editingRuleNode = null;
@@ -119,15 +142,16 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
};
vm.saveRuleNode = function(theForm) {
- theForm.$setPristine();
- vm.isEditingRuleNode = false;
- vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
- vm.editingRuleNode = angular.copy(vm.editingRuleNode);
+ $scope.$broadcast('form-submit');
+ if (theForm.$valid) {
+ theForm.$setPristine();
+ vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
+ vm.editingRuleNode = angular.copy(vm.editingRuleNode);
+ }
};
vm.saveRuleNodeLink = function(theForm) {
theForm.$setPristine();
- vm.isEditingRuleNodeLink = false;
vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex] = vm.editingRuleNodeLink;
vm.editingRuleNodeLink = angular.copy(vm.editingRuleNodeLink);
};
@@ -235,6 +259,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
+ $mdUtil.nextTick(() => {
+ vm.ruleNodeLinkForm.$setPristine();
+ });
}
},
nodeCallbacks: {
@@ -245,6 +272,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.isEditingRuleNode = true;
vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
vm.editingRuleNode = angular.copy(node);
+ $mdUtil.nextTick(() => {
+ vm.ruleNodeForm.$setPristine();
+ });
}
}
},
@@ -286,44 +316,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
loadRuleChainLibrary();
function loadRuleChainLibrary() {
- ruleChainService.getRuleNodeComponents().then(
- (ruleNodeComponents) => {
- for (var i=0;i<ruleNodeComponents.length;i++) {
- var ruleNodeComponent = ruleNodeComponents[i];
- var componentType = ruleNodeComponent.type;
- var model = vm.ruleNodeTypesModel[componentType].model;
- var node = {
- id: model.nodes.length,
- component: ruleNodeComponent,
- name: '',
- nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
- icon: vm.types.ruleNodeType[componentType].icon,
- x: 30,
- y: 10+50*model.nodes.length,
- connectors: []
- };
- if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
- node.connectors.push(
- {
- type: flowchartConstants.leftConnectorType,
- id: model.nodes.length * 2
- }
- );
+ for (var i=0;i<ruleNodeComponents.length;i++) {
+ var ruleNodeComponent = ruleNodeComponents[i];
+ var componentType = ruleNodeComponent.type;
+ var model = vm.ruleNodeTypesModel[componentType].model;
+ var node = {
+ id: 'node-lib-' + componentType + '-' + model.nodes.length,
+ component: ruleNodeComponent,
+ name: '',
+ nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
+ icon: vm.types.ruleNodeType[componentType].icon,
+ x: 30,
+ y: 10+50*model.nodes.length,
+ connectors: []
+ };
+ if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
+ 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
- }
- );
+ );
+ }
+ if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {
+ node.connectors.push(
+ {
+ type: flowchartConstants.rightConnectorType,
+ id: model.nodes.length * 2 + 1
}
- model.nodes.push(node);
- }
- vm.ruleChainLibraryLoaded = true;
- prepareRuleChain();
+ );
}
- );
+ model.nodes.push(node);
+ }
+ vm.ruleChainLibraryLoaded = true;
+ prepareRuleChain();
}
function prepareRuleChain() {
@@ -344,7 +370,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.ruleChainModel.nodes.push(
{
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
component: types.inputNodeComponent,
name: "",
nodeClass: types.ruleNodeType.INPUT.nodeClass,
@@ -375,7 +401,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) {
var node = {
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id,
additionalInfo: ruleNode.additionalInfo,
configuration: ruleNode.configuration,
@@ -452,7 +478,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) {
ruleChainNode = {
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
additionalInfo: ruleChainConnection.additionalInfo,
targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
x: ruleChainConnection.additionalInfo.layoutX,
@@ -597,7 +623,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
fullscreen: true,
targetEvent: $event
}).then(function (ruleNode) {
- ruleNode.id = vm.nextNodeID++;
+ ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
ruleNode.connectors = [];
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
ruleNode.connectors.push(
@@ -632,6 +658,19 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
});
}
+ function objectsSelected() {
+ return vm.modelservice.nodes.getSelectedNodes().length > 0 ||
+ vm.modelservice.edges.getSelectedEdges().length > 0
+ }
+
+ function deleteSelected() {
+ vm.modelservice.deleteSelected();
+ }
+
+ function triggerResize() {
+ var w = angular.element($window);
+ w.triggerHandler('resize');
+ }
}
/*@ngInject*/
diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js
index 808661a..f9578ef 100644
--- a/ui/src/app/rulechain/rulechain.routes.js
+++ b/ui/src/app/rulechain/rulechain.routes.js
@@ -68,6 +68,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
/*@ngInject*/
function($stateParams, ruleChainService) {
return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
+ },
+ ruleNodeComponents:
+ /*@ngInject*/
+ function($stateParams, ruleChainService) {
+ return ruleChainService.getRuleNodeComponents();
}
},
data: {
ui/src/app/rulechain/rulechain.scss 143(+129 -14)
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 26a5225..38f785a 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -18,13 +18,58 @@
.tb-fullscreen-button-style {
z-index: 1;
}
+ section.tb-header-buttons.tb-library-open {
+ pointer-events: none;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ z-index: 1;
+ .md-button.tb-btn-open-library {
+ left: 0px;
+ top: 0px;
+ line-height: 36px;
+ width: 36px;
+ height: 36px;
+ margin: 4px 0 0 4px;
+ opacity: 0.5;
+ }
+ }
.tb-rulechain-library {
width: 250px;
min-width: 250px;
- overflow-y: auto;
- overflow-x: hidden;
-
+ z-index: 1;
+ md-toolbar {
+ min-height: 48px;
+ height: 48px;
+ .md-toolbar-tools>.md-button:last-child {
+ margin-right: 0px;
+ }
+ .md-toolbar-tools {
+ font-size: 14px;
+ padding: 0px 6px;
+ .md-button.md-icon-button {
+ margin: 0px;
+ &.tb-small {
+ height: 32px;
+ min-height: 32px;
+ line-height: 20px;
+ padding: 6px;
+ width: 32px;
+ md-icon {
+ line-height: 20px;
+ font-size: 20px;
+ height: 20px;
+ width: 20px;
+ min-height: 20px;
+ min-width: 20px;
+ }
+ }
+ }
+ }
+ }
.tb-rulechain-library-panel-group {
+ overflow-y: auto;
+ overflow-x: hidden;
.tb-panel-title {
-webkit-user-select: none;
-moz-user-select: none;
@@ -33,7 +78,7 @@
min-width: 180px;
}
.fc-canvas {
- background: none;
+ background: #f9f9f9;
}
md-icon.md-expansion-panel-icon {
margin-right: 0px;
@@ -55,6 +100,7 @@
}
}
.tb-rulechain-graph {
+ z-index: 0;
overflow: auto;
}
}
@@ -75,6 +121,7 @@
padding: 5px 10px;
border-radius: 5px;
background-color: #F15B26;
+ pointer-events: none;
color: #333;
border: solid 1px #777;
font-size: 12px;
@@ -121,10 +168,6 @@
.fc-node {
z-index: 1;
outline: none;
- &.fc-hover, &.fc-selected {
- -webkit-filter: brightness(70%);
- filter: brightness(70%);
- }
&.fc-dragging {
z-index: 10;
}
@@ -132,6 +175,26 @@
padding: 0 15px;
text-align: center;
}
+ .fc-node-overlay {
+ position: absolute;
+ pointer-events: none;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #000;
+ opacity: 0;
+ }
+ &.fc-hover {
+ .fc-node-overlay {
+ opacity: 0.25;
+ }
+ }
+ &.fc-selected {
+ .fc-node-overlay {
+ opacity: 0.25;
+ }
+ }
}
.fc-leftConnectors, .fc-rightConnectors {
@@ -170,17 +233,33 @@
margin: 10px;
border-radius: 5px;
background-color: #ccc;
+ pointer-events: all;
}
.fc-connector.fc-hover {
background-color: #000;
}
+.fc-arrow-marker {
+ polygon {
+ stroke: gray;
+ fill: gray;
+ }
+}
+
+.fc-arrow-marker-selected {
+ polygon {
+ stroke: red;
+ fill: red;
+ }
+}
+
.fc-edge {
outline: none;
stroke: gray;
stroke-width: 4;
fill: transparent;
+ transition: stroke-width .2s;
&.fc-selected {
stroke: red;
stroke-width: 4;
@@ -229,24 +308,53 @@
cursor: pointer;
}
+.fc-noselect {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
+}
+
.fc-edge-label {
position: absolute;
- user-select: none;
- pointer-events: none;
+ transition: transform .2s;
opacity: 0.8;
+ &.ng-leave {
+ transition: 0s none;
+ }
+ &.fc-hover {
+ transform: scale(1.25);
+ }
+ &.fc-selected {
+ .fc-edge-label-text {
+ span {
+ border: solid red;
+ color: red;
+ }
+ }
+ }
+ .fc-nodedelete {
+ right: -13px;
+ top: -30px;
+ }
+ &:focus {
+ outline: 0;
+ }
}
.fc-edge-label-text {
position: absolute;
- left: 50%;
- -webkit-transform: translateX(-50%);
- transform: translateX(-50%);
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
white-space: nowrap;
text-align: center;
font-size: 14px;
font-weight: 600;
- top: 5px;
span {
+ cursor: default;
border: solid 2px #003a79;
border-radius: 10px;
color: #003a79;
@@ -255,6 +363,13 @@
}
}
+.fc-select-rectangle {
+ border: 2px dashed #5262ff;
+ position: absolute;
+ background: rgba(20,125,255,0.1);
+ z-index: 2;
+}
+
@keyframes dash {
from {
stroke-dashoffset: 500;
ui/src/app/rulechain/rulechain.tpl.html 108(+89 -19)
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 9f1141e..ddc1a90 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -16,12 +16,60 @@
-->
-<md-content flex tb-expand-fullscreen
- expand-tooltip-direction="bottom" layout="column" class="tb-rulechain">
+<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty"
+ expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"
+ ng-keydown="vm.keyDown($event)"
+ ng-keyup="vm.keyUp($event)">
<section class="tb-rulechain-container" flex layout="column">
<div class="tb-rulechain-layout" flex layout="row">
- <div class="tb-rulechain-library">
- <md-expansion-panel-group ng-if="vm.ruleChainLibraryLoaded" class="tb-rulechain-library-panel-group" md-component-id="libraryPanelGroup" auto-expand="true" multiple>
+ <section layout="row" layout-wrap
+ class="tb-header-buttons md-fab tb-library-open">
+ <md-button ng-show="!vm.isLibraryOpen"
+ class="tb-btn-header tb-btn-open-library md-primary md-fab md-fab-top-left"
+ aria-label="{{ 'action.apply' | translate }}"
+ ng-click="vm.isLibraryOpen = true">
+ <md-tooltip md-direction="top">
+ {{ 'action.apply-changes' | translate }}
+ </md-tooltip>
+ <ng-md-icon icon="menu"></ng-md-icon>
+ </md-button>
+ </section>
+ <md-sidenav class="tb-rulechain-library md-sidenav-left md-whiteframe-4dp"
+ md-disable-backdrop
+ md-is-locked-open="vm.isLibraryOpen"
+ md-is-open="vm.isLibraryOpen"
+ md-component-id="rulechain-library-sidenav" layout="column">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <md-button class="md-icon-button tb-small" aria-label="{{ 'action.search' | translate }}">
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+ <md-tooltip md-direction="top">
+ {{'rulenode.search' | translate}}
+ </md-tooltip>
+ </md-button>
+ <div layout="row" md-theme="tb-dark" flex>
+ <md-input-container flex>
+ <label> </label>
+ <input ng-model="vm.ruleNodeSearch" placeholder="{{'rulenode.search' | translate}}"/>
+ </md-input-container>
+ </div>
+ <md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.ruleNodeSearch = ''">
+ <md-icon aria-label="Close" class="material-icons">close</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.isLibraryOpen = false">
+ <md-icon aria-label="Close" class="material-icons">chevron_left</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-expansion-panel-group flex
+ ng-if="vm.ruleChainLibraryLoaded" class="tb-rulechain-library-panel-group"
+ md-component-id="libraryPanelGroup" auto-expand="true" multiple>
<md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
<md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
ng-mouseleave="vm.destroyTooltips()">
@@ -47,11 +95,9 @@
</md-expansion-panel-expanded>
</md-expansion-panel>
</md-expansion-panel-group>
- </div>
+ </md-sidenav>
<div flex class="tb-rulechain-graph">
<fc-canvas id="tb-rulchain-canvas"
- ng-keydown="vm.keyDown($event)"
- ng-keyup="vm.keyUp($event)"
model="vm.ruleChainModel"
selected-objects="vm.selectedObjects"
edge-style="curved"
@@ -65,9 +111,11 @@
</div>
<tb-details-sidenav class="tb-rulenode-details-sidenav"
header-title="{{vm.editingRuleNode.name}}"
- header-subtitle="{{'rulenode.rulenode-details' | translate}}"
- is-read-only="false"
+ header-subtitle="{{(vm.types.ruleNodeType[vm.editingRuleNode.component.type].name | translate)
+ + ' - ' + vm.editingRuleNode.component.name}}"
+ is-read-only="vm.selectedRuleNodeTabIndex > 0"
is-open="vm.isEditingRuleNode"
+ tb-enable-backdrop
is-always-edit="true"
on-close-details="vm.onEditRuleNodeClosed()"
on-toggle-details-edit-mode="vm.onRevertRuleNodeEdit(vm.ruleNodeForm)"
@@ -76,22 +124,37 @@
<details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
- <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">
- <tb-rule-node
- rule-node="vm.editingRuleNode"
- rule-chain-id="vm.ruleChain.id.id"
- is-edit="true"
- is-read-only="false"
- on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"
- the-form="vm.ruleNodeForm">
- </tb-rule-node>
- </form>
+ <md-tabs md-selected="vm.selectedRuleNodeTabIndex"
+ id="ruleNodeTabs" md-border-bottom flex class="tb-absolute-fill" ng-if="vm.isEditingRuleNode">
+ <md-tab label="{{ 'rulenode.details' | translate }}">
+ <form name="vm.ruleNodeForm">
+ <tb-rule-node
+ rule-node="vm.editingRuleNode"
+ rule-chain-id="vm.ruleChain.id.id"
+ is-edit="true"
+ is-read-only="false"
+ on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"
+ the-form="vm.ruleNodeForm">
+ </tb-rule-node>
+ </form>
+ </md-tab>
+ <md-tab ng-if="vm.isEditingRuleNode && vm.editingRuleNode.ruleNodeId"
+ md-on-select="vm.triggerResize()" label="{{ 'rulenode.events' | translate }}">
+ <tb-event-table flex entity-type="vm.types.entityType.rulenode"
+ entity-id="vm.editingRuleNode.ruleNodeId.id"
+ tenant-id="vm.ruleChain.tenantId.id"
+ debug-event-types="{{vm.types.debugEventType.debugRuleNode.value}}"
+ default-event-type="{{vm.types.debugEventType.debugRuleNode.value}}">
+ </tb-event-table>
+ </md-tab>
+ </md-tabs>
</tb-details-sidenav>
<tb-details-sidenav class="tb-rulenode-link-details-sidenav"
header-title="{{vm.editingRuleNodeLink.label}}"
header-subtitle="{{'rulenode.link-details' | translate}}"
is-read-only="false"
is-open="vm.isEditingRuleNodeLink"
+ tb-enable-backdrop
is-always-edit="true"
on-close-details="vm.onEditRuleNodeLinkClosed()"
on-toggle-details-edit-mode="vm.onRevertRuleNodeLinkEdit(vm.ruleNodeLinkForm)"
@@ -112,6 +175,13 @@
</tb-details-sidenav>
</section>
<section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
+ <md-button ng-disabled="$root.loading" ng-show="vm.objectsSelected()" class="tb-btn-footer md-accent md-hue-2 md-fab"
+ ng-click="vm.deleteSelected()" aria-label="{{ 'action.delete' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'rulenode.delete-selected-objects' | translate }}
+ </md-tooltip>
+ <ng-md-icon icon="delete"></ng-md-icon>
+ </md-button>
<md-button ng-disabled="$root.loading || !vm.isDirty"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html
index a4fbd79..cf9d256 100644
--- a/ui/src/app/rulechain/rulechains.tpl.html
+++ b/ui/src/app/rulechain/rulechains.tpl.html
@@ -55,7 +55,8 @@
<tb-event-table flex entity-type="vm.types.entityType.rulechain"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.lcEvent.value}}">
+ debug-event-types="{{vm.types.debugEventType.debugRuleChain.value}}"
+ default-event-type="{{vm.types.debugEventType.debugRuleChain.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
ui/src/app/rulechain/rulenode.scss 6(+6 -0)
diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss
index febc637..0466673 100644
--- a/ui/src/app/rulechain/rulenode.scss
+++ b/ui/src/app/rulechain/rulenode.scss
@@ -19,4 +19,10 @@
height: 300px;
display: block;
}
+}
+
+.tb-rulenode-directive-error {
+ color: rgb(221,44,0);
+ font-size: 13px;
+ font-weight: 400;
}
\ No newline at end of file
diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html
index ffc8a0f..55ee3d3 100644
--- a/ui/src/app/rulechain/rulenode.tpl.html
+++ b/ui/src/app/rulechain/rulenode.tpl.html
@@ -22,6 +22,7 @@
ng-mousedown="callbacks.mouseDown($event, node)"
ng-mouseenter="callbacks.mouseEnter($event, node)"
ng-mouseleave="callbacks.mouseLeave($event, node)">
+ <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
<div class="tb-rule-node {{node.nodeClass}}">
<md-icon aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon>
diff --git a/ui/src/app/rulechain/rulenode-config.directive.js b/ui/src/app/rulechain/rulenode-config.directive.js
new file mode 100644
index 0000000..9bb8c48
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-config.directive.js
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import ruleNodeConfigTemplate from './rulenode-config.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RuleNodeConfigDirective($compile, $templateCache, $injector, $translate) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(ruleNodeConfigTemplate);
+ element.html(template);
+
+ scope.$watch('configuration', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ ngModelCtrl.$setViewValue(scope.configuration);
+ }
+ });
+
+ ngModelCtrl.$render = function () {
+ scope.configuration = ngModelCtrl.$viewValue;
+ };
+
+ scope.useDefinedDirective = function() {
+ return scope.nodeDefinition &&
+ scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
+ };
+
+ scope.$watch('nodeDefinition', () => {
+ if (scope.nodeDefinition) {
+ validateDefinedDirective();
+ }
+ });
+
+ function validateDefinedDirective() {
+ if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
+ scope.definedDirectiveError = scope.nodeDefinition.uiResourceLoadError;
+ } else {
+ var definedDirective = scope.nodeDefinition.configDirective;
+ if (definedDirective && definedDirective.length) {
+ if (!$injector.has(definedDirective + 'Directive')) {
+ scope.definedDirectiveError = $translate.instant('rulenode.directive-is-not-loaded', {directiveName: definedDirective});
+ }
+ }
+ }
+ }
+
+ $compile(element.contents())(scope);
+ };
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ scope: {
+ nodeDefinition:'=',
+ required:'=ngRequired',
+ readonly:'=ngReadonly'
+ },
+ link: linker
+ };
+
+}
diff --git a/ui/src/app/rulechain/rulenode-config.tpl.html b/ui/src/app/rulechain/rulenode-config.tpl.html
new file mode 100644
index 0000000..32d5347
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-config.tpl.html
@@ -0,0 +1,32 @@
+<!--
+
+ 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-rule-node-defined-config ng-if="useDefinedDirective()"
+ ng-model="configuration"
+ rule-node-directive="{{nodeDefinition.configDirective}}"
+ ng-required="required"
+ ng-readonly="readonly">
+</tb-rule-node-defined-config>
+<div class="tb-rulenode-directive-error" ng-if="definedDirectiveError">{{definedDirectiveError}}</div>
+<tb-json-object-edit ng-if="!useDefinedDirective()"
+ class="tb-rule-node-configuration-json"
+ ng-model="configuration"
+ label="{{ 'rulenode.configuration' | translate }}"
+ ng-required="required"
+ fill-height="true">
+</tb-json-object-edit>
diff --git a/ui/src/app/rulechain/rulenode-defined-config.directive.js b/ui/src/app/rulechain/rulenode-defined-config.directive.js
new file mode 100644
index 0000000..5100fbb
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-defined-config.directive.js
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+const SNAKE_CASE_REGEXP = /[A-Z]/g;
+
+/*@ngInject*/
+export default function RuleNodeDefinedConfigDirective($compile) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ attrs.$observe('ruleNodeDirective', function() {
+ loadTemplate();
+ });
+
+ scope.$watch('configuration', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ ngModelCtrl.$setViewValue(scope.configuration);
+ }
+ });
+
+ ngModelCtrl.$render = function () {
+ scope.configuration = ngModelCtrl.$viewValue;
+ };
+
+ function loadTemplate() {
+ if (scope.ruleNodeConfigScope) {
+ scope.ruleNodeConfigScope.$destroy();
+ }
+ var directive = snake_case(attrs.ruleNodeDirective, '-');
+ var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
+ element.html(template);
+ scope.ruleNodeConfigScope = scope.$new();
+ $compile(element.contents())(scope.ruleNodeConfigScope);
+ }
+
+ function snake_case(name, separator) {
+ separator = separator || '_';
+ return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
+ };
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ scope: {
+ required:'=ngRequired',
+ readonly:'=ngReadonly'
+ },
+ link: linker
+ };
+
+}
ui/src/app/rulechain/rulenode-fieldset.tpl.html 38(+18 -20)
diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
index 30cf075..7b0fae5 100644
--- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
@@ -21,28 +21,26 @@
<md-content class="md-padding tb-rulenode" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
- <md-input-container class="md-block">
- <label translate>rulenode.type</label>
- <input readonly name="type" ng-model="ruleNode.component.name">
- </md-input-container>
<section ng-if="ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value">
- <md-input-container class="md-block">
- <label translate>rulenode.name</label>
- <input required name="name" ng-model="ruleNode.name">
- <div ng-messages="theForm.name.$error">
- <div translate ng-message="required">rulenode.name-required</div>
- </div>
- </md-input-container>
- <md-input-container class="md-block">
- <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"
- 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 }}"
+ <section layout="column" layout-gt-sm="row">
+ <md-input-container flex class="md-block">
+ <label translate>rulenode.name</label>
+ <input required name="name" ng-model="ruleNode.name">
+ <div ng-messages="theForm.name.$error">
+ <div translate ng-message="required">rulenode.name-required</div>
+ </div>
+ </md-input-container>
+ <md-input-container class="md-block">
+ <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"
+ ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
+ </md-checkbox>
+ </md-input-container>
+ </section>
+ <tb-rule-node-config ng-model="ruleNode.configuration"
ng-required="true"
- fill-height="true">
- </tb-json-object-edit>
+ node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
+ ng-readonly="$root.loading || !isEdit || isReadOnly">
+ </tb-rule-node-config>
<md-input-container class="md-block">
<label translate>rulenode.description</label>
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>