Commit 9352e19135223c340e199cc641ae31e091ef82bf

Authored by deaflynx
2 parents ac18f1aa d34c54b3

Merge remote-tracking branch 'origin/feature/edge' into feature/edge

Showing 78 changed files with 1859 additions and 290 deletions
@@ -99,6 +99,10 @@ @@ -99,6 +99,10 @@
99 </dependency> 99 </dependency>
100 <dependency> 100 <dependency>
101 <groupId>org.thingsboard.common</groupId> 101 <groupId>org.thingsboard.common</groupId>
  102 + <artifactId>stats</artifactId>
  103 + </dependency>
  104 + <dependency>
  105 + <groupId>org.thingsboard.common</groupId>
102 <artifactId>edge-api</artifactId> 106 <artifactId>edge-api</artifactId>
103 </dependency> 107 </dependency>
104 <dependency> 108 <dependency>
  1 +-----BEGIN CERTIFICATE-----
  2 +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
  3 +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
  4 +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
  5 +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
  6 +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
  7 +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
  8 +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
  9 +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
  10 +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
  11 +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
  12 +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
  13 +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
  14 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
  15 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
  16 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
  17 +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
  18 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
  19 +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
  20 +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
  21 +-----END CERTIFICATE-----
  22 +
@@ -29,7 +29,8 @@ import java.util.Arrays; @@ -29,7 +29,8 @@ import java.util.Arrays;
29 @ComponentScan({"org.thingsboard.server.install", 29 @ComponentScan({"org.thingsboard.server.install",
30 "org.thingsboard.server.service.component", 30 "org.thingsboard.server.service.component",
31 "org.thingsboard.server.service.install", 31 "org.thingsboard.server.service.install",
32 - "org.thingsboard.server.dao"}) 32 + "org.thingsboard.server.dao",
  33 + "org.thingsboard.server.common.stats"})
33 public class ThingsboardInstallApplication { 34 public class ThingsboardInstallApplication {
34 35
35 private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; 36 private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
@@ -221,6 +221,10 @@ public class ActorSystemContext { @@ -221,6 +221,10 @@ public class ActorSystemContext {
221 @Getter 221 @Getter
222 private ClaimDevicesService claimDevicesService; 222 private ClaimDevicesService claimDevicesService;
223 223
  224 + @Autowired
  225 + @Getter
  226 + private JsInvokeStats jsInvokeStats;
  227 +
224 //TODO: separate context for TbCore and TbRuleEngine 228 //TODO: separate context for TbCore and TbRuleEngine
225 @Autowired(required = false) 229 @Autowired(required = false)
226 @Getter 230 @Getter
@@ -284,19 +288,14 @@ public class ActorSystemContext { @@ -284,19 +288,14 @@ public class ActorSystemContext {
284 @Getter 288 @Getter
285 private long statisticsPersistFrequency; 289 private long statisticsPersistFrequency;
286 290
287 - @Getter  
288 - private final AtomicInteger jsInvokeRequestsCount = new AtomicInteger(0);  
289 - @Getter  
290 - private final AtomicInteger jsInvokeResponsesCount = new AtomicInteger(0);  
291 - @Getter  
292 - private final AtomicInteger jsInvokeFailuresCount = new AtomicInteger(0);  
293 291
294 @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}") 292 @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
295 public void printStats() { 293 public void printStats() {
296 if (statisticsEnabled) { 294 if (statisticsEnabled) {
297 - if (jsInvokeRequestsCount.get() > 0 || jsInvokeResponsesCount.get() > 0 || jsInvokeFailuresCount.get() > 0) { 295 + if (jsInvokeStats.getRequests() > 0 || jsInvokeStats.getResponses() > 0 || jsInvokeStats.getFailures() > 0) {
298 log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]", 296 log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]",
299 - jsInvokeRequestsCount.getAndSet(0), jsInvokeResponsesCount.getAndSet(0), jsInvokeFailuresCount.getAndSet(0)); 297 + jsInvokeStats.getRequests(), jsInvokeStats.getResponses(), jsInvokeStats.getFailures());
  298 + jsInvokeStats.reset();
300 } 299 }
301 } 300 }
302 } 301 }
@@ -132,6 +132,9 @@ class DefaultTbContext implements TbContext { @@ -132,6 +132,9 @@ class DefaultTbContext implements TbContext {
132 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) 132 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
133 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) 133 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
134 .setTbMsg(TbMsg.toByteString(tbMsg)).build(); 134 .setTbMsg(TbMsg.toByteString(tbMsg)).build();
  135 + if (nodeCtx.getSelf().isDebugMode()) {
  136 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "To Root Rule Chain");
  137 + }
135 mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure)); 138 mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure));
136 } 139 }
137 140
@@ -177,10 +180,10 @@ class DefaultTbContext implements TbContext { @@ -177,10 +180,10 @@ class DefaultTbContext implements TbContext {
177 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure); 180 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
178 } 181 }
179 182
180 - private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg tbMsg, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { 183 + private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
181 RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); 184 RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
182 RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); 185 RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
183 - tbMsg = TbMsg.newMsg(tbMsg, ruleChainId, ruleNodeId); 186 + TbMsg tbMsg = TbMsg.newMsg(source, ruleChainId, ruleNodeId);
184 TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder() 187 TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder()
185 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) 188 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
186 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) 189 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
@@ -189,6 +192,10 @@ class DefaultTbContext implements TbContext { @@ -189,6 +192,10 @@ class DefaultTbContext implements TbContext {
189 if (failureMessage != null) { 192 if (failureMessage != null) {
190 msg.setFailureMessage(failureMessage); 193 msg.setFailureMessage(failureMessage);
191 } 194 }
  195 + if (nodeCtx.getSelf().isDebugMode()) {
  196 + relationTypes.forEach(relationType ->
  197 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, relationType));
  198 + }
192 mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure)); 199 mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure));
193 } 200 }
194 201
@@ -294,21 +301,21 @@ class DefaultTbContext implements TbContext { @@ -294,21 +301,21 @@ class DefaultTbContext implements TbContext {
294 @Override 301 @Override
295 public void logJsEvalRequest() { 302 public void logJsEvalRequest() {
296 if (mainCtx.isStatisticsEnabled()) { 303 if (mainCtx.isStatisticsEnabled()) {
297 - mainCtx.getJsInvokeRequestsCount().incrementAndGet(); 304 + mainCtx.getJsInvokeStats().incrementRequests();
298 } 305 }
299 } 306 }
300 307
301 @Override 308 @Override
302 public void logJsEvalResponse() { 309 public void logJsEvalResponse() {
303 if (mainCtx.isStatisticsEnabled()) { 310 if (mainCtx.isStatisticsEnabled()) {
304 - mainCtx.getJsInvokeResponsesCount().incrementAndGet(); 311 + mainCtx.getJsInvokeStats().incrementResponses();
305 } 312 }
306 } 313 }
307 314
308 @Override 315 @Override
309 public void logJsEvalFailure() { 316 public void logJsEvalFailure() {
310 if (mainCtx.isStatisticsEnabled()) { 317 if (mainCtx.isStatisticsEnabled()) {
311 - mainCtx.getJsInvokeFailuresCount().incrementAndGet(); 318 + mainCtx.getJsInvokeStats().incrementFailures();
312 } 319 }
313 } 320 }
314 321
@@ -168,7 +168,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -168,7 +168,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
168 private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) { 168 private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) {
169 return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()), 169 return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()),
170 () -> DefaultActorService.RULE_DISPATCHER_NAME, 170 () -> DefaultActorService.RULE_DISPATCHER_NAME,
171 - () -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getName(), ruleNode.getId())); 171 + () -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleChainName, ruleNode.getId()));
172 } 172 }
173 173
174 private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) { 174 private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
27 import org.thingsboard.server.common.data.rule.RuleNode; 27 import org.thingsboard.server.common.data.rule.RuleNode;
28 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; 28 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
29 import org.thingsboard.server.common.msg.queue.RuleNodeException; 29 import org.thingsboard.server.common.msg.queue.RuleNodeException;
  30 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
30 31
31 /** 32 /**
32 * @author Andrew Shvayka 33 * @author Andrew Shvayka
@@ -38,6 +39,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod @@ -38,6 +39,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
38 private RuleNode ruleNode; 39 private RuleNode ruleNode;
39 private TbNode tbNode; 40 private TbNode tbNode;
40 private DefaultTbContext defaultCtx; 41 private DefaultTbContext defaultCtx;
  42 + private RuleNodeInfo info;
41 43
42 RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext 44 RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
43 , TbActorRef parent, TbActorRef self) { 45 , TbActorRef parent, TbActorRef self) {
@@ -46,6 +48,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod @@ -46,6 +48,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
46 this.self = self; 48 this.self = self;
47 this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId); 49 this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
48 this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode)); 50 this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
  51 + this.info = new RuleNodeInfo(ruleNodeId, ruleChainName, ruleNode != null ? ruleNode.getName() : "Unknown");
49 } 52 }
50 53
51 @Override 54 @Override
@@ -59,6 +62,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod @@ -59,6 +62,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
59 @Override 62 @Override
60 public void onUpdate(TbActorCtx context) throws Exception { 63 public void onUpdate(TbActorCtx context) throws Exception {
61 RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId); 64 RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
  65 + this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
62 boolean restartRequired = state != ComponentLifecycleState.ACTIVE || 66 boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
63 !(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration())); 67 !(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
64 this.ruleNode = newRuleNode; 68 this.ruleNode = newRuleNode;
@@ -99,6 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod @@ -99,6 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
99 } 103 }
100 104
101 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { 105 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
  106 + msg.getMsg().getCallback().visit(info);
102 checkActive(msg.getMsg()); 107 checkActive(msg.getMsg());
103 if (ruleNode.isDebugMode()) { 108 if (ruleNode.isDebugMode()) {
104 systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); 109 systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
@@ -962,18 +962,18 @@ public final class EdgeGrpcSession implements Closeable { @@ -962,18 +962,18 @@ public final class EdgeGrpcSession implements Closeable {
962 switch (alarmUpdateMsg.getMsgType()) { 962 switch (alarmUpdateMsg.getMsgType()) {
963 case ENTITY_CREATED_RPC_MESSAGE: 963 case ENTITY_CREATED_RPC_MESSAGE:
964 case ENTITY_UPDATED_RPC_MESSAGE: 964 case ENTITY_UPDATED_RPC_MESSAGE:
965 - if (existentAlarm == null) { 965 + if (existentAlarm == null || existentAlarm.getStatus().isCleared()) {
966 existentAlarm = new Alarm(); 966 existentAlarm = new Alarm();
967 existentAlarm.setTenantId(edge.getTenantId()); 967 existentAlarm.setTenantId(edge.getTenantId());
968 existentAlarm.setType(alarmUpdateMsg.getName()); 968 existentAlarm.setType(alarmUpdateMsg.getName());
969 existentAlarm.setOriginator(originatorId); 969 existentAlarm.setOriginator(originatorId);
970 existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); 970 existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
971 - existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));  
972 existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); 971 existentAlarm.setStartTs(alarmUpdateMsg.getStartTs());
973 - existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());  
974 existentAlarm.setClearTs(alarmUpdateMsg.getClearTs()); 972 existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
975 existentAlarm.setPropagate(alarmUpdateMsg.getPropagate()); 973 existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
976 } 974 }
  975 + existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));
  976 + existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
977 existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); 977 existentAlarm.setEndTs(alarmUpdateMsg.getEndTs());
978 existentAlarm.setDetails(mapper.readTree(alarmUpdateMsg.getDetails())); 978 existentAlarm.setDetails(mapper.readTree(alarmUpdateMsg.getDetails()));
979 ctx.getAlarmService().createOrUpdateAlarm(existentAlarm); 979 ctx.getAlarmService().createOrUpdateAlarm(existentAlarm);
@@ -15,14 +15,20 @@ @@ -15,14 +15,20 @@
15 */ 15 */
16 package org.thingsboard.server.service.edge.rpc.constructor; 16 package org.thingsboard.server.service.edge.rpc.constructor;
17 17
  18 +import com.google.gson.Gson;
  19 +import com.google.gson.JsonArray;
18 import com.google.gson.JsonElement; 20 import com.google.gson.JsonElement;
  21 +import com.google.gson.JsonObject;
19 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
21 import org.thingsboard.server.common.data.audit.ActionType; 24 import org.thingsboard.server.common.data.audit.ActionType;
22 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 26 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  27 +import org.thingsboard.server.gen.edge.AttributeDeleteMsg;
24 import org.thingsboard.server.gen.edge.EntityDataProto; 28 import org.thingsboard.server.gen.edge.EntityDataProto;
25 29
  30 +import java.util.List;
  31 +
26 @Component 32 @Component
27 @Slf4j 33 @Slf4j
28 public class EntityDataMsgConstructor { 34 public class EntityDataMsgConstructor {
@@ -42,13 +48,26 @@ public class EntityDataMsgConstructor { @@ -42,13 +48,26 @@ public class EntityDataMsgConstructor {
42 break; 48 break;
43 case ATTRIBUTES_UPDATED: 49 case ATTRIBUTES_UPDATED:
44 try { 50 try {
45 - builder.setPostAttributesMsg(JsonConverter.convertToAttributesProto(entityData)); 51 + JsonObject data = entityData.getAsJsonObject();
  52 + builder.setPostAttributesMsg(JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")));
  53 + builder.setPostAttributeScope(data.getAsJsonPrimitive("scope").getAsString());
46 } catch (Exception e) { 54 } catch (Exception e) {
47 log.warn("Can't convert to attributes proto, entityData [{}]", entityData, e); 55 log.warn("Can't convert to attributes proto, entityData [{}]", entityData, e);
48 } 56 }
49 break; 57 break;
50 - // TODO: voba - add support for attribute delete  
51 - // case ATTRIBUTES_DELETED: 58 + case ATTRIBUTES_DELETED:
  59 + try {
  60 + AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder();
  61 + attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString());
  62 + JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys");
  63 + List<String> keys = new Gson().fromJson(jsonArray.toString(), List.class);
  64 + attributeDeleteMsg.addAllAttributeNames(keys);
  65 + attributeDeleteMsg.build();
  66 + builder.setAttributeDeleteMsg(attributeDeleteMsg);
  67 + } catch (Exception e) {
  68 + log.warn("Can't convert to AttributeDeleteMsg proto, entityData [{}]", entityData, e);
  69 + }
  70 + break;
52 } 71 }
53 return builder.build(); 72 return builder.build();
54 } 73 }
@@ -45,8 +45,8 @@ public class EntityViewUpdateMsgConstructor { @@ -45,8 +45,8 @@ public class EntityViewUpdateMsgConstructor {
45 .setIdLSB(entityView.getId().getId().getLeastSignificantBits()) 45 .setIdLSB(entityView.getId().getId().getLeastSignificantBits())
46 .setName(entityView.getName()) 46 .setName(entityView.getName())
47 .setType(entityView.getType()) 47 .setType(entityView.getType())
48 - .setIdMSB(entityView.getEntityId().getId().getMostSignificantBits())  
49 - .setIdLSB(entityView.getEntityId().getId().getLeastSignificantBits()) 48 + .setEntityIdMSB(entityView.getEntityId().getId().getMostSignificantBits())
  49 + .setEntityIdLSB(entityView.getEntityId().getId().getLeastSignificantBits())
50 .setEntityType(entityType); 50 .setEntityType(entityType);
51 return builder.build(); 51 return builder.build();
52 } 52 }
@@ -71,7 +71,9 @@ import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg; @@ -71,7 +71,9 @@ import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg;
71 import org.thingsboard.server.service.executors.DbCallbackExecutorService; 71 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
72 72
73 import java.util.ArrayList; 73 import java.util.ArrayList;
  74 +import java.util.HashMap;
74 import java.util.List; 75 import java.util.List;
  76 +import java.util.Map;
75 import java.util.UUID; 77 import java.util.UUID;
76 78
77 @Service 79 @Service
@@ -289,25 +291,29 @@ public class DefaultSyncEdgeService implements SyncEdgeService { @@ -289,25 +291,29 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
289 public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) { 291 public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) {
290 if (ssAttributes != null && !ssAttributes.isEmpty()) { 292 if (ssAttributes != null && !ssAttributes.isEmpty()) {
291 try { 293 try {
292 - ObjectNode entityNode = mapper.createObjectNode(); 294 + Map<String, Object> entityData = new HashMap<>();
  295 + ObjectNode attributes = mapper.createObjectNode();
293 for (AttributeKvEntry attr : ssAttributes) { 296 for (AttributeKvEntry attr : ssAttributes) {
294 if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { 297 if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {
295 - entityNode.put(attr.getKey(), attr.getBooleanValue().get()); 298 + attributes.put(attr.getKey(), attr.getBooleanValue().get());
296 } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { 299 } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) {
297 - entityNode.put(attr.getKey(), attr.getDoubleValue().get()); 300 + attributes.put(attr.getKey(), attr.getDoubleValue().get());
298 } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) { 301 } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
299 - entityNode.put(attr.getKey(), attr.getLongValue().get()); 302 + attributes.put(attr.getKey(), attr.getLongValue().get());
300 } else { 303 } else {
301 - entityNode.put(attr.getKey(), attr.getValueAsString()); 304 + attributes.put(attr.getKey(), attr.getValueAsString());
302 } 305 }
303 } 306 }
304 - log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, entityNode); 307 + entityData.put("kv", attributes);
  308 + entityData.put("scope", DataConstants.SERVER_SCOPE);
  309 + JsonNode entityBody = mapper.valueToTree(entityData);
  310 + log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, entityBody);
305 saveEdgeEvent(edge.getTenantId(), 311 saveEdgeEvent(edge.getTenantId(),
306 edge.getId(), 312 edge.getId(),
307 edgeEventType, 313 edgeEventType,
308 ActionType.ATTRIBUTES_UPDATED, 314 ActionType.ATTRIBUTES_UPDATED,
309 entityId, 315 entityId,
310 - entityNode); 316 + entityBody);
311 } catch (Exception e) { 317 } catch (Exception e) {
312 log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e); 318 log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e);
313 } 319 }
@@ -26,21 +26,12 @@ import org.thingsboard.server.common.msg.MsgType; @@ -26,21 +26,12 @@ import org.thingsboard.server.common.msg.MsgType;
26 import org.thingsboard.server.common.msg.TbActorMsg; 26 import org.thingsboard.server.common.msg.TbActorMsg;
27 import org.thingsboard.server.common.msg.queue.ServiceType; 27 import org.thingsboard.server.common.msg.queue.ServiceType;
28 import org.thingsboard.server.common.msg.queue.TbCallback; 28 import org.thingsboard.server.common.msg.queue.TbCallback;
29 -import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;  
30 -import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;  
31 -import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;  
32 -import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;  
33 -import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;  
34 -import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;  
35 -import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;  
36 -import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;  
37 -import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;  
38 -import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;  
39 -import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; 29 +import org.thingsboard.server.gen.transport.TransportProtos.*;
40 import org.thingsboard.server.queue.TbQueueConsumer; 30 import org.thingsboard.server.queue.TbQueueConsumer;
41 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 31 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
42 import org.thingsboard.server.queue.discovery.PartitionChangeEvent; 32 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
43 import org.thingsboard.server.queue.provider.TbCoreQueueFactory; 33 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
  34 +import org.thingsboard.server.common.stats.StatsFactory;
44 import org.thingsboard.server.queue.util.TbCoreComponent; 35 import org.thingsboard.server.queue.util.TbCoreComponent;
45 import org.thingsboard.server.service.edge.EdgeNotificationService; 36 import org.thingsboard.server.service.edge.EdgeNotificationService;
46 import org.thingsboard.server.service.encoding.DataDecodingEncodingService; 37 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
@@ -83,19 +74,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -83,19 +74,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
83 private final TbLocalSubscriptionService localSubscriptionService; 74 private final TbLocalSubscriptionService localSubscriptionService;
84 private final SubscriptionManagerService subscriptionManagerService; 75 private final SubscriptionManagerService subscriptionManagerService;
85 private final TbCoreDeviceRpcService tbCoreDeviceRpcService; 76 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
  77 + private final TbCoreConsumerStats stats;
86 private final EdgeNotificationService edgeNotificationService; 78 private final EdgeNotificationService edgeNotificationService;
87 - private final TbCoreConsumerStats stats = new TbCoreConsumerStats();  
88 79
89 public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, 80 public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
90 DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService, 81 DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
91 SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService, 82 SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
92 - TbCoreDeviceRpcService tbCoreDeviceRpcService, EdgeNotificationService edgeNotificationService) { 83 + TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, EdgeNotificationService edgeNotificationService) {
93 super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); 84 super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
94 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); 85 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
95 this.stateService = stateService; 86 this.stateService = stateService;
96 this.localSubscriptionService = localSubscriptionService; 87 this.localSubscriptionService = localSubscriptionService;
97 this.subscriptionManagerService = subscriptionManagerService; 88 this.subscriptionManagerService = subscriptionManagerService;
98 this.tbCoreDeviceRpcService = tbCoreDeviceRpcService; 89 this.tbCoreDeviceRpcService = tbCoreDeviceRpcService;
  90 + this.stats = new TbCoreConsumerStats(statsFactory);
99 this.edgeNotificationService = edgeNotificationService; 91 this.edgeNotificationService = edgeNotificationService;
100 } 92 }
101 93
@@ -235,6 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -235,6 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
235 public void printStats() { 227 public void printStats() {
236 if (statsEnabled) { 228 if (statsEnabled) {
237 stats.printStats(); 229 stats.printStats();
  230 + stats.reset();
238 } 231 }
239 } 232 }
240 233
@@ -22,11 +22,13 @@ import org.springframework.scheduling.annotation.Scheduled; @@ -22,11 +22,13 @@ import org.springframework.scheduling.annotation.Scheduled;
22 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
23 import org.thingsboard.rule.engine.api.RpcError; 23 import org.thingsboard.rule.engine.api.RpcError;
24 import org.thingsboard.server.actors.ActorSystemContext; 24 import org.thingsboard.server.actors.ActorSystemContext;
  25 +import org.thingsboard.server.common.data.id.RuleNodeId;
25 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
26 import org.thingsboard.server.common.msg.TbActorMsg; 27 import org.thingsboard.server.common.msg.TbActorMsg;
27 import org.thingsboard.server.common.msg.TbMsg; 28 import org.thingsboard.server.common.msg.TbMsg;
28 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; 29 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
29 import org.thingsboard.server.common.msg.queue.RuleEngineException; 30 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  31 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
30 import org.thingsboard.server.common.msg.queue.ServiceQueue; 32 import org.thingsboard.server.common.msg.queue.ServiceQueue;
31 import org.thingsboard.server.common.msg.queue.ServiceType; 33 import org.thingsboard.server.common.msg.queue.ServiceType;
32 import org.thingsboard.server.common.msg.queue.TbCallback; 34 import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -40,6 +42,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent; @@ -40,6 +42,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
40 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; 42 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
41 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; 43 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
42 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; 44 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
  45 +import org.thingsboard.server.common.stats.StatsFactory;
43 import org.thingsboard.server.queue.util.TbRuleEngineComponent; 46 import org.thingsboard.server.queue.util.TbRuleEngineComponent;
44 import org.thingsboard.server.service.encoding.DataDecodingEncodingService; 47 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
45 import org.thingsboard.server.service.queue.processing.AbstractConsumerService; 48 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
@@ -58,6 +61,7 @@ import javax.annotation.PreDestroy; @@ -58,6 +61,7 @@ import javax.annotation.PreDestroy;
58 import java.util.Collections; 61 import java.util.Collections;
59 import java.util.HashSet; 62 import java.util.HashSet;
60 import java.util.List; 63 import java.util.List;
  64 +import java.util.Map;
61 import java.util.Optional; 65 import java.util.Optional;
62 import java.util.Set; 66 import java.util.Set;
63 import java.util.UUID; 67 import java.util.UUID;
@@ -79,6 +83,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -79,6 +83,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
79 @Value("${queue.rule-engine.stats.enabled:true}") 83 @Value("${queue.rule-engine.stats.enabled:true}")
80 private boolean statsEnabled; 84 private boolean statsEnabled;
81 85
  86 + private final StatsFactory statsFactory;
82 private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory; 87 private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory;
83 private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory; 88 private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory;
84 private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory; 89 private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory;
@@ -95,7 +100,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -95,7 +100,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
95 TbQueueRuleEngineSettings ruleEngineSettings, 100 TbQueueRuleEngineSettings ruleEngineSettings,
96 TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService, 101 TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
97 ActorSystemContext actorContext, DataDecodingEncodingService encodingService, 102 ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
98 - TbRuleEngineDeviceRpcService tbDeviceRpcService) { 103 + TbRuleEngineDeviceRpcService tbDeviceRpcService,
  104 + StatsFactory statsFactory) {
99 super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer()); 105 super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
100 this.statisticsService = statisticsService; 106 this.statisticsService = statisticsService;
101 this.ruleEngineSettings = ruleEngineSettings; 107 this.ruleEngineSettings = ruleEngineSettings;
@@ -103,6 +109,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -103,6 +109,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
103 this.submitStrategyFactory = submitStrategyFactory; 109 this.submitStrategyFactory = submitStrategyFactory;
104 this.processingStrategyFactory = processingStrategyFactory; 110 this.processingStrategyFactory = processingStrategyFactory;
105 this.tbDeviceRpcService = tbDeviceRpcService; 111 this.tbDeviceRpcService = tbDeviceRpcService;
  112 + this.statsFactory = statsFactory;
106 } 113 }
107 114
108 @PostConstruct 115 @PostConstruct
@@ -111,7 +118,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -111,7 +118,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
111 for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) { 118 for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) {
112 consumerConfigurations.putIfAbsent(configuration.getName(), configuration); 119 consumerConfigurations.putIfAbsent(configuration.getName(), configuration);
113 consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration)); 120 consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration));
114 - consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName())); 121 + consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName(), statsFactory));
115 } 122 }
116 submitExecutor = Executors.newSingleThreadExecutor(); 123 submitExecutor = Executors.newSingleThreadExecutor();
117 } 124 }
@@ -181,6 +188,12 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -181,6 +188,12 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
181 } 188 }
182 189
183 TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(configuration.getName(), timeout, ctx); 190 TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(configuration.getName(), timeout, ctx);
  191 + if (timeout) {
  192 + printFirstOrAll(configuration, ctx, ctx.getPendingMap(), "Timeout");
  193 + }
  194 + if (!ctx.getFailedMap().isEmpty()) {
  195 + printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed");
  196 + }
184 TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result); 197 TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
185 if (statsEnabled) { 198 if (statsEnabled) {
186 stats.log(result, decision.isCommit()); 199 stats.log(result, decision.isCommit());
@@ -208,6 +221,22 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -208,6 +221,22 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
208 }); 221 });
209 } 222 }
210 223
  224 + private void printFirstOrAll(TbRuleEngineQueueConfiguration configuration, TbMsgPackProcessingContext ctx, Map<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> map, String prefix) {
  225 + boolean printAll = log.isTraceEnabled();
  226 + log.info("{} to process [{}] messages", prefix, map.size());
  227 + for (Map.Entry<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> pending : map.entrySet()) {
  228 + ToRuleEngineMsg tmp = pending.getValue().getValue();
  229 + TbMsg tmpMsg = TbMsg.fromBytes(configuration.getName(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY);
  230 + RuleNodeInfo ruleNodeInfo = ctx.getLastVisitedRuleNode(pending.getKey());
  231 + if (printAll) {
  232 + log.trace("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
  233 + } else {
  234 + log.info("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
  235 + break;
  236 + }
  237 + }
  238 + }
  239 +
211 @Override 240 @Override
212 protected ServiceType getServiceType() { 241 protected ServiceType getServiceType() {
213 return ServiceType.TB_RULE_ENGINE; 242 return ServiceType.TB_RULE_ENGINE;
@@ -269,6 +298,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -269,6 +298,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
269 consumerStats.forEach((queue, stats) -> { 298 consumerStats.forEach((queue, stats) -> {
270 stats.printStats(); 299 stats.printStats();
271 statisticsService.reportQueueStats(ts, stats); 300 statisticsService.reportQueueStats(ts, stats);
  301 + stats.reset();
272 }); 302 });
273 } 303 }
274 } 304 }
@@ -17,83 +17,132 @@ package org.thingsboard.server.service.queue; @@ -17,83 +17,132 @@ package org.thingsboard.server.service.queue;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.thingsboard.server.gen.transport.TransportProtos; 19 import org.thingsboard.server.gen.transport.TransportProtos;
  20 +import org.thingsboard.server.common.stats.StatsCounter;
  21 +import org.thingsboard.server.common.stats.StatsFactory;
  22 +import org.thingsboard.server.common.stats.StatsType;
20 23
21 -import java.util.concurrent.atomic.AtomicInteger; 24 +import java.util.*;
22 25
23 @Slf4j 26 @Slf4j
24 public class TbCoreConsumerStats { 27 public class TbCoreConsumerStats {
  28 + public static final String TOTAL_MSGS = "totalMsgs";
  29 + public static final String SESSION_EVENTS = "sessionEvents";
  30 + public static final String GET_ATTRIBUTE = "getAttr";
  31 + public static final String ATTRIBUTE_SUBSCRIBES = "subToAttr";
  32 + public static final String RPC_SUBSCRIBES = "subToRpc";
  33 + public static final String TO_DEVICE_RPC_CALL_RESPONSES = "toDevRpc";
  34 + public static final String SUBSCRIPTION_INFO = "subInfo";
  35 + public static final String DEVICE_CLAIMS = "claimDevice";
  36 + public static final String DEVICE_STATES = "deviceState";
  37 + public static final String SUBSCRIPTION_MSGS = "subMsgs";
  38 + public static final String TO_CORE_NOTIFICATIONS = "coreNfs";
  39 + public static final String EDGE_NOTIFICATIONS = "edgeNfs";
25 40
26 - private final AtomicInteger totalCounter = new AtomicInteger(0);  
27 - private final AtomicInteger sessionEventCounter = new AtomicInteger(0);  
28 - private final AtomicInteger getAttributesCounter = new AtomicInteger(0);  
29 - private final AtomicInteger subscribeToAttributesCounter = new AtomicInteger(0);  
30 - private final AtomicInteger subscribeToRPCCounter = new AtomicInteger(0);  
31 - private final AtomicInteger toDeviceRPCCallResponseCounter = new AtomicInteger(0);  
32 - private final AtomicInteger subscriptionInfoCounter = new AtomicInteger(0);  
33 - private final AtomicInteger claimDeviceCounter = new AtomicInteger(0); 41 + private final StatsCounter totalCounter;
  42 + private final StatsCounter sessionEventCounter;
  43 + private final StatsCounter getAttributesCounter;
  44 + private final StatsCounter subscribeToAttributesCounter;
  45 + private final StatsCounter subscribeToRPCCounter;
  46 + private final StatsCounter toDeviceRPCCallResponseCounter;
  47 + private final StatsCounter subscriptionInfoCounter;
  48 + private final StatsCounter claimDeviceCounter;
34 49
35 - private final AtomicInteger deviceStateCounter = new AtomicInteger(0);  
36 - private final AtomicInteger subscriptionMsgCounter = new AtomicInteger(0);  
37 - private final AtomicInteger toCoreNotificationsCounter = new AtomicInteger(0);  
38 - private final AtomicInteger edgeNotificationMsgCounter = new AtomicInteger(0); 50 + private final StatsCounter deviceStateCounter;
  51 + private final StatsCounter subscriptionMsgCounter;
  52 + private final StatsCounter toCoreNotificationsCounter;
  53 + private final StatsCounter edgeNotificationMsgCounter;
  54 +
  55 + private final List<StatsCounter> counters = new ArrayList<>();
  56 +
  57 + public TbCoreConsumerStats(StatsFactory statsFactory) {
  58 + String statsKey = StatsType.CORE.getName();
  59 +
  60 + this.totalCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
  61 + this.sessionEventCounter = statsFactory.createStatsCounter(statsKey, SESSION_EVENTS);
  62 + this.getAttributesCounter = statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE);
  63 + this.subscribeToAttributesCounter = statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES);
  64 + this.subscribeToRPCCounter = statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES);
  65 + this.toDeviceRPCCallResponseCounter = statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES);
  66 + this.subscriptionInfoCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO);
  67 + this.claimDeviceCounter = statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS);
  68 + this.deviceStateCounter = statsFactory.createStatsCounter(statsKey, DEVICE_STATES);
  69 + this.subscriptionMsgCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS);
  70 + this.toCoreNotificationsCounter = statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS);
  71 + this.edgeNotificationMsgCounter = statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS);
  72 +
  73 +
  74 + counters.add(totalCounter);
  75 + counters.add(sessionEventCounter);
  76 + counters.add(getAttributesCounter);
  77 + counters.add(subscribeToAttributesCounter);
  78 + counters.add(subscribeToRPCCounter);
  79 + counters.add(toDeviceRPCCallResponseCounter);
  80 + counters.add(subscriptionInfoCounter);
  81 + counters.add(claimDeviceCounter);
  82 +
  83 + counters.add(deviceStateCounter);
  84 + counters.add(subscriptionMsgCounter);
  85 + counters.add(toCoreNotificationsCounter);
  86 + counters.add(edgeNotificationMsgCounter);
  87 + }
39 88
40 public void log(TransportProtos.TransportToDeviceActorMsg msg) { 89 public void log(TransportProtos.TransportToDeviceActorMsg msg) {
41 - totalCounter.incrementAndGet(); 90 + totalCounter.increment();
42 if (msg.hasSessionEvent()) { 91 if (msg.hasSessionEvent()) {
43 - sessionEventCounter.incrementAndGet(); 92 + sessionEventCounter.increment();
44 } 93 }
45 if (msg.hasGetAttributes()) { 94 if (msg.hasGetAttributes()) {
46 - getAttributesCounter.incrementAndGet(); 95 + getAttributesCounter.increment();
47 } 96 }
48 if (msg.hasSubscribeToAttributes()) { 97 if (msg.hasSubscribeToAttributes()) {
49 - subscribeToAttributesCounter.incrementAndGet(); 98 + subscribeToAttributesCounter.increment();
50 } 99 }
51 if (msg.hasSubscribeToRPC()) { 100 if (msg.hasSubscribeToRPC()) {
52 - subscribeToRPCCounter.incrementAndGet(); 101 + subscribeToRPCCounter.increment();
53 } 102 }
54 if (msg.hasToDeviceRPCCallResponse()) { 103 if (msg.hasToDeviceRPCCallResponse()) {
55 - toDeviceRPCCallResponseCounter.incrementAndGet(); 104 + toDeviceRPCCallResponseCounter.increment();
56 } 105 }
57 if (msg.hasSubscriptionInfo()) { 106 if (msg.hasSubscriptionInfo()) {
58 - subscriptionInfoCounter.incrementAndGet(); 107 + subscriptionInfoCounter.increment();
59 } 108 }
60 if (msg.hasClaimDevice()) { 109 if (msg.hasClaimDevice()) {
61 - claimDeviceCounter.incrementAndGet(); 110 + claimDeviceCounter.increment();
62 } 111 }
63 } 112 }
64 113
65 public void log(TransportProtos.DeviceStateServiceMsgProto msg) { 114 public void log(TransportProtos.DeviceStateServiceMsgProto msg) {
66 - totalCounter.incrementAndGet();  
67 - deviceStateCounter.incrementAndGet(); 115 + totalCounter.increment();
  116 + deviceStateCounter.increment();
68 } 117 }
69 118
70 public void log(TransportProtos.EdgeNotificationMsgProto msg) { 119 public void log(TransportProtos.EdgeNotificationMsgProto msg) {
71 - totalCounter.incrementAndGet();  
72 - edgeNotificationMsgCounter.incrementAndGet(); 120 + totalCounter.increment();
  121 + edgeNotificationMsgCounter.increment();
73 } 122 }
74 123
75 public void log(TransportProtos.SubscriptionMgrMsgProto msg) { 124 public void log(TransportProtos.SubscriptionMgrMsgProto msg) {
76 - totalCounter.incrementAndGet();  
77 - subscriptionMsgCounter.incrementAndGet(); 125 + totalCounter.increment();
  126 + subscriptionMsgCounter.increment();
78 } 127 }
79 128
80 public void log(TransportProtos.ToCoreNotificationMsg msg) { 129 public void log(TransportProtos.ToCoreNotificationMsg msg) {
81 - totalCounter.incrementAndGet();  
82 - toCoreNotificationsCounter.incrementAndGet(); 130 + totalCounter.increment();
  131 + toCoreNotificationsCounter.increment();
83 } 132 }
84 133
85 public void printStats() { 134 public void printStats() {
86 - int total = totalCounter.getAndSet(0); 135 + int total = totalCounter.get();
87 if (total > 0) { 136 if (total > 0) {
88 - log.info("Total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" +  
89 - " deviceState [{}] subMgr [{}] coreNfs [{}] edgeNfs [{}]",  
90 - total, sessionEventCounter.getAndSet(0),  
91 - getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0),  
92 - subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0),  
93 - subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0)  
94 - , deviceStateCounter.getAndSet(0), subscriptionMsgCounter.getAndSet(0), toCoreNotificationsCounter.getAndSet(0),  
95 - edgeNotificationMsgCounter.getAndSet(0)); 137 + StringBuilder stats = new StringBuilder();
  138 + counters.forEach(counter -> {
  139 + stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
  140 + });
  141 + log.info("Core Stats: {}", stats);
96 } 142 }
97 } 143 }
98 144
  145 + public void reset() {
  146 + counters.forEach(StatsCounter::clear);
  147 + }
99 } 148 }
@@ -16,8 +16,10 @@ @@ -16,8 +16,10 @@
16 package org.thingsboard.server.service.queue; 16 package org.thingsboard.server.service.queue;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
19 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.msg.queue.RuleEngineException; 21 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  22 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
21 import org.thingsboard.server.common.msg.queue.TbMsgCallback; 23 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
22 24
23 import java.util.UUID; 25 import java.util.UUID;
@@ -45,4 +47,10 @@ public class TbMsgPackCallback implements TbMsgCallback { @@ -45,4 +47,10 @@ public class TbMsgPackCallback implements TbMsgCallback {
45 log.trace("[{}] ON FAILURE", id, e); 47 log.trace("[{}] ON FAILURE", id, e);
46 ctx.onFailure(tenantId, id, e); 48 ctx.onFailure(tenantId, id, e);
47 } 49 }
  50 +
  51 + @Override
  52 + public void visit(RuleNodeInfo ruleNodeInfo) {
  53 + log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo);
  54 + ctx.visit(id, ruleNodeInfo);
  55 + }
48 } 56 }
@@ -16,8 +16,10 @@ @@ -16,8 +16,10 @@
16 package org.thingsboard.server.service.queue; 16 package org.thingsboard.server.service.queue;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
19 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.msg.queue.RuleEngineException; 21 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  22 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
21 import org.thingsboard.server.gen.transport.TransportProtos; 23 import org.thingsboard.server.gen.transport.TransportProtos;
22 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 24 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
23 import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; 25 import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
@@ -32,7 +34,6 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -32,7 +34,6 @@ import java.util.concurrent.atomic.AtomicInteger;
32 public class TbMsgPackProcessingContext { 34 public class TbMsgPackProcessingContext {
33 35
34 private final TbRuleEngineSubmitStrategy submitStrategy; 36 private final TbRuleEngineSubmitStrategy submitStrategy;
35 -  
36 private final AtomicInteger pendingCount; 37 private final AtomicInteger pendingCount;
37 private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1); 38 private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
38 @Getter 39 @Getter
@@ -44,6 +45,8 @@ public class TbMsgPackProcessingContext { @@ -44,6 +45,8 @@ public class TbMsgPackProcessingContext {
44 @Getter 45 @Getter
45 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>(); 46 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>();
46 47
  48 + private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
  49 +
47 public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) { 50 public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
48 this.submitStrategy = submitStrategy; 51 this.submitStrategy = submitStrategy;
49 this.pendingMap = submitStrategy.getPendingMap(); 52 this.pendingMap = submitStrategy.getPendingMap();
@@ -81,4 +84,13 @@ public class TbMsgPackProcessingContext { @@ -81,4 +84,13 @@ public class TbMsgPackProcessingContext {
81 processingTimeoutLatch.countDown(); 84 processingTimeoutLatch.countDown();
82 } 85 }
83 } 86 }
  87 +
  88 + public void visit(UUID id, RuleNodeInfo ruleNodeInfo) {
  89 + lastRuleNodeMap.put(id, ruleNodeInfo);
  90 + }
  91 +
  92 + public RuleNodeInfo getLastVisitedRuleNode(UUID id) {
  93 + return lastRuleNodeMap.get(id);
  94 + }
  95 +
84 } 96 }
@@ -15,23 +15,21 @@ @@ -15,23 +15,21 @@
15 */ 15 */
16 package org.thingsboard.server.service.queue; 16 package org.thingsboard.server.service.queue;
17 17
18 -import lombok.Data;  
19 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
20 import org.thingsboard.server.common.data.id.TenantId; 19 import org.thingsboard.server.common.data.id.TenantId;
21 import org.thingsboard.server.common.msg.queue.RuleEngineException; 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
22 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 21 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
23 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 22 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  23 +import org.thingsboard.server.common.stats.StatsFactory;
24 import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; 24 import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult;
  25 +import org.thingsboard.server.common.stats.StatsCounter;
  26 +import org.thingsboard.server.common.stats.StatsType;
25 27
26 -import java.util.HashMap;  
27 -import java.util.Map;  
28 -import java.util.UUID; 28 +import java.util.*;
29 import java.util.concurrent.ConcurrentHashMap; 29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap; 30 import java.util.concurrent.ConcurrentMap;
31 -import java.util.concurrent.atomic.AtomicInteger;  
32 31
33 @Slf4j 32 @Slf4j
34 -@Data  
35 public class TbRuleEngineConsumerStats { 33 public class TbRuleEngineConsumerStats {
36 34
37 public static final String TOTAL_MSGS = "totalMsgs"; 35 public static final String TOTAL_MSGS = "totalMsgs";
@@ -43,61 +41,72 @@ public class TbRuleEngineConsumerStats { @@ -43,61 +41,72 @@ public class TbRuleEngineConsumerStats {
43 public static final String SUCCESSFUL_ITERATIONS = "successfulIterations"; 41 public static final String SUCCESSFUL_ITERATIONS = "successfulIterations";
44 public static final String FAILED_ITERATIONS = "failedIterations"; 42 public static final String FAILED_ITERATIONS = "failedIterations";
45 43
46 - private final AtomicInteger totalMsgCounter = new AtomicInteger(0);  
47 - private final AtomicInteger successMsgCounter = new AtomicInteger(0);  
48 - private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0);  
49 - private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0); 44 + private final StatsCounter totalMsgCounter;
  45 + private final StatsCounter successMsgCounter;
  46 + private final StatsCounter tmpTimeoutMsgCounter;
  47 + private final StatsCounter tmpFailedMsgCounter;
50 48
51 - private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0);  
52 - private final AtomicInteger failedMsgCounter = new AtomicInteger(0); 49 + private final StatsCounter timeoutMsgCounter;
  50 + private final StatsCounter failedMsgCounter;
53 51
54 - private final AtomicInteger successIterationsCounter = new AtomicInteger(0);  
55 - private final AtomicInteger failedIterationsCounter = new AtomicInteger(0); 52 + private final StatsCounter successIterationsCounter;
  53 + private final StatsCounter failedIterationsCounter;
56 54
57 - private final Map<String, AtomicInteger> counters = new HashMap<>(); 55 + private final List<StatsCounter> counters = new ArrayList<>();
58 private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>(); 56 private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>();
59 private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>(); 57 private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>();
60 58
61 private final String queueName; 59 private final String queueName;
62 60
63 - public TbRuleEngineConsumerStats(String queueName) { 61 + public TbRuleEngineConsumerStats(String queueName, StatsFactory statsFactory) {
64 this.queueName = queueName; 62 this.queueName = queueName;
65 - counters.put(TOTAL_MSGS, totalMsgCounter);  
66 - counters.put(SUCCESSFUL_MSGS, successMsgCounter);  
67 - counters.put(TIMEOUT_MSGS, timeoutMsgCounter);  
68 - counters.put(FAILED_MSGS, failedMsgCounter);  
69 -  
70 - counters.put(TMP_TIMEOUT, tmpTimeoutMsgCounter);  
71 - counters.put(TMP_FAILED, tmpFailedMsgCounter);  
72 - counters.put(SUCCESSFUL_ITERATIONS, successIterationsCounter);  
73 - counters.put(FAILED_ITERATIONS, failedIterationsCounter); 63 +
  64 + String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName;
  65 + this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
  66 + this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS);
  67 + this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS);
  68 + this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS);
  69 + this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT);
  70 + this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED);
  71 + this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS);
  72 + this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS);
  73 +
  74 + counters.add(totalMsgCounter);
  75 + counters.add(successMsgCounter);
  76 + counters.add(timeoutMsgCounter);
  77 + counters.add(failedMsgCounter);
  78 +
  79 + counters.add(tmpTimeoutMsgCounter);
  80 + counters.add(tmpFailedMsgCounter);
  81 + counters.add(successIterationsCounter);
  82 + counters.add(failedIterationsCounter);
74 } 83 }
75 84
76 public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) { 85 public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) {
77 int success = msg.getSuccessMap().size(); 86 int success = msg.getSuccessMap().size();
78 int pending = msg.getPendingMap().size(); 87 int pending = msg.getPendingMap().size();
79 int failed = msg.getFailedMap().size(); 88 int failed = msg.getFailedMap().size();
80 - totalMsgCounter.addAndGet(success + pending + failed);  
81 - successMsgCounter.addAndGet(success); 89 + totalMsgCounter.add(success + pending + failed);
  90 + successMsgCounter.add(success);
82 msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess()); 91 msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess());
83 if (finalIterationForPack) { 92 if (finalIterationForPack) {
84 if (pending > 0 || failed > 0) { 93 if (pending > 0 || failed > 0) {
85 - timeoutMsgCounter.addAndGet(pending);  
86 - failedMsgCounter.addAndGet(failed); 94 + timeoutMsgCounter.add(pending);
  95 + failedMsgCounter.add(failed);
87 if (pending > 0) { 96 if (pending > 0) {
88 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout()); 97 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout());
89 } 98 }
90 if (failed > 0) { 99 if (failed > 0) {
91 msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed()); 100 msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed());
92 } 101 }
93 - failedIterationsCounter.incrementAndGet(); 102 + failedIterationsCounter.increment();
94 } else { 103 } else {
95 - successIterationsCounter.incrementAndGet(); 104 + successIterationsCounter.increment();
96 } 105 }
97 } else { 106 } else {
98 - failedIterationsCounter.incrementAndGet();  
99 - tmpTimeoutMsgCounter.addAndGet(pending);  
100 - tmpFailedMsgCounter.addAndGet(failed); 107 + failedIterationsCounter.increment();
  108 + tmpTimeoutMsgCounter.add(pending);
  109 + tmpFailedMsgCounter.add(failed);
101 if (pending > 0) { 110 if (pending > 0) {
102 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout()); 111 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout());
103 } 112 }
@@ -113,19 +122,31 @@ public class TbRuleEngineConsumerStats { @@ -113,19 +122,31 @@ public class TbRuleEngineConsumerStats {
113 return tenantStats.computeIfAbsent(new UUID(reMsg.getTenantIdMSB(), reMsg.getTenantIdLSB()), TbTenantRuleEngineStats::new); 122 return tenantStats.computeIfAbsent(new UUID(reMsg.getTenantIdMSB(), reMsg.getTenantIdLSB()), TbTenantRuleEngineStats::new);
114 } 123 }
115 124
  125 + public ConcurrentMap<UUID, TbTenantRuleEngineStats> getTenantStats() {
  126 + return tenantStats;
  127 + }
  128 +
  129 + public String getQueueName() {
  130 + return queueName;
  131 + }
  132 +
  133 + public ConcurrentMap<TenantId, RuleEngineException> getTenantExceptions() {
  134 + return tenantExceptions;
  135 + }
  136 +
116 public void printStats() { 137 public void printStats() {
117 int total = totalMsgCounter.get(); 138 int total = totalMsgCounter.get();
118 if (total > 0) { 139 if (total > 0) {
119 StringBuilder stats = new StringBuilder(); 140 StringBuilder stats = new StringBuilder();
120 - counters.forEach((label, value) -> {  
121 - stats.append(label).append(" = [").append(value.get()).append("] "); 141 + counters.forEach(counter -> {
  142 + stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
122 }); 143 });
123 log.info("[{}] Stats: {}", queueName, stats); 144 log.info("[{}] Stats: {}", queueName, stats);
124 } 145 }
125 } 146 }
126 147
127 public void reset() { 148 public void reset() {
128 - counters.values().forEach(counter -> counter.set(0)); 149 + counters.forEach(StatsCounter::clear);
129 tenantStats.clear(); 150 tenantStats.clear();
130 tenantExceptions.clear(); 151 tenantExceptions.clear();
131 } 152 }
@@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable { @@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
52 try { 52 try {
53 return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); 53 return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
54 } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { 54 } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
55 - log.error("Invalid JWT Token", ex); 55 + log.debug("Invalid JWT Token", ex);
56 throw new BadCredentialsException("Invalid JWT token: ", ex); 56 throw new BadCredentialsException("Invalid JWT token: ", ex);
57 } catch (ExpiredJwtException expiredEx) { 57 } catch (ExpiredJwtException expiredEx) {
58 - log.info("JWT Token is expired", expiredEx); 58 + log.debug("JWT Token is expired", expiredEx);
59 throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); 59 throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
60 } 60 }
61 } 61 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.stats;
  17 +
  18 +import org.springframework.beans.factory.annotation.Autowired;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.actors.JsInvokeStats;
  21 +import org.thingsboard.server.common.stats.StatsCounter;
  22 +import org.thingsboard.server.common.stats.StatsFactory;
  23 +import org.thingsboard.server.common.stats.StatsType;
  24 +
  25 +import javax.annotation.PostConstruct;
  26 +
  27 +@Service
  28 +public class DefaultJsInvokeStats implements JsInvokeStats {
  29 + private static final String REQUESTS = "requests";
  30 + private static final String RESPONSES = "responses";
  31 + private static final String FAILURES = "failures";
  32 +
  33 + private StatsCounter requestsCounter;
  34 + private StatsCounter responsesCounter;
  35 + private StatsCounter failuresCounter;
  36 +
  37 + @Autowired
  38 + private StatsFactory statsFactory;
  39 +
  40 + @PostConstruct
  41 + public void init() {
  42 + String key = StatsType.JS_INVOKE.getName();
  43 + this.requestsCounter = statsFactory.createStatsCounter(key, REQUESTS);
  44 + this.responsesCounter = statsFactory.createStatsCounter(key, RESPONSES);
  45 + this.failuresCounter = statsFactory.createStatsCounter(key, FAILURES);
  46 + }
  47 +
  48 + @Override
  49 + public void incrementRequests(int amount) {
  50 + requestsCounter.add(amount);
  51 + }
  52 +
  53 + @Override
  54 + public void incrementResponses(int amount) {
  55 + responsesCounter.add(amount);
  56 + }
  57 +
  58 + @Override
  59 + public void incrementFailures(int amount) {
  60 + failuresCounter.add(amount);
  61 + }
  62 +
  63 + @Override
  64 + public int getRequests() {
  65 + return requestsCounter.get();
  66 + }
  67 +
  68 + @Override
  69 + public int getResponses() {
  70 + return responsesCounter.get();
  71 + }
  72 +
  73 + @Override
  74 + public int getFailures() {
  75 + return failuresCounter.get();
  76 + }
  77 +
  78 + @Override
  79 + public void reset() {
  80 + requestsCounter.clear();
  81 + responsesCounter.clear();
  82 + failuresCounter.clear();
  83 + }
  84 +}
@@ -104,7 +104,6 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS @@ -104,7 +104,6 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
104 } 104 }
105 } 105 }
106 }); 106 });
107 - ruleEngineStats.reset();  
108 } 107 }
109 108
110 private AssetId getServiceAssetId(TenantId tenantId, String queueName) { 109 private AssetId getServiceAssetId(TenantId tenantId, String queueName) {
@@ -20,6 +20,9 @@ import org.springframework.beans.factory.annotation.Value; @@ -20,6 +20,9 @@ import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.boot.context.event.ApplicationReadyEvent; 20 import org.springframework.boot.context.event.ApplicationReadyEvent;
21 import org.springframework.context.event.EventListener; 21 import org.springframework.context.event.EventListener;
22 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.common.stats.MessagesStats;
  24 +import org.thingsboard.server.common.stats.StatsFactory;
  25 +import org.thingsboard.server.common.stats.StatsType;
23 import org.thingsboard.server.queue.TbQueueConsumer; 26 import org.thingsboard.server.queue.TbQueueConsumer;
24 import org.thingsboard.server.queue.TbQueueProducer; 27 import org.thingsboard.server.queue.TbQueueProducer;
25 import org.thingsboard.server.queue.TbQueueResponseTemplate; 28 import org.thingsboard.server.queue.TbQueueResponseTemplate;
@@ -41,9 +44,9 @@ import java.util.concurrent.*; @@ -41,9 +44,9 @@ import java.util.concurrent.*;
41 @Service 44 @Service
42 @TbCoreComponent 45 @TbCoreComponent
43 public class TbCoreTransportApiService { 46 public class TbCoreTransportApiService {
44 -  
45 private final TbCoreQueueFactory tbCoreQueueFactory; 47 private final TbCoreQueueFactory tbCoreQueueFactory;
46 private final TransportApiService transportApiService; 48 private final TransportApiService transportApiService;
  49 + private final StatsFactory statsFactory;
47 50
48 @Value("${queue.transport_api.max_pending_requests:10000}") 51 @Value("${queue.transport_api.max_pending_requests:10000}")
49 private int maxPendingRequests; 52 private int maxPendingRequests;
@@ -58,9 +61,10 @@ public class TbCoreTransportApiService { @@ -58,9 +61,10 @@ public class TbCoreTransportApiService {
58 private TbQueueResponseTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, 61 private TbQueueResponseTemplate<TbProtoQueueMsg<TransportApiRequestMsg>,
59 TbProtoQueueMsg<TransportApiResponseMsg>> transportApiTemplate; 62 TbProtoQueueMsg<TransportApiResponseMsg>> transportApiTemplate;
60 63
61 - public TbCoreTransportApiService(TbCoreQueueFactory tbCoreQueueFactory, TransportApiService transportApiService) { 64 + public TbCoreTransportApiService(TbCoreQueueFactory tbCoreQueueFactory, TransportApiService transportApiService, StatsFactory statsFactory) {
62 this.tbCoreQueueFactory = tbCoreQueueFactory; 65 this.tbCoreQueueFactory = tbCoreQueueFactory;
63 this.transportApiService = transportApiService; 66 this.transportApiService = transportApiService;
  67 + this.statsFactory = statsFactory;
64 } 68 }
65 69
66 @PostConstruct 70 @PostConstruct
@@ -69,6 +73,9 @@ public class TbCoreTransportApiService { @@ -69,6 +73,9 @@ public class TbCoreTransportApiService {
69 TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> producer = tbCoreQueueFactory.createTransportApiResponseProducer(); 73 TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> producer = tbCoreQueueFactory.createTransportApiResponseProducer();
70 TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> consumer = tbCoreQueueFactory.createTransportApiRequestConsumer(); 74 TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> consumer = tbCoreQueueFactory.createTransportApiRequestConsumer();
71 75
  76 + String key = StatsType.TRANSPORT.getName();
  77 + MessagesStats queueStats = statsFactory.createMessagesStats(key);
  78 +
72 DefaultTbQueueResponseTemplate.DefaultTbQueueResponseTemplateBuilder 79 DefaultTbQueueResponseTemplate.DefaultTbQueueResponseTemplateBuilder
73 <TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> builder = DefaultTbQueueResponseTemplate.builder(); 80 <TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> builder = DefaultTbQueueResponseTemplate.builder();
74 builder.requestTemplate(consumer); 81 builder.requestTemplate(consumer);
@@ -78,6 +85,7 @@ public class TbCoreTransportApiService { @@ -78,6 +85,7 @@ public class TbCoreTransportApiService {
78 builder.pollInterval(responsePollDuration); 85 builder.pollInterval(responsePollDuration);
79 builder.executor(transportCallbackExecutor); 86 builder.executor(transportCallbackExecutor);
80 builder.handler(transportApiService); 87 builder.handler(transportApiService);
  88 + builder.stats(queueStats);
81 transportApiTemplate = builder.build(); 89 transportApiTemplate = builder.build();
82 } 90 }
83 91
@@ -27,7 +27,6 @@ import java.sql.Statement; @@ -27,7 +27,6 @@ import java.sql.Statement;
27 27
28 28
29 @Slf4j 29 @Slf4j
30 -@PsqlDao  
31 public abstract class AbstractCleanUpService { 30 public abstract class AbstractCleanUpService {
32 31
33 @Value("${spring.datasource.url}") 32 @Value("${spring.datasource.url}")
@@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Value; @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.scheduling.annotation.Scheduled; 20 import org.springframework.scheduling.annotation.Scheduled;
21 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
22 import org.thingsboard.server.dao.util.PsqlDao; 22 import org.thingsboard.server.dao.util.PsqlDao;
  23 +import org.thingsboard.server.dao.util.SqlDao;
23 import org.thingsboard.server.service.ttl.AbstractCleanUpService; 24 import org.thingsboard.server.service.ttl.AbstractCleanUpService;
24 25
25 import java.sql.Connection; 26 import java.sql.Connection;
@@ -27,6 +28,7 @@ import java.sql.DriverManager; @@ -27,6 +28,7 @@ import java.sql.DriverManager;
27 import java.sql.SQLException; 28 import java.sql.SQLException;
28 29
29 @PsqlDao 30 @PsqlDao
  31 +@SqlDao
30 @Slf4j 32 @Slf4j
31 @Service 33 @Service
32 public class EventsCleanUpService extends AbstractCleanUpService { 34 public class EventsCleanUpService extends AbstractCleanUpService {
@@ -18,14 +18,12 @@ package org.thingsboard.server.service.ttl.timeseries; @@ -18,14 +18,12 @@ package org.thingsboard.server.service.ttl.timeseries;
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Value; 19 import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.scheduling.annotation.Scheduled; 20 import org.springframework.scheduling.annotation.Scheduled;
21 -import org.thingsboard.server.dao.util.PsqlTsAnyDao;  
22 import org.thingsboard.server.service.ttl.AbstractCleanUpService; 21 import org.thingsboard.server.service.ttl.AbstractCleanUpService;
23 22
24 import java.sql.Connection; 23 import java.sql.Connection;
25 import java.sql.DriverManager; 24 import java.sql.DriverManager;
26 import java.sql.SQLException; 25 import java.sql.SQLException;
27 26
28 -@PsqlTsAnyDao  
29 @Slf4j 27 @Slf4j
30 public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService { 28 public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService {
31 29
@@ -252,14 +252,17 @@ sql: @@ -252,14 +252,17 @@ sql:
252 batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" 252 batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
253 batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" 253 batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
254 stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" 254 stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
  255 + batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
255 ts: 256 ts:
256 batch_size: "${SQL_TS_BATCH_SIZE:10000}" 257 batch_size: "${SQL_TS_BATCH_SIZE:10000}"
257 batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" 258 batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
258 stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" 259 stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
  260 + batch_threads: "${SQL_TS_BATCH_THREADS:4}"
259 ts_latest: 261 ts_latest:
260 batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" 262 batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
261 batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" 263 batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
262 stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" 264 stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
  265 + batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
263 # Specify whether to remove null characters from strValue of attributes and timeseries before insert 266 # Specify whether to remove null characters from strValue of attributes and timeseries before insert
264 remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" 267 remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
265 postgres: 268 postgres:
@@ -268,6 +271,7 @@ sql: @@ -268,6 +271,7 @@ sql:
268 timescale: 271 timescale:
269 # Specify Interval size for new data chunks storage. 272 # Specify Interval size for new data chunks storage.
270 chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}" 273 chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
  274 + batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"
271 ttl: 275 ttl:
272 ts: 276 ts:
273 enabled: "${SQL_TTL_TS_ENABLED:true}" 277 enabled: "${SQL_TTL_TS_ENABLED:true}"
@@ -767,7 +771,7 @@ queue: @@ -767,7 +771,7 @@ queue:
767 transport: 771 transport:
768 # For high priority notifications that require minimum latency and processing time 772 # For high priority notifications that require minimum latency and processing time
769 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 773 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
770 - poll_interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" 774 + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
771 775
772 service: 776 service:
773 type: "${TB_SERVICE_TYPE:monolith}" # monolith or tb-core or tb-rule-engine 777 type: "${TB_SERVICE_TYPE:monolith}" # monolith or tb-core or tb-rule-engine
@@ -775,3 +779,13 @@ service: @@ -775,3 +779,13 @@ service:
775 id: "${TB_SERVICE_ID:}" 779 id: "${TB_SERVICE_ID:}"
776 tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id. 780 tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
777 781
  782 +metrics:
  783 + # Enable/disable actuator metrics.
  784 + enabled: "${METRICS_ENABLED:false}"
  785 +
  786 +management:
  787 + endpoints:
  788 + web:
  789 + exposure:
  790 + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
  791 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors;
  17 +
  18 +public interface JsInvokeStats {
  19 + default void incrementRequests() {
  20 + incrementRequests(1);
  21 + }
  22 +
  23 + void incrementRequests(int amount);
  24 +
  25 + default void incrementResponses() {
  26 + incrementResponses(1);
  27 + }
  28 +
  29 + void incrementResponses(int amount);
  30 +
  31 + default void incrementFailures() {
  32 + incrementFailures(1);
  33 + }
  34 +
  35 + void incrementFailures(int amount);
  36 +
  37 + int getRequests();
  38 +
  39 + int getResponses();
  40 +
  41 + int getFailures();
  42 +
  43 + void reset();
  44 +}
@@ -17,5 +17,9 @@ package org.thingsboard.server.dao.util; @@ -17,5 +17,9 @@ package org.thingsboard.server.dao.util;
17 17
18 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
19 19
  20 +import java.lang.annotation.Retention;
  21 +import java.lang.annotation.RetentionPolicy;
  22 +
  23 +@Retention(RetentionPolicy.RUNTIME)
20 @ConditionalOnExpression("'${database.ts.type}'=='sql' && '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'") 24 @ConditionalOnExpression("'${database.ts.type}'=='sql' && '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'")
21 public @interface PsqlTsDao { } 25 public @interface PsqlTsDao { }
@@ -109,9 +109,16 @@ message EntityDataProto { @@ -109,9 +109,16 @@ message EntityDataProto {
109 string entityType = 3; 109 string entityType = 3;
110 transport.PostTelemetryMsg postTelemetryMsg = 4; 110 transport.PostTelemetryMsg postTelemetryMsg = 4;
111 transport.PostAttributeMsg postAttributesMsg = 5; 111 transport.PostAttributeMsg postAttributesMsg = 5;
  112 + string postAttributeScope = 6;
  113 + AttributeDeleteMsg attributeDeleteMsg = 7;
112 // transport.ToDeviceRpcRequestMsg ??? 114 // transport.ToDeviceRpcRequestMsg ???
113 } 115 }
114 116
  117 +message AttributeDeleteMsg {
  118 + string scope = 1;
  119 + repeated string attributeNames = 2;
  120 +}
  121 +
115 message RuleChainUpdateMsg { 122 message RuleChainUpdateMsg {
116 UpdateMsgType msgType = 1; 123 UpdateMsgType msgType = 1;
117 int64 idMSB = 2; 124 int64 idMSB = 2;
@@ -106,7 +106,6 @@ public final class TbMsg implements Serializable { @@ -106,7 +106,6 @@ public final class TbMsg implements Serializable {
106 if (callback != null) { 106 if (callback != null) {
107 this.callback = callback; 107 this.callback = callback;
108 } else { 108 } else {
109 - log.warn("[{}] Created message with empty callback: {}", originator, type);  
110 this.callback = TbMsgCallback.EMPTY; 109 this.callback = TbMsgCallback.EMPTY;
111 } 110 }
112 } 111 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.msg.queue;
  17 +
  18 +import org.thingsboard.server.common.data.id.RuleNodeId;
  19 +
  20 +public class RuleNodeInfo {
  21 + private final String label;
  22 +
  23 + public RuleNodeInfo(RuleNodeId id, String ruleChainName, String ruleNodeName) {
  24 + this.label = "[RuleChain: " + ruleChainName + "|RuleNode: " + ruleNodeName + "(" + id + ")]";
  25 + }
  26 +
  27 + @Override
  28 + public String toString() {
  29 + return label;
  30 + }
  31 +}
@@ -34,4 +34,7 @@ public interface TbMsgCallback { @@ -34,4 +34,7 @@ public interface TbMsgCallback {
34 34
35 void onFailure(RuleEngineException e); 35 void onFailure(RuleEngineException e);
36 36
  37 + default void visit(RuleNodeInfo ruleNodeInfo) {
  38 + }
  39 +
37 } 40 }
@@ -41,6 +41,7 @@ @@ -41,6 +41,7 @@
41 <module>queue</module> 41 <module>queue</module>
42 <module>transport</module> 42 <module>transport</module>
43 <module>dao-api</module> 43 <module>dao-api</module>
  44 + <module>stats</module>
44 <module>edge-api</module> 45 <module>edge-api</module>
45 </modules> 46 </modules>
46 47
@@ -49,6 +49,10 @@ @@ -49,6 +49,10 @@
49 <artifactId>message</artifactId> 49 <artifactId>message</artifactId>
50 </dependency> 50 </dependency>
51 <dependency> 51 <dependency>
  52 + <groupId>org.thingsboard.common</groupId>
  53 + <artifactId>stats</artifactId>
  54 + </dependency>
  55 + <dependency>
52 <groupId>org.apache.kafka</groupId> 56 <groupId>org.apache.kafka</groupId>
53 <artifactId>kafka-clients</artifactId> 57 <artifactId>kafka-clients</artifactId>
54 </dependency> 58 </dependency>
@@ -112,6 +116,7 @@ @@ -112,6 +116,7 @@
112 <groupId>org.apache.curator</groupId> 116 <groupId>org.apache.curator</groupId>
113 <artifactId>curator-recipes</artifactId> 117 <artifactId>curator-recipes</artifactId>
114 </dependency> 118 </dependency>
  119 +
115 <dependency> 120 <dependency>
116 <groupId>junit</groupId> 121 <groupId>junit</groupId>
117 <artifactId>junit</artifactId> 122 <artifactId>junit</artifactId>
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.queue; 16 package org.thingsboard.server.queue;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.stats.MessagesStats;
19 20
20 public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> { 21 public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> {
21 22
@@ -25,4 +26,5 @@ public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response ext @@ -25,4 +26,5 @@ public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response ext
25 26
26 void stop(); 27 void stop();
27 28
  29 + void setMessagesStats(MessagesStats messagesStats);
28 } 30 }
@@ -28,6 +28,7 @@ import org.thingsboard.server.queue.TbQueueMsg; @@ -28,6 +28,7 @@ import org.thingsboard.server.queue.TbQueueMsg;
28 import org.thingsboard.server.queue.TbQueueMsgMetadata; 28 import org.thingsboard.server.queue.TbQueueMsgMetadata;
29 import org.thingsboard.server.queue.TbQueueProducer; 29 import org.thingsboard.server.queue.TbQueueProducer;
30 import org.thingsboard.server.queue.TbQueueRequestTemplate; 30 import org.thingsboard.server.queue.TbQueueRequestTemplate;
  31 +import org.thingsboard.server.common.stats.MessagesStats;
31 32
32 import java.util.List; 33 import java.util.List;
33 import java.util.UUID; 34 import java.util.UUID;
@@ -54,6 +55,8 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response @@ -54,6 +55,8 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
54 private volatile long tickSize = 0L; 55 private volatile long tickSize = 0L;
55 private volatile boolean stopped = false; 56 private volatile boolean stopped = false;
56 57
  58 + private MessagesStats messagesStats;
  59 +
57 @Builder 60 @Builder
58 public DefaultTbQueueRequestTemplate(TbQueueAdmin queueAdmin, 61 public DefaultTbQueueRequestTemplate(TbQueueAdmin queueAdmin,
59 TbQueueProducer<Request> requestTemplate, 62 TbQueueProducer<Request> requestTemplate,
@@ -154,6 +157,11 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response @@ -154,6 +157,11 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
154 } 157 }
155 158
156 @Override 159 @Override
  160 + public void setMessagesStats(MessagesStats messagesStats) {
  161 + this.messagesStats = messagesStats;
  162 + }
  163 +
  164 + @Override
157 public ListenableFuture<Response> send(Request request) { 165 public ListenableFuture<Response> send(Request request) {
158 if (tickSize > maxPendingRequests) { 166 if (tickSize > maxPendingRequests) {
159 return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!")); 167 return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!"));
@@ -166,14 +174,23 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response @@ -166,14 +174,23 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
166 ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); 174 ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future);
167 pendingRequests.putIfAbsent(requestId, responseMetaData); 175 pendingRequests.putIfAbsent(requestId, responseMetaData);
168 log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime); 176 log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime);
  177 + if (messagesStats != null) {
  178 + messagesStats.incrementTotal();
  179 + }
169 requestTemplate.send(TopicPartitionInfo.builder().topic(requestTemplate.getDefaultTopic()).build(), request, new TbQueueCallback() { 180 requestTemplate.send(TopicPartitionInfo.builder().topic(requestTemplate.getDefaultTopic()).build(), request, new TbQueueCallback() {
170 @Override 181 @Override
171 public void onSuccess(TbQueueMsgMetadata metadata) { 182 public void onSuccess(TbQueueMsgMetadata metadata) {
  183 + if (messagesStats != null) {
  184 + messagesStats.incrementSuccessful();
  185 + }
172 log.trace("[{}] Request sent: {}", requestId, metadata); 186 log.trace("[{}] Request sent: {}", requestId, metadata);
173 } 187 }
174 188
175 @Override 189 @Override
176 public void onFailure(Throwable t) { 190 public void onFailure(Throwable t) {
  191 + if (messagesStats != null) {
  192 + messagesStats.incrementFailed();
  193 + }
177 pendingRequests.remove(requestId); 194 pendingRequests.remove(requestId);
178 future.setException(t); 195 future.setException(t);
179 } 196 }
@@ -23,6 +23,7 @@ import org.thingsboard.server.queue.TbQueueHandler; @@ -23,6 +23,7 @@ import org.thingsboard.server.queue.TbQueueHandler;
23 import org.thingsboard.server.queue.TbQueueMsg; 23 import org.thingsboard.server.queue.TbQueueMsg;
24 import org.thingsboard.server.queue.TbQueueProducer; 24 import org.thingsboard.server.queue.TbQueueProducer;
25 import org.thingsboard.server.queue.TbQueueResponseTemplate; 25 import org.thingsboard.server.queue.TbQueueResponseTemplate;
  26 +import org.thingsboard.server.common.stats.MessagesStats;
26 27
27 import java.util.List; 28 import java.util.List;
28 import java.util.UUID; 29 import java.util.UUID;
@@ -44,6 +45,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -44,6 +45,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
44 private final ExecutorService loopExecutor; 45 private final ExecutorService loopExecutor;
45 private final ScheduledExecutorService timeoutExecutor; 46 private final ScheduledExecutorService timeoutExecutor;
46 private final ExecutorService callbackExecutor; 47 private final ExecutorService callbackExecutor;
  48 + private final MessagesStats stats;
47 private final int maxPendingRequests; 49 private final int maxPendingRequests;
48 private final long requestTimeout; 50 private final long requestTimeout;
49 51
@@ -58,7 +60,8 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -58,7 +60,8 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
58 long pollInterval, 60 long pollInterval,
59 long requestTimeout, 61 long requestTimeout,
60 int maxPendingRequests, 62 int maxPendingRequests,
61 - ExecutorService executor) { 63 + ExecutorService executor,
  64 + MessagesStats stats) {
62 this.requestTemplate = requestTemplate; 65 this.requestTemplate = requestTemplate;
63 this.responseTemplate = responseTemplate; 66 this.responseTemplate = responseTemplate;
64 this.pendingRequests = new ConcurrentHashMap<>(); 67 this.pendingRequests = new ConcurrentHashMap<>();
@@ -66,6 +69,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -66,6 +69,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
66 this.pollInterval = pollInterval; 69 this.pollInterval = pollInterval;
67 this.requestTimeout = requestTimeout; 70 this.requestTimeout = requestTimeout;
68 this.callbackExecutor = executor; 71 this.callbackExecutor = executor;
  72 + this.stats = stats;
69 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(); 73 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
70 this.loopExecutor = Executors.newSingleThreadExecutor(); 74 this.loopExecutor = Executors.newSingleThreadExecutor();
71 } 75 }
@@ -108,11 +112,13 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -108,11 +112,13 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
108 String responseTopic = bytesToString(responseTopicHeader); 112 String responseTopic = bytesToString(responseTopicHeader);
109 try { 113 try {
110 pendingRequestCount.getAndIncrement(); 114 pendingRequestCount.getAndIncrement();
  115 + stats.incrementTotal();
111 AsyncCallbackTemplate.withCallbackAndTimeout(handler.handle(request), 116 AsyncCallbackTemplate.withCallbackAndTimeout(handler.handle(request),
112 response -> { 117 response -> {
113 pendingRequestCount.decrementAndGet(); 118 pendingRequestCount.decrementAndGet();
114 response.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId)); 119 response.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
115 responseTemplate.send(TopicPartitionInfo.builder().topic(responseTopic).build(), response, null); 120 responseTemplate.send(TopicPartitionInfo.builder().topic(responseTopic).build(), response, null);
  121 + stats.incrementSuccessful();
116 }, 122 },
117 e -> { 123 e -> {
118 pendingRequestCount.decrementAndGet(); 124 pendingRequestCount.decrementAndGet();
@@ -121,6 +127,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -121,6 +127,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
121 } else { 127 } else {
122 log.trace("[{}] Failed to process the request: {}", requestId, request, e); 128 log.trace("[{}] Failed to process the request: {}", requestId, request, e);
123 } 129 }
  130 + stats.incrementFailed();
124 }, 131 },
125 requestTimeout, 132 requestTimeout,
126 timeoutExecutor, 133 timeoutExecutor,
@@ -128,6 +135,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response @@ -128,6 +135,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
128 } catch (Throwable e) { 135 } catch (Throwable e) {
129 pendingRequestCount.decrementAndGet(); 136 pendingRequestCount.decrementAndGet();
130 log.warn("[{}] Failed to process the request: {}", requestId, request, e); 137 log.warn("[{}] Failed to process the request: {}", requestId, request, e);
  138 + stats.incrementFailed();
131 } 139 }
132 } 140 }
133 }); 141 });
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2020 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  20 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  21 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  22 + <modelVersion>4.0.0</modelVersion>
  23 + <parent>
  24 + <groupId>org.thingsboard</groupId>
  25 + <version>2.5.3-SNAPSHOT</version>
  26 + <artifactId>common</artifactId>
  27 + </parent>
  28 + <groupId>org.thingsboard.common</groupId>
  29 + <artifactId>stats</artifactId>
  30 + <packaging>jar</packaging>
  31 +
  32 + <name>Thingsboard Server Stats</name>
  33 + <url>https://thingsboard.io</url>
  34 +
  35 + <properties>
  36 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  37 + <main.dir>${basedir}/../..</main.dir>
  38 + </properties>
  39 +
  40 + <dependencies>
  41 + <dependency>
  42 + <groupId>com.google.guava</groupId>
  43 + <artifactId>guava</artifactId>
  44 + <scope>provided</scope>
  45 + </dependency>
  46 + <dependency>
  47 + <groupId>org.slf4j</groupId>
  48 + <artifactId>slf4j-api</artifactId>
  49 + </dependency>
  50 + <dependency>
  51 + <groupId>org.slf4j</groupId>
  52 + <artifactId>log4j-over-slf4j</artifactId>
  53 + </dependency>
  54 + <dependency>
  55 + <groupId>ch.qos.logback</groupId>
  56 + <artifactId>logback-core</artifactId>
  57 + </dependency>
  58 + <dependency>
  59 + <groupId>ch.qos.logback</groupId>
  60 + <artifactId>logback-classic</artifactId>
  61 + </dependency>
  62 + <dependency>
  63 + <groupId>org.springframework.boot</groupId>
  64 + <artifactId>spring-boot-starter-actuator</artifactId>
  65 + </dependency>
  66 + <dependency>
  67 + <groupId>io.micrometer</groupId>
  68 + <artifactId>micrometer-core</artifactId>
  69 + </dependency>
  70 + <dependency>
  71 + <groupId>io.micrometer</groupId>
  72 + <artifactId>micrometer-registry-prometheus</artifactId>
  73 + </dependency>
  74 +
  75 + <dependency>
  76 + <groupId>junit</groupId>
  77 + <artifactId>junit</artifactId>
  78 + <scope>test</scope>
  79 + </dependency>
  80 + <dependency>
  81 + <groupId>org.mockito</groupId>
  82 + <artifactId>mockito-all</artifactId>
  83 + <scope>test</scope>
  84 + </dependency>
  85 + </dependencies>
  86 +
  87 + <build>
  88 + <plugins>
  89 + </plugins>
  90 + </build>
  91 +
  92 +</project>
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +import io.micrometer.core.instrument.Counter;
  19 +
  20 +import java.util.concurrent.atomic.AtomicInteger;
  21 +
  22 +public class DefaultCounter {
  23 + private final AtomicInteger aiCounter;
  24 + private final Counter micrometerCounter;
  25 +
  26 + public DefaultCounter(AtomicInteger aiCounter, Counter micrometerCounter) {
  27 + this.aiCounter = aiCounter;
  28 + this.micrometerCounter = micrometerCounter;
  29 + }
  30 +
  31 + public void increment() {
  32 + aiCounter.incrementAndGet();
  33 + micrometerCounter.increment();
  34 + }
  35 +
  36 + public void clear() {
  37 + aiCounter.set(0);
  38 + }
  39 +
  40 + public int get() {
  41 + return aiCounter.get();
  42 + }
  43 +
  44 + public void add(int delta){
  45 + aiCounter.addAndGet(delta);
  46 + micrometerCounter.increment(delta);
  47 + }
  48 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +public class DefaultMessagesStats implements MessagesStats {
  19 + private final StatsCounter totalCounter;
  20 + private final StatsCounter successfulCounter;
  21 + private final StatsCounter failedCounter;
  22 +
  23 + public DefaultMessagesStats(StatsCounter totalCounter, StatsCounter successfulCounter, StatsCounter failedCounter) {
  24 + this.totalCounter = totalCounter;
  25 + this.successfulCounter = successfulCounter;
  26 + this.failedCounter = failedCounter;
  27 + }
  28 +
  29 + @Override
  30 + public void incrementTotal(int amount) {
  31 + totalCounter.add(amount);
  32 + }
  33 +
  34 + @Override
  35 + public void incrementSuccessful(int amount) {
  36 + successfulCounter.add(amount);
  37 + }
  38 +
  39 + @Override
  40 + public void incrementFailed(int amount) {
  41 + failedCounter.add(amount);
  42 + }
  43 +
  44 + @Override
  45 + public int getTotal() {
  46 + return totalCounter.get();
  47 + }
  48 +
  49 + @Override
  50 + public int getSuccessful() {
  51 + return successfulCounter.get();
  52 + }
  53 +
  54 + @Override
  55 + public int getFailed() {
  56 + return failedCounter.get();
  57 + }
  58 +
  59 + @Override
  60 + public void reset() {
  61 + totalCounter.clear();
  62 + successfulCounter.clear();
  63 + failedCounter.clear();
  64 + }
  65 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +import io.micrometer.core.instrument.Counter;
  19 +import io.micrometer.core.instrument.MeterRegistry;
  20 +import io.micrometer.core.instrument.Tags;
  21 +import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.stereotype.Service;
  24 +
  25 +import java.util.concurrent.atomic.AtomicInteger;
  26 +
  27 +@Service
  28 +public class DefaultStatsFactory implements StatsFactory {
  29 + private static final String TOTAL_MSGS = "totalMsgs";
  30 + private static final String SUCCESSFUL_MSGS = "successfulMsgs";
  31 + private static final String FAILED_MSGS = "failedMsgs";
  32 +
  33 + private static final String STATS_NAME_TAG = "statsName";
  34 +
  35 + private static final Counter STUB_COUNTER = new StubCounter();
  36 +
  37 + @Autowired
  38 + private MeterRegistry meterRegistry;
  39 +
  40 + @Value("${metrics.enabled:false}")
  41 + private Boolean metricsEnabled;
  42 +
  43 + @Override
  44 + public StatsCounter createStatsCounter(String key, String statsName) {
  45 + return new StatsCounter(
  46 + new AtomicInteger(0),
  47 + metricsEnabled ?
  48 + meterRegistry.counter(key, STATS_NAME_TAG, statsName)
  49 + : STUB_COUNTER,
  50 + statsName
  51 + );
  52 + }
  53 +
  54 + @Override
  55 + public DefaultCounter createDefaultCounter(String key, String... tags) {
  56 + return new DefaultCounter(
  57 + new AtomicInteger(0),
  58 + metricsEnabled ?
  59 + meterRegistry.counter(key, tags)
  60 + : STUB_COUNTER
  61 + );
  62 + }
  63 +
  64 + @Override
  65 + public <T extends Number> T createGauge(String key, T number, String... tags) {
  66 + return meterRegistry.gauge(key, Tags.of(tags), number);
  67 + }
  68 +
  69 + @Override
  70 + public MessagesStats createMessagesStats(String key) {
  71 + StatsCounter totalCounter = createStatsCounter(key, TOTAL_MSGS);
  72 + StatsCounter successfulCounter = createStatsCounter(key, SUCCESSFUL_MSGS);
  73 + StatsCounter failedCounter = createStatsCounter(key, FAILED_MSGS);
  74 + return new DefaultMessagesStats(totalCounter, successfulCounter, failedCounter);
  75 + }
  76 +
  77 + private static class StubCounter implements Counter {
  78 + @Override
  79 + public void increment(double amount) {}
  80 +
  81 + @Override
  82 + public double count() {
  83 + return 0;
  84 + }
  85 +
  86 + @Override
  87 + public Id getId() {
  88 + return null;
  89 + }
  90 + }
  91 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +public interface MessagesStats {
  19 + default void incrementTotal() {
  20 + incrementTotal(1);
  21 + }
  22 +
  23 + void incrementTotal(int amount);
  24 +
  25 + default void incrementSuccessful() {
  26 + incrementSuccessful(1);
  27 + }
  28 +
  29 + void incrementSuccessful(int amount);
  30 +
  31 + default void incrementFailed() {
  32 + incrementFailed(1);
  33 + }
  34 +
  35 + void incrementFailed(int amount);
  36 +
  37 + int getTotal();
  38 +
  39 + int getSuccessful();
  40 +
  41 + int getFailed();
  42 +
  43 + void reset();
  44 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +import io.micrometer.core.instrument.Counter;
  19 +
  20 +import java.util.concurrent.atomic.AtomicInteger;
  21 +
  22 +public class StatsCounter extends DefaultCounter {
  23 + private final String name;
  24 +
  25 + public StatsCounter(AtomicInteger aiCounter, Counter micrometerCounter, String name) {
  26 + super(aiCounter, micrometerCounter);
  27 + this.name = name;
  28 + }
  29 +
  30 + public String getName() {
  31 + return name;
  32 + }
  33 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +public interface StatsFactory {
  19 + StatsCounter createStatsCounter(String key, String statsName);
  20 +
  21 + DefaultCounter createDefaultCounter(String key, String... tags);
  22 +
  23 + <T extends Number> T createGauge(String key, T number, String... tags);
  24 +
  25 + MessagesStats createMessagesStats(String key);
  26 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.stats;
  17 +
  18 +public enum StatsType {
  19 + RULE_ENGINE("ruleEngine"), CORE("core"), TRANSPORT("transport"), JS_INVOKE("jsInvoke"), RATE_EXECUTOR("rateExecutor");
  20 +
  21 + private String name;
  22 +
  23 + StatsType(String name) {
  24 + this.name = name;
  25 + }
  26 +
  27 + public String getName() {
  28 + return name;
  29 + }
  30 +}
@@ -42,6 +42,10 @@ @@ -42,6 +42,10 @@
42 </dependency> 42 </dependency>
43 <dependency> 43 <dependency>
44 <groupId>org.thingsboard.common</groupId> 44 <groupId>org.thingsboard.common</groupId>
  45 + <artifactId>stats</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>org.thingsboard.common</groupId>
45 <artifactId>data</artifactId> 49 <artifactId>data</artifactId>
46 </dependency> 50 </dependency>
47 <dependency> 51 <dependency>
@@ -55,6 +55,9 @@ import org.thingsboard.server.queue.discovery.PartitionService; @@ -55,6 +55,9 @@ import org.thingsboard.server.queue.discovery.PartitionService;
55 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; 55 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
56 import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 56 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
57 import org.thingsboard.server.queue.provider.TbTransportQueueFactory; 57 import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
  58 +import org.thingsboard.server.common.stats.MessagesStats;
  59 +import org.thingsboard.server.common.stats.StatsFactory;
  60 +import org.thingsboard.server.common.stats.StatsType;
58 61
59 import javax.annotation.PostConstruct; 62 import javax.annotation.PostConstruct;
60 import javax.annotation.PreDestroy; 63 import javax.annotation.PreDestroy;
@@ -101,12 +104,17 @@ public class DefaultTransportService implements TransportService { @@ -101,12 +104,17 @@ public class DefaultTransportService implements TransportService {
101 private final TbQueueProducerProvider producerProvider; 104 private final TbQueueProducerProvider producerProvider;
102 private final PartitionService partitionService; 105 private final PartitionService partitionService;
103 private final TbServiceInfoProvider serviceInfoProvider; 106 private final TbServiceInfoProvider serviceInfoProvider;
  107 + private final StatsFactory statsFactory;
104 108
105 protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate; 109 protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
106 protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; 110 protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
107 protected TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> tbCoreMsgProducer; 111 protected TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> tbCoreMsgProducer;
108 protected TbQueueConsumer<TbProtoQueueMsg<ToTransportMsg>> transportNotificationsConsumer; 112 protected TbQueueConsumer<TbProtoQueueMsg<ToTransportMsg>> transportNotificationsConsumer;
109 113
  114 + protected MessagesStats ruleEngineProducerStats;
  115 + protected MessagesStats tbCoreProducerStats;
  116 + protected MessagesStats transportApiStats;
  117 +
110 protected ScheduledExecutorService schedulerExecutor; 118 protected ScheduledExecutorService schedulerExecutor;
111 protected ExecutorService transportCallbackExecutor; 119 protected ExecutorService transportCallbackExecutor;
112 120
@@ -119,11 +127,12 @@ public class DefaultTransportService implements TransportService { @@ -119,11 +127,12 @@ public class DefaultTransportService implements TransportService {
119 private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); 127 private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
120 private volatile boolean stopped = false; 128 private volatile boolean stopped = false;
121 129
122 - public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider, TbTransportQueueFactory queueProvider, TbQueueProducerProvider producerProvider, PartitionService partitionService) { 130 + public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider, TbTransportQueueFactory queueProvider, TbQueueProducerProvider producerProvider, PartitionService partitionService, StatsFactory statsFactory) {
123 this.serviceInfoProvider = serviceInfoProvider; 131 this.serviceInfoProvider = serviceInfoProvider;
124 this.queueProvider = queueProvider; 132 this.queueProvider = queueProvider;
125 this.producerProvider = producerProvider; 133 this.producerProvider = producerProvider;
126 this.partitionService = partitionService; 134 this.partitionService = partitionService;
  135 + this.statsFactory = statsFactory;
127 } 136 }
128 137
129 @PostConstruct 138 @PostConstruct
@@ -133,10 +142,14 @@ public class DefaultTransportService implements TransportService { @@ -133,10 +142,14 @@ public class DefaultTransportService implements TransportService {
133 new TbRateLimits(perTenantLimitsConf); 142 new TbRateLimits(perTenantLimitsConf);
134 new TbRateLimits(perDevicesLimitsConf); 143 new TbRateLimits(perDevicesLimitsConf);
135 } 144 }
  145 + this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer");
  146 + this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer");
  147 + this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer");
136 this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler")); 148 this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler"));
137 this.transportCallbackExecutor = Executors.newWorkStealingPool(20); 149 this.transportCallbackExecutor = Executors.newWorkStealingPool(20);
138 this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); 150 this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS);
139 transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate(); 151 transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate();
  152 + transportApiRequestTemplate.setMessagesStats(transportApiStats);
140 ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); 153 ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer();
141 tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer(); 154 tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer();
142 transportNotificationsConsumer = queueProvider.createTransportNotificationsConsumer(); 155 transportNotificationsConsumer = queueProvider.createTransportNotificationsConsumer();
@@ -557,10 +570,14 @@ public class DefaultTransportService implements TransportService { @@ -557,10 +570,14 @@ public class DefaultTransportService implements TransportService {
557 if (log.isTraceEnabled()) { 570 if (log.isTraceEnabled()) {
558 log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); 571 log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg);
559 } 572 }
  573 + TransportTbQueueCallback transportTbQueueCallback = callback != null ?
  574 + new TransportTbQueueCallback(callback) : null;
  575 + tbCoreProducerStats.incrementTotal();
  576 + StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats);
560 tbCoreMsgProducer.send(tpi, 577 tbCoreMsgProducer.send(tpi,
561 new TbProtoQueueMsg<>(getRoutingKey(sessionInfo), 578 new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
562 - ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ?  
563 - new TransportTbQueueCallback(callback) : null); 579 + ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()),
  580 + wrappedCallback);
564 } 581 }
565 582
566 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { 583 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
@@ -571,7 +588,9 @@ public class DefaultTransportService implements TransportService { @@ -571,7 +588,9 @@ public class DefaultTransportService implements TransportService {
571 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) 588 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
572 .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) 589 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
573 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); 590 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
574 - ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); 591 + ruleEngineProducerStats.incrementTotal();
  592 + StatsCallback wrappedCallback = new StatsCallback(callback, ruleEngineProducerStats);
  593 + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback);
575 } 594 }
576 595
577 private class TransportTbQueueCallback implements TbQueueCallback { 596 private class TransportTbQueueCallback implements TbQueueCallback {
@@ -592,6 +611,30 @@ public class DefaultTransportService implements TransportService { @@ -592,6 +611,30 @@ public class DefaultTransportService implements TransportService {
592 } 611 }
593 } 612 }
594 613
  614 + private class StatsCallback implements TbQueueCallback {
  615 + private final TbQueueCallback callback;
  616 + private final MessagesStats stats;
  617 +
  618 + private StatsCallback(TbQueueCallback callback, MessagesStats stats) {
  619 + this.callback = callback;
  620 + this.stats = stats;
  621 + }
  622 +
  623 + @Override
  624 + public void onSuccess(TbQueueMsgMetadata metadata) {
  625 + stats.incrementSuccessful();
  626 + if (callback != null)
  627 + callback.onSuccess(metadata);
  628 + }
  629 +
  630 + @Override
  631 + public void onFailure(Throwable t) {
  632 + stats.incrementFailed();
  633 + if (callback != null)
  634 + callback.onFailure(t);
  635 + }
  636 + }
  637 +
595 private class MsgPackCallback implements TbQueueCallback { 638 private class MsgPackCallback implements TbQueueCallback {
596 private final AtomicInteger msgCount; 639 private final AtomicInteger msgCount;
597 private final TransportServiceCallback<Void> callback; 640 private final TransportServiceCallback<Void> callback;
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.common.util;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +
  20 +import javax.crypto.Mac;
  21 +import javax.crypto.spec.SecretKeySpec;
  22 +import java.io.IOException;
  23 +import java.net.URLEncoder;
  24 +import java.nio.charset.StandardCharsets;
  25 +import java.nio.file.Files;
  26 +import java.nio.file.Path;
  27 +import java.nio.file.Paths;
  28 +import java.util.Base64;
  29 +
  30 +@Slf4j
  31 +public final class AzureIotHubUtil {
  32 + private static final String BASE_DIR_PATH = System.getProperty("user.dir");
  33 + private static final String APP_DIR = "application";
  34 + private static final String SRC_DIR = "src";
  35 + private static final String MAIN_DIR = "main";
  36 + private static final String DATA_DIR = "data";
  37 + private static final String CERTS_DIR = "certs";
  38 + private static final String AZURE_DIR = "azure";
  39 + private static final String FILE_NAME = "BaltimoreCyberTrustRoot.crt.pem";
  40 +
  41 + private static final Path FULL_FILE_PATH;
  42 +
  43 + static {
  44 + if (BASE_DIR_PATH.endsWith("bin")) {
  45 + FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("bin$", ""), DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME);
  46 + } else if (BASE_DIR_PATH.endsWith("conf")) {
  47 + FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("conf$", ""), DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME);
  48 + } else {
  49 + FULL_FILE_PATH = Paths.get(BASE_DIR_PATH, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME);
  50 + }
  51 + }
  52 +
  53 + private static final long SAS_TOKEN_VALID_SECS = 365 * 24 * 60 * 60;
  54 + private static final long ONE_SECOND_IN_MILLISECONDS = 1000;
  55 +
  56 + private static final String SAS_TOKEN_FORMAT = "SharedAccessSignature sr=%s&sig=%s&se=%s";
  57 +
  58 + private static final String USERNAME_FORMAT = "%s/%s/?api-version=2018-06-30";
  59 +
  60 + private AzureIotHubUtil() {
  61 + }
  62 +
  63 + public static String buildUsername(String host, String deviceId) {
  64 + return String.format(USERNAME_FORMAT, host, deviceId);
  65 + }
  66 +
  67 + public static String buildSasToken(String host, String sasKey) {
  68 + try {
  69 + final String targetUri = URLEncoder.encode(host.toLowerCase(), "UTF-8");
  70 + final long expiryTime = buildExpiresOn();
  71 + String toSign = targetUri + "\n" + expiryTime;
  72 + byte[] keyBytes = Base64.getDecoder().decode(sasKey.getBytes(StandardCharsets.UTF_8));
  73 + SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
  74 + Mac mac = Mac.getInstance("HmacSHA256");
  75 + mac.init(signingKey);
  76 + byte[] rawHmac = mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8));
  77 + String signature = URLEncoder.encode(Base64.getEncoder().encodeToString(rawHmac), "UTF-8");
  78 + return String.format(SAS_TOKEN_FORMAT, targetUri, signature, expiryTime);
  79 + } catch (Exception e) {
  80 + throw new RuntimeException("Failed to build SAS token!!!", e);
  81 + }
  82 + }
  83 +
  84 + private static long buildExpiresOn() {
  85 + long expiresOnDate = System.currentTimeMillis();
  86 + expiresOnDate += SAS_TOKEN_VALID_SECS * ONE_SECOND_IN_MILLISECONDS;
  87 + return expiresOnDate / ONE_SECOND_IN_MILLISECONDS;
  88 + }
  89 +
  90 + public static String getDefaultCaCert() {
  91 + try {
  92 + return new String(Files.readAllBytes(FULL_FILE_PATH));
  93 + } catch (IOException e) {
  94 + log.error("Failed to load Default CaCert file!!! [{}]", FULL_FILE_PATH.toString());
  95 + throw new RuntimeException("Failed to load Default CaCert file!!!");
  96 + }
  97 + }
  98 +
  99 +}
@@ -45,6 +45,10 @@ @@ -45,6 +45,10 @@
45 </dependency> 45 </dependency>
46 <dependency> 46 <dependency>
47 <groupId>org.thingsboard.common</groupId> 47 <groupId>org.thingsboard.common</groupId>
  48 + <artifactId>stats</artifactId>
  49 + </dependency>
  50 + <dependency>
  51 + <groupId>org.thingsboard.common</groupId>
48 <artifactId>dao-api</artifactId> 52 <artifactId>dao-api</artifactId>
49 </dependency> 53 </dependency>
50 <dependency> 54 <dependency>
@@ -24,6 +24,9 @@ import org.springframework.beans.factory.annotation.Value; @@ -24,6 +24,9 @@ import org.springframework.beans.factory.annotation.Value;
24 import org.springframework.scheduling.annotation.Scheduled; 24 import org.springframework.scheduling.annotation.Scheduled;
25 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
26 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.stats.DefaultCounter;
  28 +import org.thingsboard.server.common.stats.StatsCounter;
  29 +import org.thingsboard.server.common.stats.StatsFactory;
27 import org.thingsboard.server.dao.entity.EntityService; 30 import org.thingsboard.server.dao.entity.EntityService;
28 import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; 31 import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
29 import org.thingsboard.server.dao.util.AsyncTaskContext; 32 import org.thingsboard.server.dao.util.AsyncTaskContext;
@@ -57,48 +60,58 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< @@ -57,48 +60,58 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor<
57 @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, 60 @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled,
58 @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, 61 @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration,
59 @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, 62 @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames,
60 - @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq) {  
61 - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq); 63 + @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
  64 + @Autowired StatsFactory statsFactory) {
  65 + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory);
62 this.printTenantNames = printTenantNames; 66 this.printTenantNames = printTenantNames;
63 } 67 }
64 68
65 @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") 69 @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
66 public void printStats() { 70 public void printStats() {
67 int queueSize = getQueueSize(); 71 int queueSize = getQueueSize();
68 - int totalAddedValue = totalAdded.getAndSet(0);  
69 - int totalLaunchedValue = totalLaunched.getAndSet(0);  
70 - int totalReleasedValue = totalReleased.getAndSet(0);  
71 - int totalFailedValue = totalFailed.getAndSet(0);  
72 - int totalExpiredValue = totalExpired.getAndSet(0);  
73 - int totalRejectedValue = totalRejected.getAndSet(0);  
74 - int totalRateLimitedValue = totalRateLimited.getAndSet(0);  
75 - int rateLimitedTenantsValue = rateLimitedTenants.size();  
76 - int concurrencyLevelValue = concurrencyLevel.get();  
77 - if (queueSize > 0 || totalAddedValue > 0 || totalLaunchedValue > 0 || totalReleasedValue > 0 ||  
78 - totalFailedValue > 0 || totalExpiredValue > 0 || totalRejectedValue > 0 || totalRateLimitedValue > 0 || rateLimitedTenantsValue > 0  
79 - || concurrencyLevelValue > 0) {  
80 - log.info("Permits queueSize [{}] totalAdded [{}] totalLaunched [{}] totalReleased [{}] totalFailed [{}] totalExpired [{}] totalRejected [{}] " +  
81 - "totalRateLimited [{}] totalRateLimitedTenants [{}] currBuffer [{}] ",  
82 - queueSize, totalAddedValue, totalLaunchedValue, totalReleasedValue,  
83 - totalFailedValue, totalExpiredValue, totalRejectedValue, totalRateLimitedValue, rateLimitedTenantsValue, concurrencyLevelValue); 72 + int rateLimitedTenantsCount = (int) stats.getRateLimitedTenants().values().stream()
  73 + .filter(defaultCounter -> defaultCounter.get() > 0)
  74 + .count();
  75 +
  76 + if (queueSize > 0
  77 + || rateLimitedTenantsCount > 0
  78 + || concurrencyLevel.get() > 0
  79 + || stats.getStatsCounters().stream().anyMatch(counter -> counter.get() > 0)
  80 + ) {
  81 + StringBuilder statsBuilder = new StringBuilder();
  82 +
  83 + statsBuilder.append("queueSize").append(" = [").append(queueSize).append("] ");
  84 + stats.getStatsCounters().forEach(counter -> {
  85 + statsBuilder.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
  86 + });
  87 + statsBuilder.append("totalRateLimitedTenants").append(" = [").append(rateLimitedTenantsCount).append("] ");
  88 + statsBuilder.append(CONCURRENCY_LEVEL).append(" = [").append(concurrencyLevel.get()).append("] ");
  89 +
  90 + stats.getStatsCounters().forEach(StatsCounter::clear);
  91 + log.info("Permits {}", statsBuilder);
84 } 92 }
85 93
86 - rateLimitedTenants.forEach(((tenantId, counter) -> {  
87 - if (printTenantNames) {  
88 - String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> {  
89 - try {  
90 - return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get();  
91 - } catch (Exception e) {  
92 - log.error("[{}] Failed to get tenant name", tenantId, e);  
93 - return "N/A"; 94 + stats.getRateLimitedTenants().entrySet().stream()
  95 + .filter(entry -> entry.getValue().get() > 0)
  96 + .forEach(entry -> {
  97 + TenantId tenantId = entry.getKey();
  98 + DefaultCounter counter = entry.getValue();
  99 + int rateLimitedRequests = counter.get();
  100 + counter.clear();
  101 + if (printTenantNames) {
  102 + String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> {
  103 + try {
  104 + return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get();
  105 + } catch (Exception e) {
  106 + log.error("[{}] Failed to get tenant name", tenantId, e);
  107 + return "N/A";
  108 + }
  109 + });
  110 + log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests);
  111 + } else {
  112 + log.info("[{}] Rate limited requests: {}", tenantId, rateLimitedRequests);
94 } 113 }
95 }); 114 });
96 - log.info("[{}][{}] Rate limited requests: {}", tenantId, name, counter);  
97 - } else {  
98 - log.info("[{}] Rate limited requests: {}", tenantId, counter);  
99 - }  
100 - }));  
101 - rateLimitedTenants.clear();  
102 } 115 }
103 116
104 @PreDestroy 117 @PreDestroy
@@ -31,7 +31,7 @@ import java.util.regex.Pattern; @@ -31,7 +31,7 @@ import java.util.regex.Pattern;
31 @Slf4j 31 @Slf4j
32 public abstract class DataValidator<D extends BaseData<?>> { 32 public abstract class DataValidator<D extends BaseData<?>> {
33 private static final Pattern EMAIL_PATTERN = 33 private static final Pattern EMAIL_PATTERN =
34 - Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); 34 + Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
35 35
36 public void validate(D data, Function<D, TenantId> tenantIdFunction) { 36 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
37 try { 37 try {
@@ -64,7 +64,7 @@ public abstract class DataValidator<D extends BaseData<?>> { @@ -64,7 +64,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
64 return actualData.getId() != null && existentData.getId().equals(actualData.getId()); 64 return actualData.getId() != null && existentData.getId().equals(actualData.getId());
65 } 65 }
66 66
67 - protected static void validateEmail(String email) { 67 + public static void validateEmail(String email) {
68 if (!doValidateEmail(email)) { 68 if (!doValidateEmail(email)) {
69 throw new DataValidationException("Invalid email address format '" + email + "'!"); 69 throw new DataValidationException("Invalid email address format '" + email + "'!");
70 } 70 }
@@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import com.google.common.util.concurrent.SettableFuture; 19 import com.google.common.util.concurrent.SettableFuture;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 import org.thingsboard.common.util.ThingsBoardThreadFactory; 21 import org.thingsboard.common.util.ThingsBoardThreadFactory;
  22 +import org.thingsboard.server.common.stats.MessagesStats;
22 23
23 import java.util.ArrayList; 24 import java.util.ArrayList;
24 import java.util.List; 25 import java.util.List;
@@ -27,7 +28,6 @@ import java.util.concurrent.ExecutorService; @@ -27,7 +28,6 @@ import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors; 28 import java.util.concurrent.Executors;
28 import java.util.concurrent.LinkedBlockingQueue; 29 import java.util.concurrent.LinkedBlockingQueue;
29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.TimeUnit;
30 -import java.util.concurrent.atomic.AtomicInteger;  
31 import java.util.function.Consumer; 31 import java.util.function.Consumer;
32 import java.util.stream.Collectors; 32 import java.util.stream.Collectors;
33 33
@@ -35,22 +35,19 @@ import java.util.stream.Collectors; @@ -35,22 +35,19 @@ import java.util.stream.Collectors;
35 public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> { 35 public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
36 36
37 private final BlockingQueue<TbSqlQueueElement<E>> queue = new LinkedBlockingQueue<>(); 37 private final BlockingQueue<TbSqlQueueElement<E>> queue = new LinkedBlockingQueue<>();
38 - private final AtomicInteger addedCount = new AtomicInteger();  
39 - private final AtomicInteger savedCount = new AtomicInteger();  
40 - private final AtomicInteger failedCount = new AtomicInteger();  
41 private final TbSqlBlockingQueueParams params; 38 private final TbSqlBlockingQueueParams params;
42 39
43 private ExecutorService executor; 40 private ExecutorService executor;
44 - private ScheduledLogExecutorComponent logExecutor; 41 + private final MessagesStats stats;
45 42
46 - public TbSqlBlockingQueue(TbSqlBlockingQueueParams params) { 43 + public TbSqlBlockingQueue(TbSqlBlockingQueueParams params, MessagesStats stats) {
47 this.params = params; 44 this.params = params;
  45 + this.stats = stats;
48 } 46 }
49 47
50 @Override 48 @Override
51 - public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction) {  
52 - this.logExecutor = logExecutor;  
53 - executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("sql-queue-" + params.getLogName().toLowerCase())); 49 + public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction, int index) {
  50 + executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("sql-queue-" + index + "-" + params.getLogName().toLowerCase()));
54 executor.submit(() -> { 51 executor.submit(() -> {
55 String logName = params.getLogName(); 52 String logName = params.getLogName();
56 int batchSize = params.getBatchSize(); 53 int batchSize = params.getBatchSize();
@@ -70,7 +67,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> { @@ -70,7 +67,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
70 log.debug("[{}] Going to save {} entities", logName, entities.size()); 67 log.debug("[{}] Going to save {} entities", logName, entities.size());
71 saveFunction.accept(entities.stream().map(TbSqlQueueElement::getEntity).collect(Collectors.toList())); 68 saveFunction.accept(entities.stream().map(TbSqlQueueElement::getEntity).collect(Collectors.toList()));
72 entities.forEach(v -> v.getFuture().set(null)); 69 entities.forEach(v -> v.getFuture().set(null));
73 - savedCount.addAndGet(entities.size()); 70 + stats.incrementSuccessful(entities.size());
74 if (!fullPack) { 71 if (!fullPack) {
75 long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs); 72 long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs);
76 if (remainingDelay > 0) { 73 if (remainingDelay > 0) {
@@ -78,7 +75,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> { @@ -78,7 +75,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
78 } 75 }
79 } 76 }
80 } catch (Exception e) { 77 } catch (Exception e) {
81 - failedCount.addAndGet(entities.size()); 78 + stats.incrementFailed(entities.size());
82 entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(e)); 79 entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(e));
83 if (e instanceof InterruptedException) { 80 if (e instanceof InterruptedException) {
84 log.info("[{}] Queue polling was interrupted", logName); 81 log.info("[{}] Queue polling was interrupted", logName);
@@ -93,9 +90,10 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> { @@ -93,9 +90,10 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
93 }); 90 });
94 91
95 logExecutor.scheduleAtFixedRate(() -> { 92 logExecutor.scheduleAtFixedRate(() -> {
96 - if (queue.size() > 0 || addedCount.get() > 0 || savedCount.get() > 0 || failedCount.get() > 0) {  
97 - log.info("[{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]",  
98 - params.getLogName(), queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); 93 + if (queue.size() > 0 || stats.getTotal() > 0 || stats.getSuccessful() > 0 || stats.getFailed() > 0) {
  94 + log.info("Queue-{} [{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", index,
  95 + params.getLogName(), queue.size(), stats.getTotal(), stats.getSuccessful(), stats.getFailed());
  96 + stats.reset();
99 } 97 }
100 }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); 98 }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS);
101 } 99 }
@@ -111,7 +109,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> { @@ -111,7 +109,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
111 public ListenableFuture<Void> add(E element) { 109 public ListenableFuture<Void> add(E element) {
112 SettableFuture<Void> future = SettableFuture.create(); 110 SettableFuture<Void> future = SettableFuture.create();
113 queue.add(new TbSqlQueueElement<>(future, element)); 111 queue.add(new TbSqlQueueElement<>(future, element));
114 - addedCount.incrementAndGet(); 112 + stats.incrementTotal();
115 return future; 113 return future;
116 } 114 }
117 } 115 }
@@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql; @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql;
18 import lombok.Builder; 18 import lombok.Builder;
19 import lombok.Data; 19 import lombok.Data;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.common.stats.MessagesStats;
  22 +import org.thingsboard.server.common.stats.StatsFactory;
21 23
22 @Slf4j 24 @Slf4j
23 @Data 25 @Data
@@ -28,4 +30,5 @@ public class TbSqlBlockingQueueParams { @@ -28,4 +30,5 @@ public class TbSqlBlockingQueueParams {
28 private final int batchSize; 30 private final int batchSize;
29 private final long maxDelay; 31 private final long maxDelay;
30 private final long statsPrintIntervalMs; 32 private final long statsPrintIntervalMs;
  33 + private final String statsNamePrefix;
31 } 34 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.Data;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.common.stats.MessagesStats;
  22 +import org.thingsboard.server.common.stats.StatsFactory;
  23 +
  24 +import java.util.List;
  25 +import java.util.concurrent.CopyOnWriteArrayList;
  26 +import java.util.function.Consumer;
  27 +import java.util.function.Function;
  28 +
  29 +@Slf4j
  30 +@Data
  31 +public class TbSqlBlockingQueueWrapper<E> {
  32 + private final CopyOnWriteArrayList<TbSqlBlockingQueue<E>> queues = new CopyOnWriteArrayList<>();
  33 + private final TbSqlBlockingQueueParams params;
  34 + private ScheduledLogExecutorComponent logExecutor;
  35 + private final Function<E, Integer> hashCodeFunction;
  36 + private final int maxThreads;
  37 + private final StatsFactory statsFactory;
  38 +
  39 + public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction) {
  40 + for (int i = 0; i < maxThreads; i++) {
  41 + MessagesStats stats = statsFactory.createMessagesStats(params.getStatsNamePrefix() + ".queue." + i);
  42 + TbSqlBlockingQueue<E> queue = new TbSqlBlockingQueue<>(params, stats);
  43 + queues.add(queue);
  44 + queue.init(logExecutor, saveFunction, i);
  45 + }
  46 + }
  47 +
  48 + public ListenableFuture<Void> add(E element) {
  49 + int queueIndex = element != null ? (hashCodeFunction.apply(element) & 0x7FFFFFFF) % maxThreads : 0;
  50 + return queues.get(queueIndex).add(element);
  51 + }
  52 +
  53 + public void destroy() {
  54 + queues.forEach(TbSqlBlockingQueue::destroy);
  55 + }
  56 +}
@@ -22,7 +22,7 @@ import java.util.function.Consumer; @@ -22,7 +22,7 @@ import java.util.function.Consumer;
22 22
23 public interface TbSqlQueue<E> { 23 public interface TbSqlQueue<E> {
24 24
25 - void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction); 25 + void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction, int queueIndex);
26 26
27 void destroy(); 27 void destroy();
28 28
@@ -26,14 +26,15 @@ import org.thingsboard.server.common.data.UUIDConverter; @@ -26,14 +26,15 @@ import org.thingsboard.server.common.data.UUIDConverter;
26 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
27 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  29 +import org.thingsboard.server.common.stats.StatsFactory;
29 import org.thingsboard.server.dao.DaoUtil; 30 import org.thingsboard.server.dao.DaoUtil;
30 import org.thingsboard.server.dao.attributes.AttributesDao; 31 import org.thingsboard.server.dao.attributes.AttributesDao;
31 import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; 32 import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
32 import org.thingsboard.server.dao.model.sql.AttributeKvEntity; 33 import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
33 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; 34 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
34 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; 35 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
35 -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;  
36 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; 36 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  37 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
37 import org.thingsboard.server.dao.util.SqlDao; 38 import org.thingsboard.server.dao.util.SqlDao;
38 39
39 import javax.annotation.PostConstruct; 40 import javax.annotation.PostConstruct;
@@ -41,6 +42,7 @@ import javax.annotation.PreDestroy; @@ -41,6 +42,7 @@ import javax.annotation.PreDestroy;
41 import java.util.Collection; 42 import java.util.Collection;
42 import java.util.List; 43 import java.util.List;
43 import java.util.Optional; 44 import java.util.Optional;
  45 +import java.util.function.Function;
44 import java.util.stream.Collectors; 46 import java.util.stream.Collectors;
45 47
46 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; 48 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
@@ -59,6 +61,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @@ -59,6 +61,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
59 @Autowired 61 @Autowired
60 private AttributeKvInsertRepository attributeKvInsertRepository; 62 private AttributeKvInsertRepository attributeKvInsertRepository;
61 63
  64 + @Autowired
  65 + private StatsFactory statsFactory;
  66 +
62 @Value("${sql.attributes.batch_size:1000}") 67 @Value("${sql.attributes.batch_size:1000}")
63 private int batchSize; 68 private int batchSize;
64 69
@@ -68,7 +73,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @@ -68,7 +73,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
68 @Value("${sql.attributes.stats_print_interval_ms:1000}") 73 @Value("${sql.attributes.stats_print_interval_ms:1000}")
69 private long statsPrintIntervalMs; 74 private long statsPrintIntervalMs;
70 75
71 - private TbSqlBlockingQueue<AttributeKvEntity> queue; 76 + @Value("${sql.attributes.batch_threads:4}")
  77 + private int batchThreads;
  78 +
  79 + private TbSqlBlockingQueueWrapper<AttributeKvEntity> queue;
72 80
73 @PostConstruct 81 @PostConstruct
74 private void init() { 82 private void init() {
@@ -77,8 +85,11 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @@ -77,8 +85,11 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
77 .batchSize(batchSize) 85 .batchSize(batchSize)
78 .maxDelay(maxDelay) 86 .maxDelay(maxDelay)
79 .statsPrintIntervalMs(statsPrintIntervalMs) 87 .statsPrintIntervalMs(statsPrintIntervalMs)
  88 + .statsNamePrefix("attributes")
80 .build(); 89 .build();
81 - queue = new TbSqlBlockingQueue<>(params); 90 +
  91 + Function<AttributeKvEntity, Integer> hashcodeFunction = entity -> entity.getId().getEntityId().hashCode();
  92 + queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory);
82 queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v)); 93 queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v));
83 } 94 }
84 95
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts;
17 17
18 import com.google.common.util.concurrent.Futures; 18 import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.ListeningExecutorService;
20 import com.google.common.util.concurrent.MoreExecutors; 21 import com.google.common.util.concurrent.MoreExecutors;
21 import com.google.common.util.concurrent.SettableFuture; 22 import com.google.common.util.concurrent.SettableFuture;
22 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
@@ -29,10 +30,12 @@ import org.thingsboard.server.common.data.kv.Aggregation; @@ -29,10 +30,12 @@ import org.thingsboard.server.common.data.kv.Aggregation;
29 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; 30 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
30 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 31 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
31 import org.thingsboard.server.common.data.kv.TsKvEntry; 32 import org.thingsboard.server.common.data.kv.TsKvEntry;
  33 +import org.thingsboard.server.common.stats.StatsFactory;
32 import org.thingsboard.server.dao.DaoUtil; 34 import org.thingsboard.server.dao.DaoUtil;
33 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; 35 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
34 import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; 36 import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;
35 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; 37 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  38 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
36 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; 39 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
37 import org.thingsboard.server.dao.sqlts.ts.TsKvRepository; 40 import org.thingsboard.server.dao.sqlts.ts.TsKvRepository;
38 import org.thingsboard.server.dao.timeseries.TimeseriesDao; 41 import org.thingsboard.server.dao.timeseries.TimeseriesDao;
@@ -43,6 +46,7 @@ import java.util.ArrayList; @@ -43,6 +46,7 @@ import java.util.ArrayList;
43 import java.util.List; 46 import java.util.List;
44 import java.util.Optional; 47 import java.util.Optional;
45 import java.util.concurrent.CompletableFuture; 48 import java.util.concurrent.CompletableFuture;
  49 +import java.util.function.Function;
46 import java.util.stream.Collectors; 50 import java.util.stream.Collectors;
47 51
48 @Slf4j 52 @Slf4j
@@ -54,7 +58,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq @@ -54,7 +58,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
54 @Autowired 58 @Autowired
55 protected InsertTsRepository<TsKvEntity> insertRepository; 59 protected InsertTsRepository<TsKvEntity> insertRepository;
56 60
57 - protected TbSqlBlockingQueue<TsKvEntity> tsQueue; 61 + protected TbSqlBlockingQueueWrapper<TsKvEntity> tsQueue;
  62 + @Autowired
  63 + private StatsFactory statsFactory;
58 64
59 @PostConstruct 65 @PostConstruct
60 protected void init() { 66 protected void init() {
@@ -64,8 +70,11 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq @@ -64,8 +70,11 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
64 .batchSize(tsBatchSize) 70 .batchSize(tsBatchSize)
65 .maxDelay(tsMaxDelay) 71 .maxDelay(tsMaxDelay)
66 .statsPrintIntervalMs(tsStatsPrintIntervalMs) 72 .statsPrintIntervalMs(tsStatsPrintIntervalMs)
  73 + .statsNamePrefix("ts")
67 .build(); 74 .build();
68 - tsQueue = new TbSqlBlockingQueue<>(tsParams); 75 +
  76 + Function<TsKvEntity, Integer> hashcodeFunction = entity -> entity.getEntityId().hashCode();
  77 + tsQueue = new TbSqlBlockingQueueWrapper<>(tsParams, hashcodeFunction, tsBatchThreads, statsFactory);
69 tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); 78 tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v));
70 } 79 }
71 80
@@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
34 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 34 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
35 import org.thingsboard.server.common.data.kv.StringDataEntry; 35 import org.thingsboard.server.common.data.kv.StringDataEntry;
36 import org.thingsboard.server.common.data.kv.TsKvEntry; 36 import org.thingsboard.server.common.data.kv.TsKvEntry;
  37 +import org.thingsboard.server.common.stats.StatsFactory;
37 import org.thingsboard.server.dao.DaoUtil; 38 import org.thingsboard.server.dao.DaoUtil;
38 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; 39 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary;
39 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; 40 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey;
@@ -41,8 +42,8 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; @@ -41,8 +42,8 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey;
41 import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; 42 import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
42 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; 43 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
43 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; 44 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
44 -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;  
45 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; 45 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  46 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
46 import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; 47 import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository;
47 import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; 48 import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
48 import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository; 49 import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository;
@@ -52,7 +53,10 @@ import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; @@ -52,7 +53,10 @@ import org.thingsboard.server.dao.timeseries.SimpleListenableFuture;
52 import javax.annotation.Nullable; 53 import javax.annotation.Nullable;
53 import javax.annotation.PostConstruct; 54 import javax.annotation.PostConstruct;
54 import javax.annotation.PreDestroy; 55 import javax.annotation.PreDestroy;
  56 +import java.util.ArrayList;
  57 +import java.util.HashMap;
55 import java.util.List; 58 import java.util.List;
  59 +import java.util.Map;
56 import java.util.Objects; 60 import java.util.Objects;
57 import java.util.Optional; 61 import java.util.Optional;
58 import java.util.concurrent.ConcurrentHashMap; 62 import java.util.concurrent.ConcurrentHashMap;
@@ -82,7 +86,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx @@ -82,7 +86,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
82 @Autowired 86 @Autowired
83 private TsKvDictionaryRepository dictionaryRepository; 87 private TsKvDictionaryRepository dictionaryRepository;
84 88
85 - private TbSqlBlockingQueue<TsKvLatestEntity> tsLatestQueue; 89 + private TbSqlBlockingQueueWrapper<TsKvLatestEntity> tsLatestQueue;
86 90
87 @Value("${sql.ts_latest.batch_size:1000}") 91 @Value("${sql.ts_latest.batch_size:1000}")
88 private int tsLatestBatchSize; 92 private int tsLatestBatchSize;
@@ -93,9 +97,15 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx @@ -93,9 +97,15 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
93 @Value("${sql.ts_latest.stats_print_interval_ms:1000}") 97 @Value("${sql.ts_latest.stats_print_interval_ms:1000}")
94 private long tsLatestStatsPrintIntervalMs; 98 private long tsLatestStatsPrintIntervalMs;
95 99
  100 + @Value("${sql.ts_latest.batch_threads:4}")
  101 + private int tsLatestBatchThreads;
  102 +
96 @Autowired 103 @Autowired
97 protected ScheduledLogExecutorComponent logExecutor; 104 protected ScheduledLogExecutorComponent logExecutor;
98 105
  106 + @Autowired
  107 + private StatsFactory statsFactory;
  108 +
99 @Value("${sql.ts.batch_size:1000}") 109 @Value("${sql.ts.batch_size:1000}")
100 protected int tsBatchSize; 110 protected int tsBatchSize;
101 111
@@ -105,6 +115,12 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx @@ -105,6 +115,12 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
105 @Value("${sql.ts.stats_print_interval_ms:1000}") 115 @Value("${sql.ts.stats_print_interval_ms:1000}")
106 protected long tsStatsPrintIntervalMs; 116 protected long tsStatsPrintIntervalMs;
107 117
  118 + @Value("${sql.ts.batch_threads:4}")
  119 + protected int tsBatchThreads;
  120 +
  121 + @Value("${sql.timescale.batch_threads:4}")
  122 + protected int timescaleBatchThreads;
  123 +
108 @PostConstruct 124 @PostConstruct
109 protected void init() { 125 protected void init() {
110 TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder() 126 TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder()
@@ -112,9 +128,24 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx @@ -112,9 +128,24 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
112 .batchSize(tsLatestBatchSize) 128 .batchSize(tsLatestBatchSize)
113 .maxDelay(tsLatestMaxDelay) 129 .maxDelay(tsLatestMaxDelay)
114 .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs) 130 .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs)
  131 + .statsNamePrefix("ts.latest")
115 .build(); 132 .build();
116 - tsLatestQueue = new TbSqlBlockingQueue<>(tsLatestParams);  
117 - tsLatestQueue.init(logExecutor, v -> insertLatestTsRepository.saveOrUpdate(v)); 133 +
  134 + java.util.function.Function<TsKvLatestEntity, Integer> hashcodeFunction = entity -> entity.getEntityId().hashCode();
  135 + tsLatestQueue = new TbSqlBlockingQueueWrapper<>(tsLatestParams, hashcodeFunction, tsLatestBatchThreads, statsFactory);
  136 +
  137 + tsLatestQueue.init(logExecutor, v -> {
  138 + Map<TsKey, TsKvLatestEntity> trueLatest = new HashMap<>();
  139 + v.forEach(ts -> {
  140 + TsKey key = new TsKey(ts.getEntityId(), ts.getKey());
  141 + TsKvLatestEntity old = trueLatest.get(key);
  142 + if (old == null || old.getTs() < ts.getTs()) {
  143 + trueLatest.put(key, ts);
  144 + }
  145 + });
  146 + List<TsKvLatestEntity> latestEntities = new ArrayList<>(trueLatest.values());
  147 + insertLatestTsRepository.saveOrUpdate(latestEntities);
  148 + });
118 } 149 }
119 150
120 @PreDestroy 151 @PreDestroy
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sqlts;
  17 +
  18 +import lombok.Data;
  19 +
  20 +import java.util.UUID;
  21 +
  22 +@Data
  23 +public class TsKey {
  24 + private final UUID entityId;
  25 + private final int key;
  26 +}
@@ -31,10 +31,11 @@ import org.thingsboard.server.common.data.kv.Aggregation; @@ -31,10 +31,11 @@ import org.thingsboard.server.common.data.kv.Aggregation;
31 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; 31 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
32 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 32 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
33 import org.thingsboard.server.common.data.kv.TsKvEntry; 33 import org.thingsboard.server.common.data.kv.TsKvEntry;
  34 +import org.thingsboard.server.common.stats.StatsFactory;
34 import org.thingsboard.server.dao.DaoUtil; 35 import org.thingsboard.server.dao.DaoUtil;
35 import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; 36 import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity;
36 -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;  
37 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; 37 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  38 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
38 import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; 39 import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao;
39 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; 40 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
40 import org.thingsboard.server.dao.timeseries.TimeseriesDao; 41 import org.thingsboard.server.dao.timeseries.TimeseriesDao;
@@ -48,6 +49,7 @@ import java.util.List; @@ -48,6 +49,7 @@ import java.util.List;
48 import java.util.Optional; 49 import java.util.Optional;
49 import java.util.UUID; 50 import java.util.UUID;
50 import java.util.concurrent.CompletableFuture; 51 import java.util.concurrent.CompletableFuture;
  52 +import java.util.function.Function;
51 53
52 @Component 54 @Component
53 @Slf4j 55 @Slf4j
@@ -61,9 +63,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @@ -61,9 +63,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
61 private AggregationRepository aggregationRepository; 63 private AggregationRepository aggregationRepository;
62 64
63 @Autowired 65 @Autowired
  66 + private StatsFactory statsFactory;
  67 +
  68 + @Autowired
64 protected InsertTsRepository<TimescaleTsKvEntity> insertRepository; 69 protected InsertTsRepository<TimescaleTsKvEntity> insertRepository;
65 70
66 - protected TbSqlBlockingQueue<TimescaleTsKvEntity> tsQueue; 71 + protected TbSqlBlockingQueueWrapper<TimescaleTsKvEntity> tsQueue;
67 72
68 @PostConstruct 73 @PostConstruct
69 protected void init() { 74 protected void init() {
@@ -73,8 +78,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @@ -73,8 +78,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
73 .batchSize(tsBatchSize) 78 .batchSize(tsBatchSize)
74 .maxDelay(tsMaxDelay) 79 .maxDelay(tsMaxDelay)
75 .statsPrintIntervalMs(tsStatsPrintIntervalMs) 80 .statsPrintIntervalMs(tsStatsPrintIntervalMs)
  81 + .statsNamePrefix("ts.timescale")
76 .build(); 82 .build();
77 - tsQueue = new TbSqlBlockingQueue<>(tsParams); 83 +
  84 + Function<TimescaleTsKvEntity, Integer> hashcodeFunction = entity -> entity.getEntityId().hashCode();
  85 + tsQueue = new TbSqlBlockingQueueWrapper<>(tsParams, hashcodeFunction, timescaleBatchThreads, statsFactory);
  86 +
78 tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); 87 tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v));
79 } 88 }
80 89
@@ -277,4 +286,5 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @@ -277,4 +286,5 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
277 startTs, 286 startTs,
278 endTs); 287 endTs);
279 } 288 }
  289 +
280 } 290 }
@@ -23,6 +23,8 @@ import com.google.common.util.concurrent.SettableFuture; @@ -23,6 +23,8 @@ import com.google.common.util.concurrent.SettableFuture;
23 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
24 import org.thingsboard.common.util.ThingsBoardThreadFactory; 24 import org.thingsboard.common.util.ThingsBoardThreadFactory;
25 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.stats.StatsFactory;
  27 +import org.thingsboard.server.common.stats.StatsType;
26 import org.thingsboard.server.common.msg.tools.TbRateLimits; 28 import org.thingsboard.server.common.msg.tools.TbRateLimits;
27 import org.thingsboard.server.dao.nosql.CassandraStatementTask; 29 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
28 30
@@ -38,6 +40,8 @@ import java.util.regex.Matcher; @@ -38,6 +40,8 @@ import java.util.regex.Matcher;
38 @Slf4j 40 @Slf4j
39 public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extends ListenableFuture<V>, V> implements BufferedRateExecutor<T, F> { 41 public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extends ListenableFuture<V>, V> implements BufferedRateExecutor<T, F> {
40 42
  43 + public static final String CONCURRENCY_LEVEL = "currBuffer";
  44 +
41 private final long maxWaitTime; 45 private final long maxWaitTime;
42 private final long pollMs; 46 private final long pollMs;
43 private final BlockingQueue<AsyncTaskContext<T, V>> queue; 47 private final BlockingQueue<AsyncTaskContext<T, V>> queue;
@@ -49,20 +53,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -49,20 +53,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
49 private final boolean perTenantLimitsEnabled; 53 private final boolean perTenantLimitsEnabled;
50 private final String perTenantLimitsConfiguration; 54 private final String perTenantLimitsConfiguration;
51 private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>(); 55 private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
52 - protected final ConcurrentMap<TenantId, AtomicInteger> rateLimitedTenants = new ConcurrentHashMap<>();  
53 -  
54 - protected final AtomicInteger concurrencyLevel = new AtomicInteger();  
55 - protected final AtomicInteger totalAdded = new AtomicInteger();  
56 - protected final AtomicInteger totalLaunched = new AtomicInteger();  
57 - protected final AtomicInteger totalReleased = new AtomicInteger();  
58 - protected final AtomicInteger totalFailed = new AtomicInteger();  
59 - protected final AtomicInteger totalExpired = new AtomicInteger();  
60 - protected final AtomicInteger totalRejected = new AtomicInteger();  
61 - protected final AtomicInteger totalRateLimited = new AtomicInteger();  
62 - protected final AtomicInteger printQueriesIdx = new AtomicInteger(); 56 +
  57 + private final AtomicInteger printQueriesIdx = new AtomicInteger(0);
  58 +
  59 + protected final AtomicInteger concurrencyLevel;
  60 + protected final BufferedRateExecutorStats stats;
63 61
64 public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs, 62 public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs,
65 - boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq) { 63 + boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory) {
66 this.maxWaitTime = maxWaitTime; 64 this.maxWaitTime = maxWaitTime;
67 this.pollMs = pollMs; 65 this.pollMs = pollMs;
68 this.concurrencyLimit = concurrencyLimit; 66 this.concurrencyLimit = concurrencyLimit;
@@ -73,6 +71,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -73,6 +71,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
73 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout")); 71 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout"));
74 this.perTenantLimitsEnabled = perTenantLimitsEnabled; 72 this.perTenantLimitsEnabled = perTenantLimitsEnabled;
75 this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; 73 this.perTenantLimitsConfiguration = perTenantLimitsConfiguration;
  74 + this.stats = new BufferedRateExecutorStats(statsFactory);
  75 + String concurrencyLevelKey = StatsType.RATE_EXECUTOR.getName() + "." + CONCURRENCY_LEVEL;
  76 + this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0));
  77 +
76 for (int i = 0; i < dispatcherThreads; i++) { 78 for (int i = 0; i < dispatcherThreads; i++) {
77 dispatcherExecutor.submit(this::dispatch); 79 dispatcherExecutor.submit(this::dispatch);
78 } 80 }
@@ -89,8 +91,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -89,8 +91,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
89 } else if (!task.getTenantId().isNullUid()) { 91 } else if (!task.getTenantId().isNullUid()) {
90 TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(task.getTenantId(), id -> new TbRateLimits(perTenantLimitsConfiguration)); 92 TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(task.getTenantId(), id -> new TbRateLimits(perTenantLimitsConfiguration));
91 if (!rateLimits.tryConsume()) { 93 if (!rateLimits.tryConsume()) {
92 - rateLimitedTenants.computeIfAbsent(task.getTenantId(), tId -> new AtomicInteger(0)).incrementAndGet();  
93 - totalRateLimited.incrementAndGet(); 94 + stats.incrementRateLimitedTenant(task.getTenantId());
  95 + stats.getTotalRateLimited().increment();
94 settableFuture.setException(new TenantRateLimitException()); 96 settableFuture.setException(new TenantRateLimitException());
95 perTenantLimitReached = true; 97 perTenantLimitReached = true;
96 } 98 }
@@ -98,10 +100,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -98,10 +100,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
98 } 100 }
99 if (!perTenantLimitReached) { 101 if (!perTenantLimitReached) {
100 try { 102 try {
101 - totalAdded.incrementAndGet(); 103 + stats.getTotalAdded().increment();
102 queue.add(new AsyncTaskContext<>(UUID.randomUUID(), task, settableFuture, System.currentTimeMillis())); 104 queue.add(new AsyncTaskContext<>(UUID.randomUUID(), task, settableFuture, System.currentTimeMillis()));
103 } catch (IllegalStateException e) { 105 } catch (IllegalStateException e) {
104 - totalRejected.incrementAndGet(); 106 + stats.getTotalRejected().increment();
105 settableFuture.setException(e); 107 settableFuture.setException(e);
106 } 108 }
107 } 109 }
@@ -146,14 +148,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -146,14 +148,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
146 concurrencyLevel.incrementAndGet(); 148 concurrencyLevel.incrementAndGet();
147 long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis(); 149 long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis();
148 if (timeout > 0) { 150 if (timeout > 0) {
149 - totalLaunched.incrementAndGet(); 151 + stats.getTotalLaunched().increment();
150 ListenableFuture<V> result = execute(finalTaskCtx); 152 ListenableFuture<V> result = execute(finalTaskCtx);
151 result = Futures.withTimeout(result, timeout, TimeUnit.MILLISECONDS, timeoutExecutor); 153 result = Futures.withTimeout(result, timeout, TimeUnit.MILLISECONDS, timeoutExecutor);
152 Futures.addCallback(result, new FutureCallback<V>() { 154 Futures.addCallback(result, new FutureCallback<V>() {
153 @Override 155 @Override
154 public void onSuccess(@Nullable V result) { 156 public void onSuccess(@Nullable V result) {
155 logTask("Releasing", finalTaskCtx); 157 logTask("Releasing", finalTaskCtx);
156 - totalReleased.incrementAndGet(); 158 + stats.getTotalReleased().increment();
157 concurrencyLevel.decrementAndGet(); 159 concurrencyLevel.decrementAndGet();
158 finalTaskCtx.getFuture().set(result); 160 finalTaskCtx.getFuture().set(result);
159 } 161 }
@@ -165,7 +167,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -165,7 +167,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
165 } else { 167 } else {
166 logTask("Failed", finalTaskCtx); 168 logTask("Failed", finalTaskCtx);
167 } 169 }
168 - totalFailed.incrementAndGet(); 170 + stats.getTotalFailed().increment();
169 concurrencyLevel.decrementAndGet(); 171 concurrencyLevel.decrementAndGet();
170 finalTaskCtx.getFuture().setException(t); 172 finalTaskCtx.getFuture().setException(t);
171 log.debug("[{}] Failed to execute task: {}", finalTaskCtx.getId(), finalTaskCtx.getTask(), t); 173 log.debug("[{}] Failed to execute task: {}", finalTaskCtx.getId(), finalTaskCtx.getTask(), t);
@@ -173,7 +175,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -173,7 +175,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
173 }, callbackExecutor); 175 }, callbackExecutor);
174 } else { 176 } else {
175 logTask("Expired Before Execution", finalTaskCtx); 177 logTask("Expired Before Execution", finalTaskCtx);
176 - totalExpired.incrementAndGet(); 178 + stats.getTotalExpired().increment();
177 concurrencyLevel.decrementAndGet(); 179 concurrencyLevel.decrementAndGet();
178 taskCtx.getFuture().setException(new TimeoutException()); 180 taskCtx.getFuture().setException(new TimeoutException());
179 } 181 }
@@ -185,7 +187,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend @@ -185,7 +187,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
185 } catch (Throwable e) { 187 } catch (Throwable e) {
186 if (taskCtx != null) { 188 if (taskCtx != null) {
187 log.debug("[{}] Failed to execute task: {}", taskCtx.getId(), taskCtx, e); 189 log.debug("[{}] Failed to execute task: {}", taskCtx.getId(), taskCtx, e);
188 - totalFailed.incrementAndGet(); 190 + stats.getTotalFailed().increment();
189 concurrencyLevel.decrementAndGet(); 191 concurrencyLevel.decrementAndGet();
190 } else { 192 } else {
191 log.debug("Failed to queue task:", e); 193 log.debug("Failed to queue task:", e);
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.util;
  17 +
  18 +import lombok.Getter;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.stats.DefaultCounter;
  22 +import org.thingsboard.server.common.stats.StatsCounter;
  23 +import org.thingsboard.server.common.stats.StatsFactory;
  24 +import org.thingsboard.server.common.stats.StatsType;
  25 +
  26 +import java.util.ArrayList;
  27 +import java.util.List;
  28 +import java.util.concurrent.ConcurrentHashMap;
  29 +import java.util.concurrent.ConcurrentMap;
  30 +
  31 +@Slf4j
  32 +@Getter
  33 +public class BufferedRateExecutorStats {
  34 + private static final String TENANT_ID_TAG = "tenantId";
  35 +
  36 +
  37 + private static final String TOTAL_ADDED = "totalAdded";
  38 + private static final String TOTAL_LAUNCHED = "totalLaunched";
  39 + private static final String TOTAL_RELEASED = "totalReleased";
  40 + private static final String TOTAL_FAILED = "totalFailed";
  41 + private static final String TOTAL_EXPIRED = "totalExpired";
  42 + private static final String TOTAL_REJECTED = "totalRejected";
  43 + private static final String TOTAL_RATE_LIMITED = "totalRateLimited";
  44 +
  45 + private final StatsFactory statsFactory;
  46 +
  47 + private final ConcurrentMap<TenantId, DefaultCounter> rateLimitedTenants = new ConcurrentHashMap<>();
  48 +
  49 + private final List<StatsCounter> statsCounters = new ArrayList<>();
  50 +
  51 + private final StatsCounter totalAdded;
  52 + private final StatsCounter totalLaunched;
  53 + private final StatsCounter totalReleased;
  54 + private final StatsCounter totalFailed;
  55 + private final StatsCounter totalExpired;
  56 + private final StatsCounter totalRejected;
  57 + private final StatsCounter totalRateLimited;
  58 +
  59 + public BufferedRateExecutorStats(StatsFactory statsFactory) {
  60 + this.statsFactory = statsFactory;
  61 +
  62 + String key = StatsType.RATE_EXECUTOR.getName();
  63 +
  64 + this.totalAdded = statsFactory.createStatsCounter(key, TOTAL_ADDED);
  65 + this.totalLaunched = statsFactory.createStatsCounter(key, TOTAL_LAUNCHED);
  66 + this.totalReleased = statsFactory.createStatsCounter(key, TOTAL_RELEASED);
  67 + this.totalFailed = statsFactory.createStatsCounter(key, TOTAL_FAILED);
  68 + this.totalExpired = statsFactory.createStatsCounter(key, TOTAL_EXPIRED);
  69 + this.totalRejected = statsFactory.createStatsCounter(key, TOTAL_REJECTED);
  70 + this.totalRateLimited = statsFactory.createStatsCounter(key, TOTAL_RATE_LIMITED);
  71 +
  72 + this.statsCounters.add(totalAdded);
  73 + this.statsCounters.add(totalLaunched);
  74 + this.statsCounters.add(totalReleased);
  75 + this.statsCounters.add(totalFailed);
  76 + this.statsCounters.add(totalExpired);
  77 + this.statsCounters.add(totalRejected);
  78 + this.statsCounters.add(totalRateLimited);
  79 + }
  80 +
  81 + public void incrementRateLimitedTenant(TenantId tenantId){
  82 + rateLimitedTenants.computeIfAbsent(tenantId,
  83 + tId -> {
  84 + String key = StatsType.RATE_EXECUTOR.getName() + ".tenant";
  85 + return statsFactory.createDefaultCounter(key, TENANT_ID_TAG, tId.toString());
  86 + }
  87 + )
  88 + .increment();
  89 + }
  90 +}
@@ -105,6 +105,7 @@ @@ -105,6 +105,7 @@
105 <ua-parser.version>1.4.3</ua-parser.version> 105 <ua-parser.version>1.4.3</ua-parser.version>
106 <commons-beanutils.version>1.9.4</commons-beanutils.version> 106 <commons-beanutils.version>1.9.4</commons-beanutils.version>
107 <commons-collections.version>3.2.2</commons-collections.version> 107 <commons-collections.version>3.2.2</commons-collections.version>
  108 + <micrometer.version>1.5.2</micrometer.version>
108 </properties> 109 </properties>
109 110
110 <modules> 111 <modules>
@@ -843,6 +844,11 @@ @@ -843,6 +844,11 @@
843 <version>${project.version}</version> 844 <version>${project.version}</version>
844 </dependency> 845 </dependency>
845 <dependency> 846 <dependency>
  847 + <groupId>org.thingsboard.common</groupId>
  848 + <artifactId>stats</artifactId>
  849 + <version>${project.version}</version>
  850 + </dependency>
  851 + <dependency>
846 <groupId>org.thingsboard</groupId> 852 <groupId>org.thingsboard</groupId>
847 <artifactId>tools</artifactId> 853 <artifactId>tools</artifactId>
848 <version>${project.version}</version> 854 <version>${project.version}</version>
@@ -1376,6 +1382,21 @@ @@ -1376,6 +1382,21 @@
1376 <artifactId>struts-tiles</artifactId> 1382 <artifactId>struts-tiles</artifactId>
1377 <version>${struts.version}</version> 1383 <version>${struts.version}</version>
1378 </dependency> 1384 </dependency>
  1385 + <dependency>
  1386 + <groupId>org.springframework.boot</groupId>
  1387 + <artifactId>spring-boot-starter-actuator</artifactId>
  1388 + <version>${spring-boot.version}</version>
  1389 + </dependency>
  1390 + <dependency>
  1391 + <groupId>io.micrometer</groupId>
  1392 + <artifactId>micrometer-core</artifactId>
  1393 + <version>${micrometer.version}</version>
  1394 + </dependency>
  1395 + <dependency>
  1396 + <groupId>io.micrometer</groupId>
  1397 + <artifactId>micrometer-registry-prometheus</artifactId>
  1398 + <version>${micrometer.version}</version>
  1399 + </dependency>
1379 </dependencies> 1400 </dependencies>
1380 </dependencyManagement> 1401 </dependencyManagement>
1381 1402
@@ -40,7 +40,7 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -40,7 +40,7 @@ import org.thingsboard.server.common.msg.TbMsg;
40 nodeDetails = 40 nodeDetails =
41 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + 41 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
42 "Node output:\n" + 42 "Node output:\n" +
43 - "If alarm was not cleared, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains 'isClearedAlarm' property. " + 43 + "If alarm was not cleared, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'metadata' will contains 'isClearedAlarm' property. " +
44 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " + 44 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " +
45 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.", 45 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.",
46 uiResources = {"static/rulenode/rulenode-core-config.js"}, 46 uiResources = {"static/rulenode/rulenode-core-config.js"},
@@ -46,7 +46,7 @@ import java.util.List; @@ -46,7 +46,7 @@ import java.util.List;
46 nodeDetails = 46 nodeDetails =
47 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + 47 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
48 "Node output:\n" + 48 "Node output:\n" +
49 - "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains one of those properties 'isNewAlarm/isExistingAlarm'. " + 49 + "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'metadata' will contains one of those properties 'isNewAlarm/isExistingAlarm'. " +
50 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " + 50 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " +
51 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.", 51 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.",
52 uiResources = {"static/rulenode/rulenode-core-config.js"}, 52 uiResources = {"static/rulenode/rulenode-core-config.js"},
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.rule.engine.edge; 16 package org.thingsboard.rule.engine.edge;
17 17
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
19 import com.fasterxml.jackson.databind.ObjectMapper; 20 import com.fasterxml.jackson.databind.ObjectMapper;
20 import com.google.common.util.concurrent.FutureCallback; 21 import com.google.common.util.concurrent.FutureCallback;
21 import com.google.common.util.concurrent.Futures; 22 import com.google.common.util.concurrent.Futures;
@@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsg;
45 import org.thingsboard.server.common.msg.session.SessionMsgType; 46 import org.thingsboard.server.common.msg.session.SessionMsgType;
46 47
47 import javax.annotation.Nullable; 48 import javax.annotation.Nullable;
  49 +import java.util.HashMap;
48 import java.util.List; 50 import java.util.List;
  51 +import java.util.Map;
  52 +import java.util.UUID;
49 53
50 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; 54 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
51 55
@@ -84,30 +88,33 @@ public class TbMsgPushToEdgeNode implements TbNode { @@ -84,30 +88,33 @@ public class TbMsgPushToEdgeNode implements TbNode {
84 Futures.addCallback(getEdgeIdFuture, new FutureCallback<EdgeId>() { 88 Futures.addCallback(getEdgeIdFuture, new FutureCallback<EdgeId>() {
85 @Override 89 @Override
86 public void onSuccess(@Nullable EdgeId edgeId) { 90 public void onSuccess(@Nullable EdgeId edgeId) {
87 - EdgeEventType edgeEventTypeByEntityType = EdgeUtils.getEdgeEventTypeByEntityType(msg.getOriginator().getEntityType());  
88 - if (edgeEventTypeByEntityType == null) {  
89 - log.debug("Edge event type is null. Entity Type {}", msg.getOriginator().getEntityType());  
90 - ctx.tellFailure(msg, new RuntimeException("Edge event type is null. Entity Type '" + msg.getOriginator().getEntityType() + "'"));  
91 - }  
92 - EdgeEvent edgeEvent = null;  
93 try { 91 try {
94 - edgeEvent = buildEdgeEvent(ctx, msg, edgeId, edgeEventTypeByEntityType); 92 + EdgeEvent edgeEvent = buildEdgeEvent(msg, ctx);
  93 + if (edgeEvent == null) {
  94 + log.debug("Edge event type is null. Entity Type {}", msg.getOriginator().getEntityType());
  95 + ctx.tellFailure(msg, new RuntimeException("Edge event type is null. Entity Type '" + msg.getOriginator().getEntityType() + "'"));
  96 + } else {
  97 + edgeEvent.setEdgeId(edgeId);
  98 + ListenableFuture<EdgeEvent> saveFuture = ctx.getEdgeEventService().saveAsync(edgeEvent);
  99 + Futures.addCallback(saveFuture, new FutureCallback<EdgeEvent>() {
  100 + @Override
  101 + public void onSuccess(@Nullable EdgeEvent event) {
  102 + ctx.tellNext(msg, SUCCESS);
  103 + }
  104 +
  105 + @Override
  106 + public void onFailure(Throwable th) {
  107 + log.error("Could not save edge event", th);
  108 + ctx.tellFailure(msg, th);
  109 + }
  110 + }, ctx.getDbCallbackExecutor());
  111 + }
95 } catch (JsonProcessingException e) { 112 } catch (JsonProcessingException e) {
96 log.error("Failed to build edge event", e); 113 log.error("Failed to build edge event", e);
  114 + ctx.tellFailure(msg, e);
97 } 115 }
98 - ListenableFuture<EdgeEvent> saveFuture = ctx.getEdgeEventService().saveAsync(edgeEvent);  
99 - Futures.addCallback(saveFuture, new FutureCallback<EdgeEvent>() {  
100 - @Override  
101 - public void onSuccess(@Nullable EdgeEvent event) {  
102 - ctx.tellNext(msg, SUCCESS);  
103 - }  
104 - @Override  
105 - public void onFailure(Throwable th) {  
106 - log.error("Could not save edge event", th);  
107 - ctx.tellFailure(msg, th);  
108 - }  
109 - }, ctx.getDbCallbackExecutor());  
110 } 116 }
  117 +
111 @Override 118 @Override
112 public void onFailure(Throwable t) { 119 public void onFailure(Throwable t) {
113 ctx.tellFailure(msg, t); 120 ctx.tellFailure(msg, t);
@@ -124,17 +131,57 @@ public class TbMsgPushToEdgeNode implements TbNode { @@ -124,17 +131,57 @@ public class TbMsgPushToEdgeNode implements TbNode {
124 } 131 }
125 } 132 }
126 133
127 - private EdgeEvent buildEdgeEvent(TbContext ctx, TbMsg msg, EdgeId edgeId, EdgeEventType edgeEventTypeByEntityType) throws JsonProcessingException { 134 + private EdgeEvent buildEdgeEvent(TbMsg msg, TbContext ctx) throws JsonProcessingException {
  135 + if (DataConstants.ALARM.equals(msg.getType())) {
  136 + return buildEdgeEvent(ctx.getTenantId(), ActionType.ADDED, getUUIDFromMsgData(msg), EdgeEventType.ALARM, null);
  137 + } else {
  138 + EdgeEventType edgeEventTypeByEntityType = EdgeUtils.getEdgeEventTypeByEntityType(msg.getOriginator().getEntityType());
  139 + if (edgeEventTypeByEntityType == null) {
  140 + return null;
  141 + }
  142 + ActionType actionType = getActionTypeByMsgType(msg.getType());
  143 + JsonNode entityBody = null;
  144 + JsonNode data = json.readTree(msg.getData());
  145 + if (actionType.equals(ActionType.ATTRIBUTES_UPDATED) || actionType.equals(ActionType.ATTRIBUTES_DELETED)) {
  146 + entityBody = getAttributeEntityBody(actionType, data, msg.getMetaData().getData());
  147 + } else {
  148 + entityBody = data;
  149 + }
  150 + return buildEdgeEvent(ctx.getTenantId(), actionType, msg.getOriginator().getId(), edgeEventTypeByEntityType, entityBody);
  151 + }
  152 + }
  153 +
  154 + private EdgeEvent buildEdgeEvent(TenantId tenantId, ActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) {
128 EdgeEvent edgeEvent = new EdgeEvent(); 155 EdgeEvent edgeEvent = new EdgeEvent();
129 - edgeEvent.setTenantId(ctx.getTenantId());  
130 - edgeEvent.setEdgeId(edgeId);  
131 - edgeEvent.setEdgeEventAction(getActionTypeByMsgType(msg.getType()).name());  
132 - edgeEvent.setEntityId(msg.getOriginator().getId());  
133 - edgeEvent.setEdgeEventType(edgeEventTypeByEntityType);  
134 - edgeEvent.setEntityBody(json.readTree(msg.getData())); 156 + edgeEvent.setTenantId(tenantId);
  157 + edgeEvent.setEdgeEventAction(edgeEventAction.name());
  158 + edgeEvent.setEntityId(entityId);
  159 + edgeEvent.setEdgeEventType(edgeEventType);
  160 + edgeEvent.setEntityBody(entityBody);
135 return edgeEvent; 161 return edgeEvent;
136 } 162 }
137 163
  164 + private JsonNode getAttributeEntityBody(ActionType actionType, JsonNode data, Map<String, String> metadata) throws JsonProcessingException {
  165 + Map<String, Object> entityData = new HashMap<>();
  166 + switch (actionType) {
  167 + case ATTRIBUTES_UPDATED:
  168 + entityData.put("kv", data);
  169 + break;
  170 + case ATTRIBUTES_DELETED:
  171 + List<String> keys = json.treeToValue(data.get("attributes"), List.class);
  172 + entityData.put("keys", keys);
  173 + break;
  174 + }
  175 + entityData.put("scope", metadata.get("scope"));
  176 + return json.valueToTree(entityData);
  177 + }
  178 +
  179 + private UUID getUUIDFromMsgData(TbMsg msg) throws JsonProcessingException {
  180 + JsonNode data = json.readTree(msg.getData()).get("id");
  181 + String id = json.treeToValue(data.get("id"), String.class);
  182 + return UUID.fromString(id);
  183 + }
  184 +
138 private ActionType getActionTypeByMsgType(String msgType) { 185 private ActionType getActionTypeByMsgType(String msgType) {
139 ActionType actionType; 186 ActionType actionType;
140 if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)) { 187 if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)) {
@@ -161,14 +208,11 @@ public class TbMsgPushToEdgeNode implements TbNode { @@ -161,14 +208,11 @@ public class TbMsgPushToEdgeNode implements TbNode {
161 } 208 }
162 209
163 private boolean isSupportedMsgType(String msgType) { 210 private boolean isSupportedMsgType(String msgType) {
164 - if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) 211 + return SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)
165 || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType) 212 || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)
166 || DataConstants.ATTRIBUTES_UPDATED.equals(msgType) 213 || DataConstants.ATTRIBUTES_UPDATED.equals(msgType)
167 - || DataConstants.ATTRIBUTES_DELETED.equals(msgType)) {  
168 - return true;  
169 - } else {  
170 - return false;  
171 - } 214 + || DataConstants.ATTRIBUTES_DELETED.equals(msgType)
  215 + || DataConstants.ALARM.equals(msgType);
172 } 216 }
173 217
174 private ListenableFuture<EdgeId> getEdgeIdByOriginatorId(TbContext ctx, TenantId tenantId, EntityId originatorId) { 218 private ListenableFuture<EdgeId> getEdgeIdByOriginatorId(TbContext ctx, TenantId tenantId, EntityId originatorId) {
@@ -67,7 +67,7 @@ public class TbCheckAlarmStatusNode implements TbNode { @@ -67,7 +67,7 @@ public class TbCheckAlarmStatusNode implements TbNode {
67 if (result != null) { 67 if (result != null) {
68 boolean isPresent = false; 68 boolean isPresent = false;
69 for (AlarmStatus alarmStatus : config.getAlarmStatusList()) { 69 for (AlarmStatus alarmStatus : config.getAlarmStatusList()) {
70 - if (alarm.getStatus() == alarmStatus) { 70 + if (result.getStatus() == alarmStatus) {
71 isPresent = true; 71 isPresent = true;
72 break; 72 break;
73 } 73 }
@@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
16 package org.thingsboard.rule.engine.mqtt; 16 package org.thingsboard.rule.engine.mqtt;
17 17
18 import io.netty.buffer.Unpooled; 18 import io.netty.buffer.Unpooled;
19 -import io.netty.channel.EventLoopGroup;  
20 -import io.netty.channel.nio.NioEventLoopGroup;  
21 import io.netty.handler.codec.mqtt.MqttQoS; 19 import io.netty.handler.codec.mqtt.MqttQoS;
22 import io.netty.handler.ssl.SslContext; 20 import io.netty.handler.ssl.SslContext;
23 import io.netty.handler.ssl.SslContextBuilder; 21 import io.netty.handler.ssl.SslContextBuilder;
@@ -37,7 +35,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -37,7 +35,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
37 import javax.net.ssl.SSLException; 35 import javax.net.ssl.SSLException;
38 import java.nio.charset.Charset; 36 import java.nio.charset.Charset;
39 import java.util.Optional; 37 import java.util.Optional;
40 -import java.util.concurrent.ExecutionException;  
41 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.TimeUnit;
42 import java.util.concurrent.TimeoutException; 39 import java.util.concurrent.TimeoutException;
43 40
@@ -58,14 +55,14 @@ public class TbMqttNode implements TbNode { @@ -58,14 +55,14 @@ public class TbMqttNode implements TbNode {
58 55
59 private static final String ERROR = "error"; 56 private static final String ERROR = "error";
60 57
61 - private TbMqttNodeConfiguration config; 58 + protected TbMqttNodeConfiguration mqttNodeConfiguration;
62 59
63 - private MqttClient mqttClient; 60 + protected MqttClient mqttClient;
64 61
65 @Override 62 @Override
66 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 63 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
67 try { 64 try {
68 - this.config = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class); 65 + this.mqttNodeConfiguration = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class);
69 this.mqttClient = initClient(ctx); 66 this.mqttClient = initClient(ctx);
70 } catch (Exception e) { 67 } catch (Exception e) {
71 throw new TbNodeException(e); 68 throw new TbNodeException(e);
@@ -74,7 +71,7 @@ public class TbMqttNode implements TbNode { @@ -74,7 +71,7 @@ public class TbMqttNode implements TbNode {
74 71
75 @Override 72 @Override
76 public void onMsg(TbContext ctx, TbMsg msg) { 73 public void onMsg(TbContext ctx, TbMsg msg) {
77 - String topic = TbNodeUtils.processPattern(this.config.getTopicPattern(), msg.getMetaData()); 74 + String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg.getMetaData());
78 this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) 75 this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE)
79 .addListener(future -> { 76 .addListener(future -> {
80 if (future.isSuccess()) { 77 if (future.isSuccess()) {
@@ -100,38 +97,38 @@ public class TbMqttNode implements TbNode { @@ -100,38 +97,38 @@ public class TbMqttNode implements TbNode {
100 } 97 }
101 } 98 }
102 99
103 - private MqttClient initClient(TbContext ctx) throws Exception { 100 + protected MqttClient initClient(TbContext ctx) throws Exception {
104 Optional<SslContext> sslContextOpt = initSslContext(); 101 Optional<SslContext> sslContextOpt = initSslContext();
105 MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig(); 102 MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig();
106 - if (!StringUtils.isEmpty(this.config.getClientId())) {  
107 - config.setClientId(this.config.getClientId()); 103 + if (!StringUtils.isEmpty(this.mqttNodeConfiguration.getClientId())) {
  104 + config.setClientId(this.mqttNodeConfiguration.getClientId());
108 } 105 }
109 - config.setCleanSession(this.config.isCleanSession());  
110 - this.config.getCredentials().configure(config); 106 + config.setCleanSession(this.mqttNodeConfiguration.isCleanSession());
  107 + this.mqttNodeConfiguration.getCredentials().configure(config);
111 MqttClient client = MqttClient.create(config, null); 108 MqttClient client = MqttClient.create(config, null);
112 client.setEventLoop(ctx.getSharedEventLoop()); 109 client.setEventLoop(ctx.getSharedEventLoop());
113 - Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort()); 110 + Future<MqttConnectResult> connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort());
114 MqttConnectResult result; 111 MqttConnectResult result;
115 try { 112 try {
116 - result = connectFuture.get(this.config.getConnectTimeoutSec(), TimeUnit.SECONDS); 113 + result = connectFuture.get(this.mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS);
117 } catch (TimeoutException ex) { 114 } catch (TimeoutException ex) {
118 connectFuture.cancel(true); 115 connectFuture.cancel(true);
119 client.disconnect(); 116 client.disconnect();
120 - String hostPort = this.config.getHost() + ":" + this.config.getPort(); 117 + String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
121 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort)); 118 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort));
122 } 119 }
123 if (!result.isSuccess()) { 120 if (!result.isSuccess()) {
124 connectFuture.cancel(true); 121 connectFuture.cancel(true);
125 client.disconnect(); 122 client.disconnect();
126 - String hostPort = this.config.getHost() + ":" + this.config.getPort(); 123 + String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
127 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode())); 124 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode()));
128 } 125 }
129 return client; 126 return client;
130 } 127 }
131 128
132 private Optional<SslContext> initSslContext() throws SSLException { 129 private Optional<SslContext> initSslContext() throws SSLException {
133 - Optional<SslContext> result = this.config.getCredentials().initSslContext();  
134 - if (this.config.isSsl() && !result.isPresent()) { 130 + Optional<SslContext> result = this.mqttNodeConfiguration.getCredentials().initSslContext();
  131 + if (this.mqttNodeConfiguration.isSsl() && !result.isPresent()) {
135 result = Optional.of(SslContextBuilder.forClient().build()); 132 result = Optional.of(SslContextBuilder.forClient().build());
136 } 133 }
137 return result; 134 return result;
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.mqtt.azure;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  19 +import io.netty.handler.ssl.ClientAuth;
  20 +import io.netty.handler.ssl.SslContext;
  21 +import io.netty.handler.ssl.SslContextBuilder;
  22 +import lombok.Data;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.apache.commons.codec.binary.Base64;
  25 +import org.bouncycastle.jce.provider.BouncyCastleProvider;
  26 +import org.thingsboard.common.util.AzureIotHubUtil;
  27 +import org.thingsboard.mqtt.MqttClientConfig;
  28 +import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials;
  29 +
  30 +import javax.net.ssl.TrustManagerFactory;
  31 +import java.io.ByteArrayInputStream;
  32 +import java.security.KeyStore;
  33 +import java.security.Security;
  34 +import java.security.cert.CertificateFactory;
  35 +import java.security.cert.X509Certificate;
  36 +import java.util.Optional;
  37 +
  38 +@Data
  39 +@Slf4j
  40 +@JsonIgnoreProperties(ignoreUnknown = true)
  41 +public class AzureIotHubSasCredentials implements MqttClientCredentials {
  42 + private String sasKey;
  43 + private String caCert;
  44 +
  45 + @Override
  46 + public Optional<SslContext> initSslContext() {
  47 + try {
  48 + Security.addProvider(new BouncyCastleProvider());
  49 + if (caCert == null || caCert.isEmpty()) {
  50 + caCert = AzureIotHubUtil.getDefaultCaCert();
  51 + }
  52 + return Optional.of(SslContextBuilder.forClient()
  53 + .trustManager(createAndInitTrustManagerFactory())
  54 + .clientAuth(ClientAuth.REQUIRE)
  55 + .build());
  56 + } catch (Exception e) {
  57 + log.error("[{}] Creating TLS factory failed!", caCert, e);
  58 + throw new RuntimeException("Creating TLS factory failed!", e);
  59 + }
  60 + }
  61 +
  62 + @Override
  63 + public void configure(MqttClientConfig config) {
  64 + }
  65 +
  66 + private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception {
  67 + X509Certificate caCertHolder;
  68 + caCertHolder = readCertFile(caCert);
  69 +
  70 + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  71 + caKeyStore.load(null, null);
  72 + caKeyStore.setCertificateEntry("caCert-cert", caCertHolder);
  73 +
  74 + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  75 + trustManagerFactory.init(caKeyStore);
  76 + return trustManagerFactory;
  77 + }
  78 +
  79 + private X509Certificate readCertFile(String fileContent) throws Exception {
  80 + X509Certificate certificate = null;
  81 + if (fileContent != null && !fileContent.trim().isEmpty()) {
  82 + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "")
  83 + .replace("-----END CERTIFICATE-----", "")
  84 + .replaceAll("\\s", "");
  85 + byte[] decoded = Base64.decodeBase64(fileContent);
  86 + CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  87 + certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decoded));
  88 + }
  89 + return certificate;
  90 + }
  91 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.mqtt.azure;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttVersion;
  19 +import io.netty.handler.ssl.SslContext;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.common.util.AzureIotHubUtil;
  22 +import org.thingsboard.mqtt.MqttClientConfig;
  23 +import org.thingsboard.rule.engine.api.RuleNode;
  24 +import org.thingsboard.rule.engine.api.TbContext;
  25 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  26 +import org.thingsboard.rule.engine.api.TbNodeException;
  27 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  28 +import org.thingsboard.rule.engine.mqtt.TbMqttNode;
  29 +import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration;
  30 +import org.thingsboard.rule.engine.mqtt.credentials.CertPemClientCredentials;
  31 +import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials;
  32 +import org.thingsboard.server.common.data.plugin.ComponentType;
  33 +
  34 +import java.util.Optional;
  35 +
  36 +@Slf4j
  37 +@RuleNode(
  38 + type = ComponentType.EXTERNAL,
  39 + name = "azure iot hub",
  40 + configClazz = TbAzureIotHubNodeConfiguration.class,
  41 + nodeDescription = "Publish messages to the Azure IoT Hub",
  42 + nodeDetails = "Will publish message payload to the Azure IoT Hub with QoS <b>AT_LEAST_ONCE</b>.",
  43 + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
  44 + configDirective = "tbActionNodeAzureIotHubConfig"
  45 +)
  46 +public class TbAzureIotHubNode extends TbMqttNode {
  47 + @Override
  48 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  49 + try {
  50 + this.mqttNodeConfiguration = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class);
  51 + mqttNodeConfiguration.setPort(8883);
  52 + mqttNodeConfiguration.setCleanSession(true);
  53 + MqttClientCredentials credentials = mqttNodeConfiguration.getCredentials();
  54 + mqttNodeConfiguration.setCredentials(new MqttClientCredentials() {
  55 + @Override
  56 + public Optional<SslContext> initSslContext() {
  57 + if (credentials instanceof AzureIotHubSasCredentials) {
  58 + AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials;
  59 + if (sasCredentials.getCaCert() == null || sasCredentials.getCaCert().isEmpty()) {
  60 + sasCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert());
  61 + }
  62 + } else if (credentials instanceof CertPemClientCredentials) {
  63 + CertPemClientCredentials pemCredentials = (CertPemClientCredentials) credentials;
  64 + if (pemCredentials.getCaCert() == null || pemCredentials.getCaCert().isEmpty()) {
  65 + pemCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert());
  66 + }
  67 + }
  68 + return credentials.initSslContext();
  69 + }
  70 +
  71 + @Override
  72 + public void configure(MqttClientConfig config) {
  73 + config.setProtocolVersion(MqttVersion.MQTT_3_1_1);
  74 + config.setUsername(AzureIotHubUtil.buildUsername(mqttNodeConfiguration.getHost(), config.getClientId()));
  75 + if (credentials instanceof AzureIotHubSasCredentials) {
  76 + AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials;
  77 + config.setPassword(AzureIotHubUtil.buildSasToken(mqttNodeConfiguration.getHost(), sasCredentials.getSasKey()));
  78 + }
  79 + }
  80 + });
  81 +
  82 + this.mqttClient = initClient(ctx);
  83 + } catch (Exception e) {
  84 + throw new TbNodeException(e);
  85 + } }
  86 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.mqtt.azure;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration;
  21 +import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials;
  22 +import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials;
  23 +
  24 +@Data
  25 +public class TbAzureIotHubNodeConfiguration extends TbMqttNodeConfiguration {
  26 +
  27 + @Override
  28 + public TbAzureIotHubNodeConfiguration defaultConfiguration() {
  29 + TbAzureIotHubNodeConfiguration configuration = new TbAzureIotHubNodeConfiguration();
  30 + configuration.setTopicPattern("devices/<device_id>/messages/events/");
  31 + configuration.setHost("<iot-hub-name>.azure-devices.net");
  32 + configuration.setPort(8883);
  33 + configuration.setConnectTimeoutSec(10);
  34 + configuration.setCleanSession(true);
  35 + configuration.setSsl(true);
  36 + configuration.setCredentials(new AzureIotHubSasCredentials());
  37 + return configuration;
  38 + }
  39 +
  40 +}
@@ -163,7 +163,7 @@ public class CertPemClientCredentials implements MqttClientCredentials { @@ -163,7 +163,7 @@ public class CertPemClientCredentials implements MqttClientCredentials {
163 163
164 private KeySpec getKeySpec(byte[] encodedKey) throws Exception { 164 private KeySpec getKeySpec(byte[] encodedKey) throws Exception {
165 KeySpec keySpec; 165 KeySpec keySpec;
166 - if (password == null) { 166 + if (password == null || password.isEmpty()) {
167 keySpec = new PKCS8EncodedKeySpec(encodedKey); 167 keySpec = new PKCS8EncodedKeySpec(encodedKey);
168 } else { 168 } else {
169 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); 169 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
19 import com.fasterxml.jackson.annotation.JsonTypeInfo; 19 import com.fasterxml.jackson.annotation.JsonTypeInfo;
20 import io.netty.handler.ssl.SslContext; 20 import io.netty.handler.ssl.SslContext;
21 import org.thingsboard.mqtt.MqttClientConfig; 21 import org.thingsboard.mqtt.MqttClientConfig;
  22 +import org.thingsboard.rule.engine.mqtt.azure.AzureIotHubSasCredentials;
22 23
23 import java.util.Optional; 24 import java.util.Optional;
24 25
@@ -29,6 +30,7 @@ import java.util.Optional; @@ -29,6 +30,7 @@ import java.util.Optional;
29 @JsonSubTypes({ 30 @JsonSubTypes({
30 @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"), 31 @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"),
31 @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"), 32 @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"),
  33 + @JsonSubTypes.Type(value = AzureIotHubSasCredentials.class, name = "sas"),
32 @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")}) 34 @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")})
33 public interface MqttClientCredentials { 35 public interface MqttClientCredentials {
34 36
@@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.telemetry; @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.telemetry;
17 17
18 import com.google.gson.JsonParser; 18 import com.google.gson.JsonParser;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.util.StringUtils;
20 import org.thingsboard.rule.engine.api.RuleNode; 21 import org.thingsboard.rule.engine.api.RuleNode;
21 import org.thingsboard.rule.engine.api.TbContext; 22 import org.thingsboard.rule.engine.api.TbContext;
22 import org.thingsboard.rule.engine.api.TbNode; 23 import org.thingsboard.rule.engine.api.TbNode;
@@ -51,6 +52,8 @@ public class TbMsgAttributesNode implements TbNode { @@ -51,6 +52,8 @@ public class TbMsgAttributesNode implements TbNode {
51 52
52 private TbMsgAttributesNodeConfiguration config; 53 private TbMsgAttributesNodeConfiguration config;
53 54
  55 + private static final String SCOPE = "scope";
  56 +
54 @Override 57 @Override
55 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 58 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
56 this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class); 59 this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class);
@@ -64,7 +67,12 @@ public class TbMsgAttributesNode implements TbNode { @@ -64,7 +67,12 @@ public class TbMsgAttributesNode implements TbNode {
64 } 67 }
65 String src = msg.getData(); 68 String src = msg.getData();
66 Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)); 69 Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src));
67 - ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg)); 70 + String scope = msg.getMetaData().getValue(SCOPE);
  71 + if (StringUtils.isEmpty(scope)) {
  72 + scope = config.getScope();
  73 + msg.getMetaData().putValue("scope", scope);
  74 + }
  75 + ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), scope, new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
68 } 76 }
69 77
70 @Override 78 @Override
1 -!function(e){function t(a){if(n[a])return n[a].exports;var i=n[a]={exports:{},id:a,loaded:!1};return e[a].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,i){a.apply(this,[e,t,i].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(105)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=checkPointConfigForm layout=column> <tb-queue-type-list the-form=checkPointConfigForm tb-required=true ng-disabled=false queue-type=serviceType ng-model=configuration.queueName> </tb-queue-type-list> <div class=tb-hint translate>tb.rulenode.select-queue-hint</div> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.use-metadata-interval-patterns' | translate }}\" ng-model=configuration.useMessageAlarmData>{{ 'tb.rulenode.use-message-alarm-data' | translate }} </md-checkbox> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <section layout=column ng-if=!configuration.useMessageAlarmData> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> <div ng-if=configuration.propagate> <label translate class=\"tb-title no-padding\">tb.rulenode.relation-types-list</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.relationTypes placeholder=\"{{'tb.rulenode.relation-types-list' | translate}}\" md-separator-keys=separatorKeys> </md-chips> </div> <div class=tb-hint style=padding-top:2px ng-if=configuration.propagate translate>tb.rulenode.relation-types-list-hint</div> </section> </section> "},function(e,t){e.exports=" <section ng-form name=createRelationConfigForm layout=column style=min-width:650px> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=createRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=createRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if=\"configuration.entityType == 'DEVICE' || configuration.entityType == 'ASSET'\" style=margin-top:38px> <label translate>tb.rulenode.entity-type-pattern</label> <input ng-required=true name=entityTypePattern ng-model=configuration.entityTypePattern> <div ng-messages=createRelationConfigForm.entityTypePattern.$error> <div ng-message=required translate>tb.rulenode.entity-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex style=margin-top:0> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-checkbox flex ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" aria-label=\"{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}\" ng-model=configuration.createEntityIfNotExists>{{ 'tb.rulenode.create-entity-if-not-exists' | translate }} </md-checkbox> <div class=tb-hint ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" translate>tb.rulenode.create-entity-if-not-exists-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.remove-current-relations' | translate }}\" ng-model=configuration.removeCurrentRelations>{{ 'tb.rulenode.remove-current-relations' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.remove-current-relations-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}\" ng-model=configuration.changeOriginatorToRelatedEntity>{{ 'tb.rulenode.change-originator-to-related-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.change-originator-to-related-entity-hint</div> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=createRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=deleteRelationConfigForm layout=column> <md-checkbox aria-label=\"{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}\" ng-model=configuration.deleteForSingleEntity> {{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.delete-relation-hint</div> <md-input-container class=md-block style=min-width:100px;margin-bottom:38px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.deleteForSingleEntity> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=deleteRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=deleteRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=deleteRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=geoActionConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoActionConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoActionConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=-90 max=90 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoActionConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=-180 max=180 step=0.1 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoActionConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoActionConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoActionConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minInsideDuration ng-model=configuration.minInsideDuration> <div ng-messages=geoActionConfigForm.minInsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-inside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration-time-unit</label> <md-select required name=minInsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-inside-duration-time-unit\' | translate }}" ng-model=configuration.minInsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minOutsideDuration ng-model=configuration.minOutsideDuration> <div ng-messages=geoActionConfigForm.minOutsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-outside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration-time-unit</label> <md-select required name=minOutsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-outside-duration-time-unit\' | translate }}" ng-model=configuration.minOutsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </section> '},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-checkbox flex style=margin-top:18px aria-label="{{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}" ng-model=configuration.addMetadataKeyValuesAsKafkaHeaders>{{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.add-metadata-key-values-as-kafka-headers-hint</div> <md-input-container flex class=md-block flex ng-if="configuration.addMetadataKeyValuesAsKafkaHeaders == true"> <label translate class=tb-title>tb.rulenode.charset-encoding</label> <md-select required name=addMetadataKeyValuesAsKafkaHeaders aria-label="{{ \'tb.rulenode.charset-encoding\' | translate }}" ng-model=configuration.kafkaHeadersCharset> <md-option ng-repeat="charset in ruleNodeTypes.toBytesStandartCharsetTypes" ng-value=charset.value> {{charset.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>';  
2 -},function(e,t){e.exports=" <section ng-form name=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=msgDelayConfigForm layout=column> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}" ng-model=configuration.useMetadataPeriodInSecondsPatterns>{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-period-in-seconds-patterns-hint</div> <md-input-container class=md-block ng-if="configuration.useMetadataPeriodInSecondsPatterns === undefined || configuration.useMetadataPeriodInSecondsPatterns == false"> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataPeriodInSecondsPatterns === true"> <label translate>tb.rulenode.period-in-seconds-pattern</label> <input ng-required=true name=periodInSecondsPattern ng-model=configuration.periodInSecondsPattern> <div ng-messages=msgDelayConfigForm.periodInSecondsPattern.$error> <div ng-message=required translate>tb.rulenode.period-in-seconds-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.period-in-seconds-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=pubsubConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.gcp-project-id</label> <input ng-required=true name=projectId ng-model=configuration.projectId> <div ng-messages=pubsubConfigForm.projectId.$error> <div ng-message=required translate>tb.rulenode.gcp-project-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.pubsub-topic-name</label> <input ng-required=true name=topicName ng-model=configuration.topicName> <div ng-messages=pubsubConfigForm.topicName.$error> <div ng-message=required translate>tb.rulenode.pubsub-topic-name-required</div> </div> </md-input-container> <div class=tb-container ng-class=\"configuration.serviceAccountKeyFileName ? 'ng-valid' : 'ng-invalid'\"> <label class=tb-label translate>tb.rulenode.gcp-service-account-key</label> <div flow-init={singleFile:true} flow-file-added=serviceAccountFileAdded($file) class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click=clearServiceAccountFile() class=\"tb-file-clear-btn md-icon-button md-primary\" aria-label=\"{{ 'action.remove' | translate }}\"> <md-tooltip md-direction=top> {{ 'action.remove' | translate }} </md-tooltip> <md-icon aria-label=\"{{ 'action.remove' | translate }}\" class=material-icons>close</md-icon> </md-button> </div> <div class=\"alert tb-flow-drop\" flow-drop> <label for=serviceAccountKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=serviceAccountKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.serviceAccountKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.serviceAccountKeyFileName>{{configuration.serviceAccountKeyFileName}}</div> </div> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text=\"'tb.rulenode.name'\" key-required-text=\"'tb.rulenode.name-required'\" val-text=\"'tb.rulenode.value'\" val-required-text=\"'tb.rulenode.value-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-proxy\' | translate }}" ng-model=configuration.enableProxy> {{ \'tb.rulenode.enable-proxy\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" ng-if=!configuration.enableProxy aria-label="{{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}" ng-model=configuration.useSimpleClientHttpFactory> {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }} </md-checkbox> <div ng-if=configuration.enableProxy> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-proxy-properties\' | translate }}" ng-model=configuration.useSystemProxyProperties> {{ \'tb.rulenode.use-system-proxy-properties\' | translate }} </md-checkbox> <div ng-if=!configuration.useSystemProxyProperties> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=10> <label translate>tb.rulenode.proxy-scheme</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.proxyScheme> <md-option ng-repeat="proxyScheme in proxySchemes" value={{proxyScheme}}> {{proxyScheme}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=50> <label translate>tb.rulenode.proxy-host</label> <input ng-required=true name=proxyHost ng-model=configuration.proxyHost> <div ng-messages=restApiCallConfigForm.proxyHost.$error> <div translate ng-message=required>tb.rulenode.proxy-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.proxy-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=proxyPort ng-model=configuration.proxyPort> <div ng-messages=restApiCallConfigForm.proxyPort.$error> <div translate ng-message=required>tb.rulenode.proxy-port-required</div> <div translate ng-message=min>tb.rulenode.proxy-port-range</div> <div translate ng-message=max>tb.rulenode.proxy-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.proxy-user</label> <input name=proxyUser ng-model=configuration.proxyUser> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.proxy-password</label> <input name=proxyPassword ng-model=configuration.proxyPassword> </md-input-container> </div> </div> <md-input-container flex ng-if="!configuration.useSimpleClientHttpFactory || configuration.enableProxy"> <label translate>tb.rulenode.read-timeout</label> <input type=number step=1 ng-model=configuration.readTimeoutMs min=0> <div class=tb-hint translate>tb.rulenode.read-timeout-hint</div> </md-input-container> <md-input-container> <label translate>tb.rulenode.max-parallel-requests-count</label> <input type=number step=1 ng-model=configuration.maxParallelRequestsCount min=0> <div class=tb-hint translate>tb.rulenode.max-parallel-requests-count-hint</div> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-checkbox style=line-height:50px ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-redis-queue\' | translate }}" ng-model=configuration.useRedisQueueForMsgPersistence> {{ \'tb.rulenode.use-redis-queue\' | translate }} </md-checkbox> <div layout=column ng-if=configuration.useRedisQueueForMsgPersistence> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.trim-redis-queue\' | translate }}" ng-model=configuration.trimQueue> {{ \'tb.rulenode.trim-redis-queue\' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.redis-queue-max-size</label> <input type=number step=1 name=redisQueueMaxSize ng-model=configuration.maxQueueSize min=0> </md-input-container> </div> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.custom-table-name</label> <input ng-required=true name=tableName ng-model=configuration.tableName> <div ng-messages=saveToCustomTableConfigForm.tableName.$error> <div ng-message=required translate>tb.rulenode.custom-table-name-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.custom-table-hint</div> </md-input-container> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.message-field\'" key-required-text="\'tb.rulenode.message-field-required\'" val-text="\'tb.rulenode.table-col\'" val-required-text="\'tb.rulenode.table-col-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block ng-if=configuration.enableTls> <label translate>tb.rulenode.tls-version</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.tlsVersion> <md-option ng-repeat="tlsVersion in tlsVersions" value={{tlsVersion}}> {{tlsVersion}} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-proxy\' | translate }}" ng-model=configuration.enableProxy>{{ \'tb.rulenode.enable-proxy\' | translate }}</md-checkbox> <div layout-gt-sm=row ng-if=configuration.enableProxy> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.proxy-host</label> <input ng-required=true name=proxyHost ng-model=configuration.proxyHost> <div ng-messages=sendEmailConfigForm.proxyHost.$error> <div translate ng-message=required>tb.rulenode.proxy-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.proxy-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=proxyPort ng-model=configuration.proxyPort> <div ng-messages=sendEmailConfigForm.proxyPort.$error> <div translate ng-message=required>tb.rulenode.proxy-port-required</div> <div translate ng-message=min>tb.rulenode.proxy-port-range</div> <div translate ng-message=max>tb.rulenode.proxy-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block ng-if=configuration.enableProxy> <label translate>tb.rulenode.proxy-user</label> <input name=proxyUser ng-model=configuration.proxyUser> </md-input-container> <md-input-container class=md-block ng-if=configuration.enableProxy> <label translate>tb.rulenode.proxy-password</label> <input name=proxyPassword ng-model=configuration.proxyPassword> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <md-checkbox ng-model=query.fetchLastLevelOnly aria-label=\"{{ 'alias.last-level-relation' | translate}}\"> {{ 'alias.last-level-relation' | translate}} </md-checkbox> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class=\"md-caption tb-required\" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <md-checkbox aria-label="{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}" ng-model=configuration.tellFailureIfAbsent> {{ \'tb.rulenode.tell-failure-if-absent\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.tell-failure-if-absent-hint</div> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div style=margin-top:20px> <md-checkbox aria-label="{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}" ng-model=configuration.getLatestValueWithTs> {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.get-latest-value-with-ts-hint</div> </div> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.entity-details</label> <md-chips readonly=disabled style=margin-bottom:28px id=entityDetailsListChips ng-required=tbRequired ng-model=configuration.detailsList placeholder={{placeholder}} secondary-placeholder={{secondaryPlaceholder}} md-autocomplete-snap md-require-match=true> <md-autocomplete md-no-cache=true id=entityDetails md-selected-item=selectedEntityDetail md-selected-item-change=selectedItemChange(item) md-search-text=entityDetailsSearchText md-items="item in entityDetailsList" md-item-text=item md-min-length=0 placeholder="{{ (!ruleNodeTypes.entityDetails || !ruleNodeTypes.entityDetails.length) ? placeholder : secondaryPlaceholder }}"> <md-item-template> <span md-highlight-text=entityDetailsSearchText md-highlight-flags=^i> {{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} </span> </md-item-template> <md-not-found> <span translate translate-values="{ entityDetails: entityDetailsSearchText }">tb.rulenode.no-entity-details-matching</span> </md-not-found> </md-autocomplete> <md-chip-template> <span> <strong>{{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}}</strong> </span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error ng-if="inputTouched && tbRequired" role=alert> <div translate ng-message=configuration.detailsList class=tb-error-message>tb.rulenode.entity-details-list-empty</div> </div> <md-checkbox aria-label="{{ \'tb.rulenode.add-to-metadata\' | translate }}" ng-model=configuration.addToMetadata> {{ \'tb.rulenode.add-to-metadata\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.add-to-metadata-hint</div> </section> ';  
3 -},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:18px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate>tb.rulenode.fetch-mode-hint</div> <md-input-container flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.order-by</label> <md-select required ng-model=configuration.orderBy> <md-option ng-repeat="type in ruleNodeTypes.samplingOrder" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.order-by-hint</div> <md-input-container style=margin-bottom:0 flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.limit</label> <input required type=number step=1 ng-model=configuration.limit min=2 max=1000> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.limit-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}" ng-model=configuration.useMetadataIntervalPatterns>{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-interval-patterns-hint</div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns == false "> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns === false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true" style=margin-top:38px> <label translate>tb.rulenode.start-interval-pattern</label> <input ng-required=true name=startIntervalPattern ng-model=configuration.startIntervalPattern> <div ng-messages=getTelemetryConfigForm.startIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.start-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.start-interval-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true"> <label translate>tb.rulenode.end-interval-pattern</label> <input ng-required=true name=endIntervalPattern ng-model=configuration.endIntervalPattern> <div ng-messages=getTelemetryConfigForm.endIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.end-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.end-interval-pattern-hint</div> </md-input-container> </section>'},function(e,t){e.exports=' <section layout=column> <md-checkbox aria-label="{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}" ng-model=configuration.tellFailureIfAbsent> {{ \'tb.rulenode.tell-failure-if-absent\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.tell-failure-if-absent-hint</div> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div style=margin-top:20px> <md-checkbox aria-label="{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}" ng-model=configuration.getLatestValueWithTs> {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.get-latest-value-with-ts-hint</div> </div> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},32,function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\" class=\"{'tb-required': required}\">tb.rulenode.alarm-statuses-filter</label> <md-chips readonly=readonly id=alarmStatusListChips ng-required=required ng-model=configuration.alarmStatusList md-autocomplete-snap md-require-match=true> <md-autocomplete md-no-cache=true id=alarmStatus md-selected-item=selectedAlarmStatus md-selected-item-change=selectedItemChange(item) md-search-text=alarmStatusSearchText md-items=\"item in getAlarmStatusList()\" md-item-text=item md-min-length=0 placeholder=\"{{'alarm.alarm-status' | translate }}\"> <span>{{'alarm.display-status.' + item | translate}}</span> </md-autocomplete> <md-chip-template> <span>{{'alarm.display-status.' + $chip | translate}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=configuration.alarmStatusList class=tb-error-message> tb.rulenode.alarm-statuses-required </div> </div> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding tb-required">tb.rulenode.data-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.metadataNames).length readonly=readonly ng-model=configuration.messageNames placeholder="{{\'tb.rulenode.data-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <label translate class="tb-title no-padding tb-required">tb.rulenode.metadata-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.messageNames).length readonly=readonly ng-model=configuration.metadataNames placeholder="{{\'tb.rulenode.metadata-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.check-all-keys\' | translate }}" ng-model=configuration.checkAllKeys>{{ \'tb.rulenode.check-all-keys\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-all-keys-hint</div> </section> '},function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-checkbox aria-label=\"{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}\" ng-model=configuration.checkForSingleEntity> {{ 'tb.rulenode.check-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-relation-hint</div> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.checkForSingleEntity style=padding-top:20px> <tb-entity-type-select style=min-width:100px;padding-bottom:20px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section ng-form name=geoFilterConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoFilterConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoFilterConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=-90 max=90 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoFilterConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=-180 max=180 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoFilterConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoFilterConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoFilterConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> </section> '},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:&apos;...&apos;}}" }\'>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" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </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', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </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', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <md-checkbox ng-model=query.fetchLastLevelOnly aria-label=\"{{ 'alias.last-level-relation' | translate}}\"> {{ 'alias.last-level-relation' | translate}} </md-checkbox> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(6),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(7),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.serviceType="TB_RULE_ENGINE",n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(8),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(9),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.hasOwnProperty("relationTypes")||(i.configuration.relationTypes=[])},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(10),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(11),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(12),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.originator=null,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.originatorId&&i.configuration.originatorType?i.originator={id:i.configuration.originatorId,entityType:i.configuration.originatorType}:i.originator=null,i.$watch("originator",function(e,t){angular.equals(e,t)||(i.originator?(s.$viewValue.originatorId=i.originator.id,s.$viewValue.originatorType=i.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},i.testScript=function(e){var n=angular.copy(i.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(1);var r=n(13),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(14),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(77),r=a(i),o=n(54),l=a(o),s=n(60),d=a(s),u=n(57),c=a(u),m=n(56),g=a(m),p=n(64),f=a(p),b=n(71),v=a(b),y=n(72),h=a(y),q=n(70),x=a(q),k=n(63),$=a(k),T=n(75),C=a(T),w=n(76),M=a(w),S=n(69),N=a(S),P=n(65),_=a(P),F=n(74),E=a(F),A=n(67),V=a(A),I=n(66),j=a(I),O=n(53),D=a(O),L=n(78),R=a(L),K=n(59),U=a(K),z=n(58),H=a(z),B=n(73),G=a(B),Y=n(61),Q=a(Y),W=n(68),J=a(W),Z=n(55),X=a(Z);  
4 -t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",N.default).directive("tbActionNodeMqttConfig",_.default).directive("tbActionNodeSendEmailConfig",E.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",R.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",H.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",J.default).directive("tbActionNodeCheckPointConfig",X.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ackValues=["all","-1","0","1"],n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue,n.configuration.hasOwnProperty("kafkaHeadersCharset")||(n.configuration.kafkaHeadersCharset="UTF-8")},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(15),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(16),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var i=n.target.result;i&&i.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=i),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=i),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=i)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(2);var r=n(17),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(18),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(19),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var a=t.target.result;a&&a.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=a),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(20),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(21),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.proxySchemes=["http","https"],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(22),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(23),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(24),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(25),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(26),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(27),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(28),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(29),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(30),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(31),o=a(r)},function(e,t){"use strict";function n(e){var t=function(t,n,a,i){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(32),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(33),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(34),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s);var d=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}i.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(35),o=a(r);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(86),r=a(i),o=n(87),l=a(o),s=n(82),d=a(s),u=n(88),c=a(u),m=n(81),g=a(m),p=n(89),f=a(p),b=n(84),v=a(b),y=n(83),h=a(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(36),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(37),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(38),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(39),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.alarmStatusList=[];for(var s in t.alarmStatus)n.alarmStatusList.push(t.alarmStatus[s]);r.$render=function(){n.configuration=r.$viewValue},n.getAlarmStatusList=function(){return n.alarmStatusList.filter(function(e){return n.configuration.alarmStatusList.indexOf(e)===-1})},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(40),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(41),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(42),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(43),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(97),r=a(i),o=n(95),l=a(o),s=n(98),d=a(s),u=n(92),c=a(u),m=n(96),g=a(m),p=n(91),f=a(p),b=n(93),v=a(b),y=n(90),h=a(y);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).directive("tbFilterNodeCheckAlarmStatusConfig",h.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,d()}}function d(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var u=o.default;i.html(u),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,i)[0].firstElementChild,a=angular.element(n),r=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(r.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var i=0;i<e.messageTypes.length;i++){var r=e.messageTypes[i];n.messageType[r]?t.push(angular.copy(n.messageType[r])):t.push({name:r,value:r})}a.messageTypes=t,a.messageTypesWatch=a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(4);var r=n(44),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.entityView,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(45),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(46),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(47),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){function r(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),i.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),i.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=i,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||i.$setViewValue(t.query)}),i.$render=function(){if(i.$viewValue){var e=i.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(48),o=a(r);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(49),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(50),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(101),r=a(i),o=n(103),l=a(o),s=n(104),d=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(51),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(52),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(108),r=a(i),o=n(94),l=a(o),s=n(85),d=a(s),u=n(102),c=a(u),m=n(62),g=a(m),p=n(80),f=a(p),b=n(100),v=a(b),y=n(79),h=a(y),q=n(99),x=a(q),k=n(107),$=a(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config($.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type",  
5 -"message-type-required":"Message type is required.","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.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-statuses-filter":"Alarm statuses filter","alarm-statuses-required":"Alarm statuses is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use <code>${metaKeyName}</code> to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use <code>${metaKeyName}</code> to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"You must supply a proxy port.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","tls-version":"TLS version","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){(0,o.default)(e)}i.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(106),o=a(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}},toBytesStandartCharsetTypes:{"US-ASCII":{value:"US-ASCII",name:"tb.rulenode.charset-us-ascii"},"ISO-8859-1":{value:"ISO-8859-1",name:"tb.rulenode.charset-iso-8859-1"},"UTF-8":{value:"UTF-8",name:"tb.rulenode.charset-utf-8"},"UTF-16BE":{value:"UTF-16BE",name:"tb.rulenode.charset-utf-16be"},"UTF-16LE":{value:"UTF-16LE",name:"tb.rulenode.charset-utf-16le"},"UTF-16":{value:"UTF-16",name:"tb.rulenode.charset-utf-16"}}}).name}])); 1 +!function(e){function t(a){if(n[a])return n[a].exports;var i=n[a]={exports:{},id:a,loaded:!1};return e[a].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,i){a.apply(this,[e,t,i].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(107)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=azureIotHubConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=azureIotHubConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.hostname</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=azureIotHubConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.hostname-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.device-id</label> <input required name=clientId ng-model=configuration.clientId> <div ng-messages=azureIotHubConfigForm.clientId.$error> <div translate ng-message=required>tb.rulenode.device-id-required</div> </div> </md-input-container> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.azureIotHubCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.azureIotHubCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.azureIotHubCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=azureIotHubConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.azureIotHubCredentialTypes.sas.value"> <md-input-container class=md-block> <label translate>tb.rulenode.sas-key</label> <input required type=password name=sasKey ng-model=configuration.credentials.sasKey> <div ng-messages=azureIotHubConfigForm.sasKey.$error> <div translate ng-message=required>tb.rulenode.sas-key-required</div> </div> </md-input-container> <div class=tb-container ng-class="\'ng-valid\'"> <label class=tb-label translate>tb.rulenode.azure-ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.azureIotHubCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.azure-ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=checkPointConfigForm layout=column> <tb-queue-type-list the-form=checkPointConfigForm tb-required=true ng-disabled=false queue-type=serviceType ng-model=configuration.queueName> </tb-queue-type-list> <div class=tb-hint translate>tb.rulenode.select-queue-hint</div> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.use-metadata-interval-patterns' | translate }}\" ng-model=configuration.useMessageAlarmData>{{ 'tb.rulenode.use-message-alarm-data' | translate }} </md-checkbox> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <section layout=column ng-if=!configuration.useMessageAlarmData> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> <div ng-if=configuration.propagate> <label translate class=\"tb-title no-padding\">tb.rulenode.relation-types-list</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.relationTypes placeholder=\"{{'tb.rulenode.relation-types-list' | translate}}\" md-separator-keys=separatorKeys> </md-chips> </div> <div class=tb-hint style=padding-top:2px ng-if=configuration.propagate translate>tb.rulenode.relation-types-list-hint</div> </section> </section> "},function(e,t){e.exports=" <section ng-form name=createRelationConfigForm layout=column style=min-width:650px> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=createRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=createRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if=\"configuration.entityType == 'DEVICE' || configuration.entityType == 'ASSET'\" style=margin-top:38px> <label translate>tb.rulenode.entity-type-pattern</label> <input ng-required=true name=entityTypePattern ng-model=configuration.entityTypePattern> <div ng-messages=createRelationConfigForm.entityTypePattern.$error> <div ng-message=required translate>tb.rulenode.entity-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex style=margin-top:0> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-checkbox flex ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" aria-label=\"{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}\" ng-model=configuration.createEntityIfNotExists>{{ 'tb.rulenode.create-entity-if-not-exists' | translate }} </md-checkbox> <div class=tb-hint ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" translate>tb.rulenode.create-entity-if-not-exists-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.remove-current-relations' | translate }}\" ng-model=configuration.removeCurrentRelations>{{ 'tb.rulenode.remove-current-relations' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.remove-current-relations-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}\" ng-model=configuration.changeOriginatorToRelatedEntity>{{ 'tb.rulenode.change-originator-to-related-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.change-originator-to-related-entity-hint</div> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=createRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=deleteRelationConfigForm layout=column> <md-checkbox aria-label=\"{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}\" ng-model=configuration.deleteForSingleEntity> {{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.delete-relation-hint</div> <md-input-container class=md-block style=min-width:100px;margin-bottom:38px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.deleteForSingleEntity> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=deleteRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=deleteRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=deleteRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=geoActionConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoActionConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoActionConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=-90 max=90 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoActionConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=-180 max=180 step=0.1 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoActionConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoActionConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoActionConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minInsideDuration ng-model=configuration.minInsideDuration> <div ng-messages=geoActionConfigForm.minInsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-inside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration-time-unit</label> <md-select required name=minInsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-inside-duration-time-unit\' | translate }}" ng-model=configuration.minInsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minOutsideDuration ng-model=configuration.minOutsideDuration> <div ng-messages=geoActionConfigForm.minOutsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-outside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration-time-unit</label> <md-select required name=minOutsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-outside-duration-time-unit\' | translate }}" ng-model=configuration.minOutsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </section> '},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-checkbox flex style=margin-top:18px aria-label="{{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}" ng-model=configuration.addMetadataKeyValuesAsKafkaHeaders>{{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.add-metadata-key-values-as-kafka-headers-hint</div> <md-input-container flex class=md-block flex ng-if="configuration.addMetadataKeyValuesAsKafkaHeaders == true"> <label translate class=tb-title>tb.rulenode.charset-encoding</label> <md-select required name=addMetadataKeyValuesAsKafkaHeaders aria-label="{{ \'tb.rulenode.charset-encoding\' | translate }}" ng-model=configuration.kafkaHeadersCharset> <md-option ng-repeat="charset in ruleNodeTypes.toBytesStandartCharsetTypes" ng-value=charset.value> {{charset.name | translate}} </md-option> </md-select> </md-input-container> </section> ';
  2 +},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=msgDelayConfigForm layout=column> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}" ng-model=configuration.useMetadataPeriodInSecondsPatterns>{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-period-in-seconds-patterns-hint</div> <md-input-container class=md-block ng-if="configuration.useMetadataPeriodInSecondsPatterns === undefined || configuration.useMetadataPeriodInSecondsPatterns == false"> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataPeriodInSecondsPatterns === true"> <label translate>tb.rulenode.period-in-seconds-pattern</label> <input ng-required=true name=periodInSecondsPattern ng-model=configuration.periodInSecondsPattern> <div ng-messages=msgDelayConfigForm.periodInSecondsPattern.$error> <div ng-message=required translate>tb.rulenode.period-in-seconds-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.period-in-seconds-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=pubsubConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.gcp-project-id</label> <input ng-required=true name=projectId ng-model=configuration.projectId> <div ng-messages=pubsubConfigForm.projectId.$error> <div ng-message=required translate>tb.rulenode.gcp-project-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.pubsub-topic-name</label> <input ng-required=true name=topicName ng-model=configuration.topicName> <div ng-messages=pubsubConfigForm.topicName.$error> <div ng-message=required translate>tb.rulenode.pubsub-topic-name-required</div> </div> </md-input-container> <div class=tb-container ng-class=\"configuration.serviceAccountKeyFileName ? 'ng-valid' : 'ng-invalid'\"> <label class=tb-label translate>tb.rulenode.gcp-service-account-key</label> <div flow-init={singleFile:true} flow-file-added=serviceAccountFileAdded($file) class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click=clearServiceAccountFile() class=\"tb-file-clear-btn md-icon-button md-primary\" aria-label=\"{{ 'action.remove' | translate }}\"> <md-tooltip md-direction=top> {{ 'action.remove' | translate }} </md-tooltip> <md-icon aria-label=\"{{ 'action.remove' | translate }}\" class=material-icons>close</md-icon> </md-button> </div> <div class=\"alert tb-flow-drop\" flow-drop> <label for=serviceAccountKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=serviceAccountKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.serviceAccountKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.serviceAccountKeyFileName>{{configuration.serviceAccountKeyFileName}}</div> </div> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text=\"'tb.rulenode.name'\" key-required-text=\"'tb.rulenode.name-required'\" val-text=\"'tb.rulenode.value'\" val-required-text=\"'tb.rulenode.value-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input required name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-proxy\' | translate }}" ng-model=configuration.enableProxy> {{ \'tb.rulenode.enable-proxy\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" ng-if=!configuration.enableProxy aria-label="{{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}" ng-model=configuration.useSimpleClientHttpFactory> {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }} </md-checkbox> <div ng-if=configuration.enableProxy> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-proxy-properties\' | translate }}" ng-model=configuration.useSystemProxyProperties> {{ \'tb.rulenode.use-system-proxy-properties\' | translate }} </md-checkbox> <div ng-if=!configuration.useSystemProxyProperties> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=10> <label translate>tb.rulenode.proxy-scheme</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.proxyScheme> <md-option ng-repeat="proxyScheme in proxySchemes" value={{proxyScheme}}> {{proxyScheme}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=50> <label translate>tb.rulenode.proxy-host</label> <input required name=proxyHost ng-model=configuration.proxyHost> <div ng-messages=restApiCallConfigForm.proxyHost.$error> <div translate ng-message=required>tb.rulenode.proxy-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.proxy-port</label> <input type=number step=1 min=1 max=65535 required name=proxyPort ng-model=configuration.proxyPort> <div ng-messages=restApiCallConfigForm.proxyPort.$error> <div translate ng-message=required>tb.rulenode.proxy-port-required</div> <div translate ng-message=min>tb.rulenode.proxy-port-range</div> <div translate ng-message=max>tb.rulenode.proxy-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.proxy-user</label> <input name=proxyUser ng-model=configuration.proxyUser> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.proxy-password</label> <input name=proxyPassword ng-model=configuration.proxyPassword> </md-input-container> </div> </div> <md-input-container flex ng-if="!configuration.useSimpleClientHttpFactory || configuration.enableProxy"> <label translate>tb.rulenode.read-timeout</label> <input type=number step=1 ng-model=configuration.readTimeoutMs min=0> <div class=tb-hint translate>tb.rulenode.read-timeout-hint</div> </md-input-container> <md-input-container> <label translate>tb.rulenode.max-parallel-requests-count</label> <input type=number step=1 ng-model=configuration.maxParallelRequestsCount min=0> <div class=tb-hint translate>tb.rulenode.max-parallel-requests-count-hint</div> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-checkbox style=line-height:50px ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-redis-queue\' | translate }}" ng-model=configuration.useRedisQueueForMsgPersistence> {{ \'tb.rulenode.use-redis-queue\' | translate }} </md-checkbox> <div layout=column ng-if=configuration.useRedisQueueForMsgPersistence> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.trim-redis-queue\' | translate }}" ng-model=configuration.trimQueue> {{ \'tb.rulenode.trim-redis-queue\' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.redis-queue-max-size</label> <input type=number step=1 name=redisQueueMaxSize ng-model=configuration.maxQueueSize min=0> </md-input-container> </div> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.custom-table-name</label> <input ng-required=true name=tableName ng-model=configuration.tableName> <div ng-messages=saveToCustomTableConfigForm.tableName.$error> <div ng-message=required translate>tb.rulenode.custom-table-name-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.custom-table-hint</div> </md-input-container> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.message-field\'" key-required-text="\'tb.rulenode.message-field-required\'" val-text="\'tb.rulenode.table-col\'" val-required-text="\'tb.rulenode.table-col-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block ng-if=configuration.enableTls> <label translate>tb.rulenode.tls-version</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.tlsVersion> <md-option ng-repeat="tlsVersion in tlsVersions" value={{tlsVersion}}> {{tlsVersion}} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-proxy\' | translate }}" ng-model=configuration.enableProxy>{{ \'tb.rulenode.enable-proxy\' | translate }}</md-checkbox> <div layout-gt-sm=row ng-if=configuration.enableProxy> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.proxy-host</label> <input ng-required=true name=proxyHost ng-model=configuration.proxyHost> <div ng-messages=sendEmailConfigForm.proxyHost.$error> <div translate ng-message=required>tb.rulenode.proxy-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.proxy-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=proxyPort ng-model=configuration.proxyPort> <div ng-messages=sendEmailConfigForm.proxyPort.$error> <div translate ng-message=required>tb.rulenode.proxy-port-required</div> <div translate ng-message=min>tb.rulenode.proxy-port-range</div> <div translate ng-message=max>tb.rulenode.proxy-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block ng-if=configuration.enableProxy> <label translate>tb.rulenode.proxy-user</label> <input name=proxyUser ng-model=configuration.proxyUser> </md-input-container> <md-input-container class=md-block ng-if=configuration.enableProxy> <label translate>tb.rulenode.proxy-password</label> <input name=proxyPassword ng-model=configuration.proxyPassword> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> ';
  3 +},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <md-checkbox ng-model=query.fetchLastLevelOnly aria-label=\"{{ 'alias.last-level-relation' | translate}}\"> {{ 'alias.last-level-relation' | translate}} </md-checkbox> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class=\"md-caption tb-required\" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <md-checkbox aria-label="{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}" ng-model=configuration.tellFailureIfAbsent> {{ \'tb.rulenode.tell-failure-if-absent\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.tell-failure-if-absent-hint</div> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div style=margin-top:20px> <md-checkbox aria-label="{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}" ng-model=configuration.getLatestValueWithTs> {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.get-latest-value-with-ts-hint</div> </div> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.entity-details</label> <md-chips readonly=disabled style=margin-bottom:28px id=entityDetailsListChips ng-required=tbRequired ng-model=configuration.detailsList placeholder={{placeholder}} secondary-placeholder={{secondaryPlaceholder}} md-autocomplete-snap md-require-match=true> <md-autocomplete md-no-cache=true id=entityDetails md-selected-item=selectedEntityDetail md-selected-item-change=selectedItemChange(item) md-search-text=entityDetailsSearchText md-items="item in entityDetailsList" md-item-text=item md-min-length=0 placeholder="{{ (!ruleNodeTypes.entityDetails || !ruleNodeTypes.entityDetails.length) ? placeholder : secondaryPlaceholder }}"> <md-item-template> <span md-highlight-text=entityDetailsSearchText md-highlight-flags=^i> {{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} </span> </md-item-template> <md-not-found> <span translate translate-values="{ entityDetails: entityDetailsSearchText }">tb.rulenode.no-entity-details-matching</span> </md-not-found> </md-autocomplete> <md-chip-template> <span> <strong>{{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}}</strong> </span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error ng-if="inputTouched && tbRequired" role=alert> <div translate ng-message=configuration.detailsList class=tb-error-message>tb.rulenode.entity-details-list-empty</div> </div> <md-checkbox aria-label="{{ \'tb.rulenode.add-to-metadata\' | translate }}" ng-model=configuration.addToMetadata> {{ \'tb.rulenode.add-to-metadata\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.add-to-metadata-hint</div> </section> '},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:18px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate>tb.rulenode.fetch-mode-hint</div> <md-input-container flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.order-by</label> <md-select required ng-model=configuration.orderBy> <md-option ng-repeat="type in ruleNodeTypes.samplingOrder" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.order-by-hint</div> <md-input-container style=margin-bottom:0 flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.limit</label> <input required type=number step=1 ng-model=configuration.limit min=2 max=1000> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.limit-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}" ng-model=configuration.useMetadataIntervalPatterns>{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-interval-patterns-hint</div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns == false "> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns === false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true" style=margin-top:38px> <label translate>tb.rulenode.start-interval-pattern</label> <input ng-required=true name=startIntervalPattern ng-model=configuration.startIntervalPattern> <div ng-messages=getTelemetryConfigForm.startIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.start-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.start-interval-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true"> <label translate>tb.rulenode.end-interval-pattern</label> <input ng-required=true name=endIntervalPattern ng-model=configuration.endIntervalPattern> <div ng-messages=getTelemetryConfigForm.endIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.end-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.end-interval-pattern-hint</div> </md-input-container> </section>'},function(e,t){e.exports=' <section layout=column> <md-checkbox aria-label="{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}" ng-model=configuration.tellFailureIfAbsent> {{ \'tb.rulenode.tell-failure-if-absent\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.tell-failure-if-absent-hint</div> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div style=margin-top:20px> <md-checkbox aria-label="{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}" ng-model=configuration.getLatestValueWithTs> {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.get-latest-value-with-ts-hint</div> </div> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},33,function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\" class=\"{'tb-required': required}\">tb.rulenode.alarm-statuses-filter</label> <md-chips readonly=readonly id=alarmStatusListChips ng-required=required ng-model=configuration.alarmStatusList md-autocomplete-snap md-require-match=true> <md-autocomplete md-no-cache=true id=alarmStatus md-selected-item=selectedAlarmStatus md-selected-item-change=selectedItemChange(item) md-search-text=alarmStatusSearchText md-items=\"item in getAlarmStatusList()\" md-item-text=item md-min-length=0 placeholder=\"{{'alarm.alarm-status' | translate }}\"> <span>{{'alarm.display-status.' + item | translate}}</span> </md-autocomplete> <md-chip-template> <span>{{'alarm.display-status.' + $chip | translate}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=configuration.alarmStatusList class=tb-error-message> tb.rulenode.alarm-statuses-required </div> </div> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding tb-required">tb.rulenode.data-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.metadataNames).length readonly=readonly ng-model=configuration.messageNames placeholder="{{\'tb.rulenode.data-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <label translate class="tb-title no-padding tb-required">tb.rulenode.metadata-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.messageNames).length readonly=readonly ng-model=configuration.metadataNames placeholder="{{\'tb.rulenode.metadata-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.check-all-keys\' | translate }}" ng-model=configuration.checkAllKeys>{{ \'tb.rulenode.check-all-keys\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-all-keys-hint</div> </section> '},function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-checkbox aria-label=\"{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}\" ng-model=configuration.checkForSingleEntity> {{ 'tb.rulenode.check-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-relation-hint</div> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.checkForSingleEntity style=padding-top:20px> <tb-entity-type-select style=min-width:100px;padding-bottom:20px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section ng-form name=geoFilterConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoFilterConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoFilterConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=-90 max=90 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoFilterConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=-180 max=180 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoFilterConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoFilterConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoFilterConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> </section> '},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:&apos;...&apos;}}" }\'>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" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </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', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </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', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <md-checkbox ng-model=query.fetchLastLevelOnly aria-label=\"{{ 'alias.last-level-relation' | translate}}\"> {{ 'alias.last-level-relation' | translate}} </md-checkbox> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> ";
  4 +},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(6),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(7),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var i=n.target.result;i&&i.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=i),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=i),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=i)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){switch(l.$setDirty(),e){case"caCert":a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null;break;case"privateKey":a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null;break;case"Cert":a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null}a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(1);var r=n(8),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.serviceType="TB_RULE_ENGINE",n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(9),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(10),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.hasOwnProperty("relationTypes")||(i.configuration.relationTypes=[])},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(11),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(12),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(13),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.originator=null,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.originatorId&&i.configuration.originatorType?i.originator={id:i.configuration.originatorId,entityType:i.configuration.originatorType}:i.originator=null,i.$watch("originator",function(e,t){angular.equals(e,t)||(i.originator?(s.$viewValue.originatorId=i.originator.id,s.$viewValue.originatorType=i.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},i.testScript=function(e){var n=angular.copy(i.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(2);var r=n(14),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(15),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(79),r=a(i),o=n(55),l=a(o),s=n(62),d=a(s),u=n(59),c=a(u),m=n(58),g=a(m),p=n(66),f=a(p),b=n(73),v=a(b),y=n(74),h=a(y),q=n(72),x=a(q),k=n(65),$=a(k),T=n(77),C=a(T),w=n(78),M=a(w),N=n(71),S=a(N),F=n(67),P=a(F),_=n(76),E=a(_),A=n(69),V=a(A),I=n(68),j=a(I),O=n(54),K=a(O),D=n(80),L=a(D),R=n(61),U=a(R),z=n(60),H=a(z),B=n(75),G=a(B),Y=n(63),Q=a(Y),W=n(70),J=a(W),Z=n(57),X=a(Z),ee=n(56),te=a(ee);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",P.default).directive("tbActionNodeSendEmailConfig",E.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",K.default).directive("tbActionNodeUnAssignToCustomerConfig",L.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",H.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",J.default).directive("tbActionNodeCheckPointConfig",X.default).directive("tbActionNodeAzureIotHubConfig",te.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ackValues=["all","-1","0","1"],n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue,n.configuration.hasOwnProperty("kafkaHeadersCharset")||(n.configuration.kafkaHeadersCharset="UTF-8")},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(16),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(17),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var i=n.target.result;i&&i.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=i),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=i),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=i)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(1);var r=n(18),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(19),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(20),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var a=t.target.result;a&&a.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=a),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(21),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(22),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.proxySchemes=["http","https"],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(23),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(24),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(25),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(26),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(27),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(28),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(29),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(30),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(31),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(32),o=a(r)},function(e,t){"use strict";function n(e){var t=function(t,n,a,i){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(33),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(34),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(35),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s);var d=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}i.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(36),o=a(r);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(88),r=a(i),o=n(89),l=a(o),s=n(84),d=a(s),u=n(90),c=a(u),m=n(83),g=a(m),p=n(91),f=a(p),b=n(86),v=a(b),y=n(85),h=a(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(37),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(38),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(39),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(40),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.alarmStatusList=[];for(var s in t.alarmStatus)n.alarmStatusList.push(t.alarmStatus[s]);r.$render=function(){n.configuration=r.$viewValue},n.getAlarmStatusList=function(){return n.alarmStatusList.filter(function(e){return n.configuration.alarmStatusList.indexOf(e)===-1})},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(41),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(42),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(43),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(44),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(99),r=a(i),o=n(97),l=a(o),s=n(100),d=a(s),u=n(94),c=a(u),m=n(98),g=a(m),p=n(93),f=a(p),b=n(95),v=a(b),y=n(92),h=a(y);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).directive("tbFilterNodeCheckAlarmStatusConfig",h.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,d()}}function d(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var u=o.default;i.html(u),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,i)[0].firstElementChild,a=angular.element(n),r=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(r.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var i=0;i<e.messageTypes.length;i++){var r=e.messageTypes[i];n.messageType[r]?t.push(angular.copy(n.messageType[r])):t.push({name:r,value:r})}a.messageTypes=t,a.messageTypesWatch=a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(4);var r=n(45),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.entityView,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(46),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){
  5 +angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(47),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(48),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){function r(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),i.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),i.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=i,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||i.$setViewValue(t.query)}),i.$render=function(){if(i.$viewValue){var e=i.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(49),o=a(r);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(50),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(51),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(103),r=a(i),o=n(105),l=a(o),s=n(106),d=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(52),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(53),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(110),r=a(i),o=n(96),l=a(o),s=n(87),d=a(s),u=n(104),c=a(u),m=n(64),g=a(m),p=n(82),f=a(p),b=n(102),v=a(b),y=n(81),h=a(y),q=n(101),x=a(q),k=n(109),$=a(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config($.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","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.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-statuses-filter":"Alarm statuses filter","alarm-statuses-required":"Alarm statuses is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use <code>${metaKeyName}</code> to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use <code>${metaKeyName}</code> to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"You must supply a proxy port.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","tls-version":"TLS version","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){(0,o.default)(e)}i.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(108),o=a(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}},azureIotHubCredentialTypes:{sas:{value:"sas",name:"tb.rulenode.credentials-sas"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}},toBytesStandartCharsetTypes:{"US-ASCII":{value:"US-ASCII",name:"tb.rulenode.charset-us-ascii"},"ISO-8859-1":{value:"ISO-8859-1",name:"tb.rulenode.charset-iso-8859-1"},"UTF-8":{value:"UTF-8",name:"tb.rulenode.charset-utf-8"},"UTF-16BE":{value:"UTF-16BE",name:"tb.rulenode.charset-utf-16be"},"UTF-16LE":{value:"UTF-16LE",name:"tb.rulenode.charset-utf-16le"},"UTF-16":{value:"UTF-16",name:"tb.rulenode.charset-utf-16"}}}).name}]));
6 //# sourceMappingURL=rulenode-core-config.js.map 6 //# sourceMappingURL=rulenode-core-config.js.map
@@ -14,8 +14,16 @@ @@ -14,8 +14,16 @@
14 # limitations under the License. 14 # limitations under the License.
15 # 15 #
16 16
17 -spring.main.web-environment: false  
18 -spring.main.web-application-type: none 17 +# If you enabled process metrics you should also enable 'web-environment'.
  18 +spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
  19 +# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
  20 +spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
  21 +
  22 +server:
  23 + # Server bind address (has no effect if web-environment is disabled).
  24 + address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
  25 + # Server bind port (has no effect if web-environment is disabled).
  26 + port: "${HTTP_BIND_PORT:8083}"
19 27
20 # Zookeeper connection parameters. Used for service discovery. 28 # Zookeeper connection parameters. Used for service discovery.
21 zk: 29 zk:
@@ -205,10 +213,22 @@ queue: @@ -205,10 +213,22 @@ queue:
205 transport: 213 transport:
206 # For high priority notifications that require minimum latency and processing time 214 # For high priority notifications that require minimum latency and processing time
207 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 215 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
208 - poll_interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" 216 + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
209 217
210 service: 218 service:
211 type: "${TB_SERVICE_TYPE:tb-transport}" 219 type: "${TB_SERVICE_TYPE:tb-transport}"
212 # Unique id for this service (autogenerated if empty) 220 # Unique id for this service (autogenerated if empty)
213 id: "${TB_SERVICE_ID:}" 221 id: "${TB_SERVICE_ID:}"
214 - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.  
  222 + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
  223 +
  224 +
  225 +metrics:
  226 + # Enable/disable actuator metrics.
  227 + enabled: "${METRICS_ENABLED:false}"
  228 +
  229 +management:
  230 + endpoints:
  231 + web:
  232 + exposure:
  233 + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
  234 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
@@ -206,10 +206,22 @@ queue: @@ -206,10 +206,22 @@ queue:
206 transport: 206 transport:
207 # For high priority notifications that require minimum latency and processing time 207 # For high priority notifications that require minimum latency and processing time
208 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 208 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
209 - poll_interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" 209 + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
210 210
211 service: 211 service:
212 type: "${TB_SERVICE_TYPE:tb-transport}" 212 type: "${TB_SERVICE_TYPE:tb-transport}"
213 # Unique id for this service (autogenerated if empty) 213 # Unique id for this service (autogenerated if empty)
214 id: "${TB_SERVICE_ID:}" 214 id: "${TB_SERVICE_ID:}"
215 - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.  
  215 + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
  216 +
  217 +
  218 +metrics:
  219 + # Enable/disable actuator metrics.
  220 + enabled: "${METRICS_ENABLED:false}"
  221 +
  222 +management:
  223 + endpoints:
  224 + web:
  225 + exposure:
  226 + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
  227 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
@@ -14,8 +14,16 @@ @@ -14,8 +14,16 @@
14 # limitations under the License. 14 # limitations under the License.
15 # 15 #
16 16
17 -spring.main.web-environment: false  
18 -spring.main.web-application-type: none 17 +# If you enabled process metrics you should also enable 'web-environment'.
  18 +spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
  19 +# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
  20 +spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
  21 +
  22 +server:
  23 + # Server bind address (has no effect if web-environment is disabled).
  24 + address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
  25 + # Server bind port (has no effect if web-environment is disabled).
  26 + port: "${HTTP_BIND_PORT:8083}"
19 27
20 # Zookeeper connection parameters. Used for service discovery. 28 # Zookeeper connection parameters. Used for service discovery.
21 zk: 29 zk:
@@ -226,10 +234,21 @@ queue: @@ -226,10 +234,21 @@ queue:
226 transport: 234 transport:
227 # For high priority notifications that require minimum latency and processing time 235 # For high priority notifications that require minimum latency and processing time
228 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 236 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
229 - poll_interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" 237 + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
230 238
231 service: 239 service:
232 type: "${TB_SERVICE_TYPE:tb-transport}" 240 type: "${TB_SERVICE_TYPE:tb-transport}"
233 # Unique id for this service (autogenerated if empty) 241 # Unique id for this service (autogenerated if empty)
234 id: "${TB_SERVICE_ID:}" 242 id: "${TB_SERVICE_ID:}"
235 - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.  
  243 + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
  244 +
  245 +metrics:
  246 + # Enable/disable actuator metrics.
  247 + enabled: "${METRICS_ENABLED:false}"
  248 +
  249 +management:
  250 + endpoints:
  251 + web:
  252 + exposure:
  253 + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
  254 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
@@ -25,7 +25,7 @@ const DAY = 24 * HOUR; @@ -25,7 +25,7 @@ const DAY = 24 * HOUR;
25 const MIN_INTERVAL = SECOND; 25 const MIN_INTERVAL = SECOND;
26 const MAX_INTERVAL = 365 * 20 * DAY; 26 const MAX_INTERVAL = 365 * 20 * DAY;
27 27
28 -const MIN_LIMIT = 10; 28 +const MIN_LIMIT = 7;
29 //const AVG_LIMIT = 200; 29 //const AVG_LIMIT = 200;
30 //const MAX_LIMIT = 500; 30 //const MAX_LIMIT = 500;
31 31