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 99 </dependency>
100 100 <dependency>
101 101 <groupId>org.thingsboard.common</groupId>
  102 + <artifactId>stats</artifactId>
  103 + </dependency>
  104 + <dependency>
  105 + <groupId>org.thingsboard.common</groupId>
102 106 <artifactId>edge-api</artifactId>
103 107 </dependency>
104 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 29 @ComponentScan({"org.thingsboard.server.install",
30 30 "org.thingsboard.server.service.component",
31 31 "org.thingsboard.server.service.install",
32   - "org.thingsboard.server.dao"})
  32 + "org.thingsboard.server.dao",
  33 + "org.thingsboard.server.common.stats"})
33 34 public class ThingsboardInstallApplication {
34 35
35 36 private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
... ...
... ... @@ -221,6 +221,10 @@ public class ActorSystemContext {
221 221 @Getter
222 222 private ClaimDevicesService claimDevicesService;
223 223
  224 + @Autowired
  225 + @Getter
  226 + private JsInvokeStats jsInvokeStats;
  227 +
224 228 //TODO: separate context for TbCore and TbRuleEngine
225 229 @Autowired(required = false)
226 230 @Getter
... ... @@ -284,19 +288,14 @@ public class ActorSystemContext {
284 288 @Getter
285 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 292 @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
295 293 public void printStats() {
296 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 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 132 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
133 133 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
134 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 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 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 184 RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
182 185 RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
183   - tbMsg = TbMsg.newMsg(tbMsg, ruleChainId, ruleNodeId);
  186 + TbMsg tbMsg = TbMsg.newMsg(source, ruleChainId, ruleNodeId);
184 187 TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder()
185 188 .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
186 189 .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
... ... @@ -189,6 +192,10 @@ class DefaultTbContext implements TbContext {
189 192 if (failureMessage != null) {
190 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 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 301 @Override
295 302 public void logJsEvalRequest() {
296 303 if (mainCtx.isStatisticsEnabled()) {
297   - mainCtx.getJsInvokeRequestsCount().incrementAndGet();
  304 + mainCtx.getJsInvokeStats().incrementRequests();
298 305 }
299 306 }
300 307
301 308 @Override
302 309 public void logJsEvalResponse() {
303 310 if (mainCtx.isStatisticsEnabled()) {
304   - mainCtx.getJsInvokeResponsesCount().incrementAndGet();
  311 + mainCtx.getJsInvokeStats().incrementResponses();
305 312 }
306 313 }
307 314
308 315 @Override
309 316 public void logJsEvalFailure() {
310 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 168 private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) {
169 169 return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()),
170 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 174 private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
27 27 import org.thingsboard.server.common.data.rule.RuleNode;
28 28 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
29 29 import org.thingsboard.server.common.msg.queue.RuleNodeException;
  30 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
30 31
31 32 /**
32 33 * @author Andrew Shvayka
... ... @@ -38,6 +39,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
38 39 private RuleNode ruleNode;
39 40 private TbNode tbNode;
40 41 private DefaultTbContext defaultCtx;
  42 + private RuleNodeInfo info;
41 43
42 44 RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
43 45 , TbActorRef parent, TbActorRef self) {
... ... @@ -46,6 +48,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
46 48 this.self = self;
47 49 this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
48 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 54 @Override
... ... @@ -59,6 +62,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
59 62 @Override
60 63 public void onUpdate(TbActorCtx context) throws Exception {
61 64 RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
  65 + this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
62 66 boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
63 67 !(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
64 68 this.ruleNode = newRuleNode;
... ... @@ -99,6 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
99 103 }
100 104
101 105 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
  106 + msg.getMsg().getCallback().visit(info);
102 107 checkActive(msg.getMsg());
103 108 if (ruleNode.isDebugMode()) {
104 109 systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
... ...
... ... @@ -962,18 +962,18 @@ public final class EdgeGrpcSession implements Closeable {
962 962 switch (alarmUpdateMsg.getMsgType()) {
963 963 case ENTITY_CREATED_RPC_MESSAGE:
964 964 case ENTITY_UPDATED_RPC_MESSAGE:
965   - if (existentAlarm == null) {
  965 + if (existentAlarm == null || existentAlarm.getStatus().isCleared()) {
966 966 existentAlarm = new Alarm();
967 967 existentAlarm.setTenantId(edge.getTenantId());
968 968 existentAlarm.setType(alarmUpdateMsg.getName());
969 969 existentAlarm.setOriginator(originatorId);
970 970 existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
971   - existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));
972 971 existentAlarm.setStartTs(alarmUpdateMsg.getStartTs());
973   - existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
974 972 existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
975 973 existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
976 974 }
  975 + existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));
  976 + existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
977 977 existentAlarm.setEndTs(alarmUpdateMsg.getEndTs());
978 978 existentAlarm.setDetails(mapper.readTree(alarmUpdateMsg.getDetails()));
979 979 ctx.getAlarmService().createOrUpdateAlarm(existentAlarm);
... ...
... ... @@ -15,14 +15,20 @@
15 15 */
16 16 package org.thingsboard.server.service.edge.rpc.constructor;
17 17
  18 +import com.google.gson.Gson;
  19 +import com.google.gson.JsonArray;
18 20 import com.google.gson.JsonElement;
  21 +import com.google.gson.JsonObject;
19 22 import lombok.extern.slf4j.Slf4j;
20 23 import org.springframework.stereotype.Component;
21 24 import org.thingsboard.server.common.data.audit.ActionType;
22 25 import org.thingsboard.server.common.data.id.EntityId;
23 26 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  27 +import org.thingsboard.server.gen.edge.AttributeDeleteMsg;
24 28 import org.thingsboard.server.gen.edge.EntityDataProto;
25 29
  30 +import java.util.List;
  31 +
26 32 @Component
27 33 @Slf4j
28 34 public class EntityDataMsgConstructor {
... ... @@ -42,13 +48,26 @@ public class EntityDataMsgConstructor {
42 48 break;
43 49 case ATTRIBUTES_UPDATED:
44 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 54 } catch (Exception e) {
47 55 log.warn("Can't convert to attributes proto, entityData [{}]", entityData, e);
48 56 }
49 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 72 return builder.build();
54 73 }
... ...
... ... @@ -45,8 +45,8 @@ public class EntityViewUpdateMsgConstructor {
45 45 .setIdLSB(entityView.getId().getId().getLeastSignificantBits())
46 46 .setName(entityView.getName())
47 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 50 .setEntityType(entityType);
51 51 return builder.build();
52 52 }
... ...
... ... @@ -71,7 +71,9 @@ import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg;
71 71 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
72 72
73 73 import java.util.ArrayList;
  74 +import java.util.HashMap;
74 75 import java.util.List;
  76 +import java.util.Map;
75 77 import java.util.UUID;
76 78
77 79 @Service
... ... @@ -289,25 +291,29 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
289 291 public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) {
290 292 if (ssAttributes != null && !ssAttributes.isEmpty()) {
291 293 try {
292   - ObjectNode entityNode = mapper.createObjectNode();
  294 + Map<String, Object> entityData = new HashMap<>();
  295 + ObjectNode attributes = mapper.createObjectNode();
293 296 for (AttributeKvEntry attr : ssAttributes) {
294 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 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 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 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 311 saveEdgeEvent(edge.getTenantId(),
306 312 edge.getId(),
307 313 edgeEventType,
308 314 ActionType.ATTRIBUTES_UPDATED,
309 315 entityId,
310   - entityNode);
  316 + entityBody);
311 317 } catch (Exception e) {
312 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 26 import org.thingsboard.server.common.msg.TbActorMsg;
27 27 import org.thingsboard.server.common.msg.queue.ServiceType;
28 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 30 import org.thingsboard.server.queue.TbQueueConsumer;
41 31 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
42 32 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
43 33 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
  34 +import org.thingsboard.server.common.stats.StatsFactory;
44 35 import org.thingsboard.server.queue.util.TbCoreComponent;
45 36 import org.thingsboard.server.service.edge.EdgeNotificationService;
46 37 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
... ... @@ -83,19 +74,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
83 74 private final TbLocalSubscriptionService localSubscriptionService;
84 75 private final SubscriptionManagerService subscriptionManagerService;
85 76 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
  77 + private final TbCoreConsumerStats stats;
86 78 private final EdgeNotificationService edgeNotificationService;
87   - private final TbCoreConsumerStats stats = new TbCoreConsumerStats();
88 79
89 80 public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
90 81 DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
91 82 SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
92   - TbCoreDeviceRpcService tbCoreDeviceRpcService, EdgeNotificationService edgeNotificationService) {
  83 + TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, EdgeNotificationService edgeNotificationService) {
93 84 super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
94 85 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
95 86 this.stateService = stateService;
96 87 this.localSubscriptionService = localSubscriptionService;
97 88 this.subscriptionManagerService = subscriptionManagerService;
98 89 this.tbCoreDeviceRpcService = tbCoreDeviceRpcService;
  90 + this.stats = new TbCoreConsumerStats(statsFactory);
99 91 this.edgeNotificationService = edgeNotificationService;
100 92 }
101 93
... ... @@ -235,6 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
235 227 public void printStats() {
236 228 if (statsEnabled) {
237 229 stats.printStats();
  230 + stats.reset();
238 231 }
239 232 }
240 233
... ...
... ... @@ -22,11 +22,13 @@ import org.springframework.scheduling.annotation.Scheduled;
22 22 import org.springframework.stereotype.Service;
23 23 import org.thingsboard.rule.engine.api.RpcError;
24 24 import org.thingsboard.server.actors.ActorSystemContext;
  25 +import org.thingsboard.server.common.data.id.RuleNodeId;
25 26 import org.thingsboard.server.common.data.id.TenantId;
26 27 import org.thingsboard.server.common.msg.TbActorMsg;
27 28 import org.thingsboard.server.common.msg.TbMsg;
28 29 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
29 30 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  31 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
30 32 import org.thingsboard.server.common.msg.queue.ServiceQueue;
31 33 import org.thingsboard.server.common.msg.queue.ServiceType;
32 34 import org.thingsboard.server.common.msg.queue.TbCallback;
... ... @@ -40,6 +42,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
40 42 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
41 43 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
42 44 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
  45 +import org.thingsboard.server.common.stats.StatsFactory;
43 46 import org.thingsboard.server.queue.util.TbRuleEngineComponent;
44 47 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
45 48 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
... ... @@ -58,6 +61,7 @@ import javax.annotation.PreDestroy;
58 61 import java.util.Collections;
59 62 import java.util.HashSet;
60 63 import java.util.List;
  64 +import java.util.Map;
61 65 import java.util.Optional;
62 66 import java.util.Set;
63 67 import java.util.UUID;
... ... @@ -79,6 +83,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
79 83 @Value("${queue.rule-engine.stats.enabled:true}")
80 84 private boolean statsEnabled;
81 85
  86 + private final StatsFactory statsFactory;
82 87 private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory;
83 88 private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory;
84 89 private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory;
... ... @@ -95,7 +100,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
95 100 TbQueueRuleEngineSettings ruleEngineSettings,
96 101 TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
97 102 ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
98   - TbRuleEngineDeviceRpcService tbDeviceRpcService) {
  103 + TbRuleEngineDeviceRpcService tbDeviceRpcService,
  104 + StatsFactory statsFactory) {
99 105 super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
100 106 this.statisticsService = statisticsService;
101 107 this.ruleEngineSettings = ruleEngineSettings;
... ... @@ -103,6 +109,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
103 109 this.submitStrategyFactory = submitStrategyFactory;
104 110 this.processingStrategyFactory = processingStrategyFactory;
105 111 this.tbDeviceRpcService = tbDeviceRpcService;
  112 + this.statsFactory = statsFactory;
106 113 }
107 114
108 115 @PostConstruct
... ... @@ -111,7 +118,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
111 118 for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) {
112 119 consumerConfigurations.putIfAbsent(configuration.getName(), configuration);
113 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 123 submitExecutor = Executors.newSingleThreadExecutor();
117 124 }
... ... @@ -181,6 +188,12 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
181 188 }
182 189
183 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 197 TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
185 198 if (statsEnabled) {
186 199 stats.log(result, decision.isCommit());
... ... @@ -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 240 @Override
212 241 protected ServiceType getServiceType() {
213 242 return ServiceType.TB_RULE_ENGINE;
... ... @@ -269,6 +298,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
269 298 consumerStats.forEach((queue, stats) -> {
270 299 stats.printStats();
271 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 17
18 18 import lombok.extern.slf4j.Slf4j;
19 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 26 @Slf4j
24 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 89 public void log(TransportProtos.TransportToDeviceActorMsg msg) {
41   - totalCounter.incrementAndGet();
  90 + totalCounter.increment();
42 91 if (msg.hasSessionEvent()) {
43   - sessionEventCounter.incrementAndGet();
  92 + sessionEventCounter.increment();
44 93 }
45 94 if (msg.hasGetAttributes()) {
46   - getAttributesCounter.incrementAndGet();
  95 + getAttributesCounter.increment();
47 96 }
48 97 if (msg.hasSubscribeToAttributes()) {
49   - subscribeToAttributesCounter.incrementAndGet();
  98 + subscribeToAttributesCounter.increment();
50 99 }
51 100 if (msg.hasSubscribeToRPC()) {
52   - subscribeToRPCCounter.incrementAndGet();
  101 + subscribeToRPCCounter.increment();
53 102 }
54 103 if (msg.hasToDeviceRPCCallResponse()) {
55   - toDeviceRPCCallResponseCounter.incrementAndGet();
  104 + toDeviceRPCCallResponseCounter.increment();
56 105 }
57 106 if (msg.hasSubscriptionInfo()) {
58   - subscriptionInfoCounter.incrementAndGet();
  107 + subscriptionInfoCounter.increment();
59 108 }
60 109 if (msg.hasClaimDevice()) {
61   - claimDeviceCounter.incrementAndGet();
  110 + claimDeviceCounter.increment();
62 111 }
63 112 }
64 113
65 114 public void log(TransportProtos.DeviceStateServiceMsgProto msg) {
66   - totalCounter.incrementAndGet();
67   - deviceStateCounter.incrementAndGet();
  115 + totalCounter.increment();
  116 + deviceStateCounter.increment();
68 117 }
69 118
70 119 public void log(TransportProtos.EdgeNotificationMsgProto msg) {
71   - totalCounter.incrementAndGet();
72   - edgeNotificationMsgCounter.incrementAndGet();
  120 + totalCounter.increment();
  121 + edgeNotificationMsgCounter.increment();
73 122 }
74 123
75 124 public void log(TransportProtos.SubscriptionMgrMsgProto msg) {
76   - totalCounter.incrementAndGet();
77   - subscriptionMsgCounter.incrementAndGet();
  125 + totalCounter.increment();
  126 + subscriptionMsgCounter.increment();
78 127 }
79 128
80 129 public void log(TransportProtos.ToCoreNotificationMsg msg) {
81   - totalCounter.incrementAndGet();
82   - toCoreNotificationsCounter.incrementAndGet();
  130 + totalCounter.increment();
  131 + toCoreNotificationsCounter.increment();
83 132 }
84 133
85 134 public void printStats() {
86   - int total = totalCounter.getAndSet(0);
  135 + int total = totalCounter.get();
87 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 16 package org.thingsboard.server.service.queue;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
19 20 import org.thingsboard.server.common.data.id.TenantId;
20 21 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  22 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
21 23 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
22 24
23 25 import java.util.UUID;
... ... @@ -45,4 +47,10 @@ public class TbMsgPackCallback implements TbMsgCallback {
45 47 log.trace("[{}] ON FAILURE", id, e);
46 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 16 package org.thingsboard.server.service.queue;
17 17
18 18 import lombok.Getter;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
19 20 import org.thingsboard.server.common.data.id.TenantId;
20 21 import org.thingsboard.server.common.msg.queue.RuleEngineException;
  22 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
21 23 import org.thingsboard.server.gen.transport.TransportProtos;
22 24 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
23 25 import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
... ... @@ -32,7 +34,6 @@ import java.util.concurrent.atomic.AtomicInteger;
32 34 public class TbMsgPackProcessingContext {
33 35
34 36 private final TbRuleEngineSubmitStrategy submitStrategy;
35   -
36 37 private final AtomicInteger pendingCount;
37 38 private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
38 39 @Getter
... ... @@ -44,6 +45,8 @@ public class TbMsgPackProcessingContext {
44 45 @Getter
45 46 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>();
46 47
  48 + private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
  49 +
47 50 public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
48 51 this.submitStrategy = submitStrategy;
49 52 this.pendingMap = submitStrategy.getPendingMap();
... ... @@ -81,4 +84,13 @@ public class TbMsgPackProcessingContext {
81 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 15 */
16 16 package org.thingsboard.server.service.queue;
17 17
18   -import lombok.Data;
19 18 import lombok.extern.slf4j.Slf4j;
20 19 import org.thingsboard.server.common.data.id.TenantId;
21 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
22 21 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
23 22 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  23 +import org.thingsboard.server.common.stats.StatsFactory;
24 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 29 import java.util.concurrent.ConcurrentHashMap;
30 30 import java.util.concurrent.ConcurrentMap;
31   -import java.util.concurrent.atomic.AtomicInteger;
32 31
33 32 @Slf4j
34   -@Data
35 33 public class TbRuleEngineConsumerStats {
36 34
37 35 public static final String TOTAL_MSGS = "totalMsgs";
... ... @@ -43,61 +41,72 @@ public class TbRuleEngineConsumerStats {
43 41 public static final String SUCCESSFUL_ITERATIONS = "successfulIterations";
44 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 56 private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>();
59 57 private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>();
60 58
61 59 private final String queueName;
62 60
63   - public TbRuleEngineConsumerStats(String queueName) {
  61 + public TbRuleEngineConsumerStats(String queueName, StatsFactory statsFactory) {
64 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 85 public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) {
77 86 int success = msg.getSuccessMap().size();
78 87 int pending = msg.getPendingMap().size();
79 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 91 msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess());
83 92 if (finalIterationForPack) {
84 93 if (pending > 0 || failed > 0) {
85   - timeoutMsgCounter.addAndGet(pending);
86   - failedMsgCounter.addAndGet(failed);
  94 + timeoutMsgCounter.add(pending);
  95 + failedMsgCounter.add(failed);
87 96 if (pending > 0) {
88 97 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout());
89 98 }
90 99 if (failed > 0) {
91 100 msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed());
92 101 }
93   - failedIterationsCounter.incrementAndGet();
  102 + failedIterationsCounter.increment();
94 103 } else {
95   - successIterationsCounter.incrementAndGet();
  104 + successIterationsCounter.increment();
96 105 }
97 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 110 if (pending > 0) {
102 111 msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout());
103 112 }
... ... @@ -113,19 +122,31 @@ public class TbRuleEngineConsumerStats {
113 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 137 public void printStats() {
117 138 int total = totalMsgCounter.get();
118 139 if (total > 0) {
119 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 144 log.info("[{}] Stats: {}", queueName, stats);
124 145 }
125 146 }
126 147
127 148 public void reset() {
128   - counters.values().forEach(counter -> counter.set(0));
  149 + counters.forEach(StatsCounter::clear);
129 150 tenantStats.clear();
130 151 tenantExceptions.clear();
131 152 }
... ...
... ... @@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
52 52 try {
53 53 return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
54 54 } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
55   - log.error("Invalid JWT Token", ex);
  55 + log.debug("Invalid JWT Token", ex);
56 56 throw new BadCredentialsException("Invalid JWT token: ", ex);
57 57 } catch (ExpiredJwtException expiredEx) {
58   - log.info("JWT Token is expired", expiredEx);
  58 + log.debug("JWT Token is expired", expiredEx);
59 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 104 }
105 105 }
106 106 });
107   - ruleEngineStats.reset();
108 107 }
109 108
110 109 private AssetId getServiceAssetId(TenantId tenantId, String queueName) {
... ...
... ... @@ -20,6 +20,9 @@ import org.springframework.beans.factory.annotation.Value;
20 20 import org.springframework.boot.context.event.ApplicationReadyEvent;
21 21 import org.springframework.context.event.EventListener;
22 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 26 import org.thingsboard.server.queue.TbQueueConsumer;
24 27 import org.thingsboard.server.queue.TbQueueProducer;
25 28 import org.thingsboard.server.queue.TbQueueResponseTemplate;
... ... @@ -41,9 +44,9 @@ import java.util.concurrent.*;
41 44 @Service
42 45 @TbCoreComponent
43 46 public class TbCoreTransportApiService {
44   -
45 47 private final TbCoreQueueFactory tbCoreQueueFactory;
46 48 private final TransportApiService transportApiService;
  49 + private final StatsFactory statsFactory;
47 50
48 51 @Value("${queue.transport_api.max_pending_requests:10000}")
49 52 private int maxPendingRequests;
... ... @@ -58,9 +61,10 @@ public class TbCoreTransportApiService {
58 61 private TbQueueResponseTemplate<TbProtoQueueMsg<TransportApiRequestMsg>,
59 62 TbProtoQueueMsg<TransportApiResponseMsg>> transportApiTemplate;
60 63
61   - public TbCoreTransportApiService(TbCoreQueueFactory tbCoreQueueFactory, TransportApiService transportApiService) {
  64 + public TbCoreTransportApiService(TbCoreQueueFactory tbCoreQueueFactory, TransportApiService transportApiService, StatsFactory statsFactory) {
62 65 this.tbCoreQueueFactory = tbCoreQueueFactory;
63 66 this.transportApiService = transportApiService;
  67 + this.statsFactory = statsFactory;
64 68 }
65 69
66 70 @PostConstruct
... ... @@ -69,6 +73,9 @@ public class TbCoreTransportApiService {
69 73 TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> producer = tbCoreQueueFactory.createTransportApiResponseProducer();
70 74 TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> consumer = tbCoreQueueFactory.createTransportApiRequestConsumer();
71 75
  76 + String key = StatsType.TRANSPORT.getName();
  77 + MessagesStats queueStats = statsFactory.createMessagesStats(key);
  78 +
72 79 DefaultTbQueueResponseTemplate.DefaultTbQueueResponseTemplateBuilder
73 80 <TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> builder = DefaultTbQueueResponseTemplate.builder();
74 81 builder.requestTemplate(consumer);
... ... @@ -78,6 +85,7 @@ public class TbCoreTransportApiService {
78 85 builder.pollInterval(responsePollDuration);
79 86 builder.executor(transportCallbackExecutor);
80 87 builder.handler(transportApiService);
  88 + builder.stats(queueStats);
81 89 transportApiTemplate = builder.build();
82 90 }
83 91
... ...
... ... @@ -27,7 +27,6 @@ import java.sql.Statement;
27 27
28 28
29 29 @Slf4j
30   -@PsqlDao
31 30 public abstract class AbstractCleanUpService {
32 31
33 32 @Value("${spring.datasource.url}")
... ...
... ... @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Value;
20 20 import org.springframework.scheduling.annotation.Scheduled;
21 21 import org.springframework.stereotype.Service;
22 22 import org.thingsboard.server.dao.util.PsqlDao;
  23 +import org.thingsboard.server.dao.util.SqlDao;
23 24 import org.thingsboard.server.service.ttl.AbstractCleanUpService;
24 25
25 26 import java.sql.Connection;
... ... @@ -27,6 +28,7 @@ import java.sql.DriverManager;
27 28 import java.sql.SQLException;
28 29
29 30 @PsqlDao
  31 +@SqlDao
30 32 @Slf4j
31 33 @Service
32 34 public class EventsCleanUpService extends AbstractCleanUpService {
... ...
... ... @@ -18,14 +18,12 @@ package org.thingsboard.server.service.ttl.timeseries;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Value;
20 20 import org.springframework.scheduling.annotation.Scheduled;
21   -import org.thingsboard.server.dao.util.PsqlTsAnyDao;
22 21 import org.thingsboard.server.service.ttl.AbstractCleanUpService;
23 22
24 23 import java.sql.Connection;
25 24 import java.sql.DriverManager;
26 25 import java.sql.SQLException;
27 26
28   -@PsqlTsAnyDao
29 27 @Slf4j
30 28 public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService {
31 29
... ...
... ... @@ -252,14 +252,17 @@ sql:
252 252 batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
253 253 batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
254 254 stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
  255 + batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
255 256 ts:
256 257 batch_size: "${SQL_TS_BATCH_SIZE:10000}"
257 258 batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
258 259 stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
  260 + batch_threads: "${SQL_TS_BATCH_THREADS:4}"
259 261 ts_latest:
260 262 batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
261 263 batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
262 264 stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
  265 + batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
263 266 # Specify whether to remove null characters from strValue of attributes and timeseries before insert
264 267 remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
265 268 postgres:
... ... @@ -268,6 +271,7 @@ sql:
268 271 timescale:
269 272 # Specify Interval size for new data chunks storage.
270 273 chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
  274 + batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"
271 275 ttl:
272 276 ts:
273 277 enabled: "${SQL_TTL_TS_ENABLED:true}"
... ... @@ -767,7 +771,7 @@ queue:
767 771 transport:
768 772 # For high priority notifications that require minimum latency and processing time
769 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 776 service:
773 777 type: "${TB_SERVICE_TYPE:monolith}" # monolith or tb-core or tb-rule-engine
... ... @@ -775,3 +779,13 @@ service:
775 779 id: "${TB_SERVICE_ID:}"
776 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}'
\ No newline at end of file
... ...
  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 17
18 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 24 @ConditionalOnExpression("'${database.ts.type}'=='sql' && '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'")
21 25 public @interface PsqlTsDao { }
\ No newline at end of file
... ...
... ... @@ -109,9 +109,16 @@ message EntityDataProto {
109 109 string entityType = 3;
110 110 transport.PostTelemetryMsg postTelemetryMsg = 4;
111 111 transport.PostAttributeMsg postAttributesMsg = 5;
  112 + string postAttributeScope = 6;
  113 + AttributeDeleteMsg attributeDeleteMsg = 7;
112 114 // transport.ToDeviceRpcRequestMsg ???
113 115 }
114 116
  117 +message AttributeDeleteMsg {
  118 + string scope = 1;
  119 + repeated string attributeNames = 2;
  120 +}
  121 +
115 122 message RuleChainUpdateMsg {
116 123 UpdateMsgType msgType = 1;
117 124 int64 idMSB = 2;
... ...
... ... @@ -106,7 +106,6 @@ public final class TbMsg implements Serializable {
106 106 if (callback != null) {
107 107 this.callback = callback;
108 108 } else {
109   - log.warn("[{}] Created message with empty callback: {}", originator, type);
110 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 +}
\ No newline at end of file
... ...
... ... @@ -34,4 +34,7 @@ public interface TbMsgCallback {
34 34
35 35 void onFailure(RuleEngineException e);
36 36
  37 + default void visit(RuleNodeInfo ruleNodeInfo) {
  38 + }
  39 +
37 40 }
... ...
... ... @@ -41,6 +41,7 @@
41 41 <module>queue</module>
42 42 <module>transport</module>
43 43 <module>dao-api</module>
  44 + <module>stats</module>
44 45 <module>edge-api</module>
45 46 </modules>
46 47
... ...
... ... @@ -49,6 +49,10 @@
49 49 <artifactId>message</artifactId>
50 50 </dependency>
51 51 <dependency>
  52 + <groupId>org.thingsboard.common</groupId>
  53 + <artifactId>stats</artifactId>
  54 + </dependency>
  55 + <dependency>
52 56 <groupId>org.apache.kafka</groupId>
53 57 <artifactId>kafka-clients</artifactId>
54 58 </dependency>
... ... @@ -112,6 +116,7 @@
112 116 <groupId>org.apache.curator</groupId>
113 117 <artifactId>curator-recipes</artifactId>
114 118 </dependency>
  119 +
115 120 <dependency>
116 121 <groupId>junit</groupId>
117 122 <artifactId>junit</artifactId>
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.queue;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.stats.MessagesStats;
19 20
20 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 26
26 27 void stop();
27 28
  29 + void setMessagesStats(MessagesStats messagesStats);
28 30 }
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.queue.TbQueueMsg;
28 28 import org.thingsboard.server.queue.TbQueueMsgMetadata;
29 29 import org.thingsboard.server.queue.TbQueueProducer;
30 30 import org.thingsboard.server.queue.TbQueueRequestTemplate;
  31 +import org.thingsboard.server.common.stats.MessagesStats;
31 32
32 33 import java.util.List;
33 34 import java.util.UUID;
... ... @@ -54,6 +55,8 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
54 55 private volatile long tickSize = 0L;
55 56 private volatile boolean stopped = false;
56 57
  58 + private MessagesStats messagesStats;
  59 +
57 60 @Builder
58 61 public DefaultTbQueueRequestTemplate(TbQueueAdmin queueAdmin,
59 62 TbQueueProducer<Request> requestTemplate,
... ... @@ -154,6 +157,11 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
154 157 }
155 158
156 159 @Override
  160 + public void setMessagesStats(MessagesStats messagesStats) {
  161 + this.messagesStats = messagesStats;
  162 + }
  163 +
  164 + @Override
157 165 public ListenableFuture<Response> send(Request request) {
158 166 if (tickSize > maxPendingRequests) {
159 167 return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!"));
... ... @@ -166,14 +174,23 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
166 174 ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future);
167 175 pendingRequests.putIfAbsent(requestId, responseMetaData);
168 176 log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime);
  177 + if (messagesStats != null) {
  178 + messagesStats.incrementTotal();
  179 + }
169 180 requestTemplate.send(TopicPartitionInfo.builder().topic(requestTemplate.getDefaultTopic()).build(), request, new TbQueueCallback() {
170 181 @Override
171 182 public void onSuccess(TbQueueMsgMetadata metadata) {
  183 + if (messagesStats != null) {
  184 + messagesStats.incrementSuccessful();
  185 + }
172 186 log.trace("[{}] Request sent: {}", requestId, metadata);
173 187 }
174 188
175 189 @Override
176 190 public void onFailure(Throwable t) {
  191 + if (messagesStats != null) {
  192 + messagesStats.incrementFailed();
  193 + }
177 194 pendingRequests.remove(requestId);
178 195 future.setException(t);
179 196 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.queue.TbQueueHandler;
23 23 import org.thingsboard.server.queue.TbQueueMsg;
24 24 import org.thingsboard.server.queue.TbQueueProducer;
25 25 import org.thingsboard.server.queue.TbQueueResponseTemplate;
  26 +import org.thingsboard.server.common.stats.MessagesStats;
26 27
27 28 import java.util.List;
28 29 import java.util.UUID;
... ... @@ -44,6 +45,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
44 45 private final ExecutorService loopExecutor;
45 46 private final ScheduledExecutorService timeoutExecutor;
46 47 private final ExecutorService callbackExecutor;
  48 + private final MessagesStats stats;
47 49 private final int maxPendingRequests;
48 50 private final long requestTimeout;
49 51
... ... @@ -58,7 +60,8 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
58 60 long pollInterval,
59 61 long requestTimeout,
60 62 int maxPendingRequests,
61   - ExecutorService executor) {
  63 + ExecutorService executor,
  64 + MessagesStats stats) {
62 65 this.requestTemplate = requestTemplate;
63 66 this.responseTemplate = responseTemplate;
64 67 this.pendingRequests = new ConcurrentHashMap<>();
... ... @@ -66,6 +69,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
66 69 this.pollInterval = pollInterval;
67 70 this.requestTimeout = requestTimeout;
68 71 this.callbackExecutor = executor;
  72 + this.stats = stats;
69 73 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
70 74 this.loopExecutor = Executors.newSingleThreadExecutor();
71 75 }
... ... @@ -108,11 +112,13 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
108 112 String responseTopic = bytesToString(responseTopicHeader);
109 113 try {
110 114 pendingRequestCount.getAndIncrement();
  115 + stats.incrementTotal();
111 116 AsyncCallbackTemplate.withCallbackAndTimeout(handler.handle(request),
112 117 response -> {
113 118 pendingRequestCount.decrementAndGet();
114 119 response.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
115 120 responseTemplate.send(TopicPartitionInfo.builder().topic(responseTopic).build(), response, null);
  121 + stats.incrementSuccessful();
116 122 },
117 123 e -> {
118 124 pendingRequestCount.decrementAndGet();
... ... @@ -121,6 +127,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
121 127 } else {
122 128 log.trace("[{}] Failed to process the request: {}", requestId, request, e);
123 129 }
  130 + stats.incrementFailed();
124 131 },
125 132 requestTimeout,
126 133 timeoutExecutor,
... ... @@ -128,6 +135,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
128 135 } catch (Throwable e) {
129 136 pendingRequestCount.decrementAndGet();
130 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>
\ No newline at end of file
... ...
  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 42 </dependency>
43 43 <dependency>
44 44 <groupId>org.thingsboard.common</groupId>
  45 + <artifactId>stats</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>org.thingsboard.common</groupId>
45 49 <artifactId>data</artifactId>
46 50 </dependency>
47 51 <dependency>
... ...
... ... @@ -55,6 +55,9 @@ import org.thingsboard.server.queue.discovery.PartitionService;
55 55 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
56 56 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
57 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 62 import javax.annotation.PostConstruct;
60 63 import javax.annotation.PreDestroy;
... ... @@ -101,12 +104,17 @@ public class DefaultTransportService implements TransportService {
101 104 private final TbQueueProducerProvider producerProvider;
102 105 private final PartitionService partitionService;
103 106 private final TbServiceInfoProvider serviceInfoProvider;
  107 + private final StatsFactory statsFactory;
104 108
105 109 protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
106 110 protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
107 111 protected TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> tbCoreMsgProducer;
108 112 protected TbQueueConsumer<TbProtoQueueMsg<ToTransportMsg>> transportNotificationsConsumer;
109 113
  114 + protected MessagesStats ruleEngineProducerStats;
  115 + protected MessagesStats tbCoreProducerStats;
  116 + protected MessagesStats transportApiStats;
  117 +
110 118 protected ScheduledExecutorService schedulerExecutor;
111 119 protected ExecutorService transportCallbackExecutor;
112 120
... ... @@ -119,11 +127,12 @@ public class DefaultTransportService implements TransportService {
119 127 private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
120 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 131 this.serviceInfoProvider = serviceInfoProvider;
124 132 this.queueProvider = queueProvider;
125 133 this.producerProvider = producerProvider;
126 134 this.partitionService = partitionService;
  135 + this.statsFactory = statsFactory;
127 136 }
128 137
129 138 @PostConstruct
... ... @@ -133,10 +142,14 @@ public class DefaultTransportService implements TransportService {
133 142 new TbRateLimits(perTenantLimitsConf);
134 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 148 this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler"));
137 149 this.transportCallbackExecutor = Executors.newWorkStealingPool(20);
138 150 this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS);
139 151 transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate();
  152 + transportApiRequestTemplate.setMessagesStats(transportApiStats);
140 153 ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer();
141 154 tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer();
142 155 transportNotificationsConsumer = queueProvider.createTransportNotificationsConsumer();
... ... @@ -557,10 +570,14 @@ public class DefaultTransportService implements TransportService {
557 570 if (log.isTraceEnabled()) {
558 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 577 tbCoreMsgProducer.send(tpi,
561 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 583 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
... ... @@ -571,7 +588,9 @@ public class DefaultTransportService implements TransportService {
571 588 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
572 589 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
573 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 596 private class TransportTbQueueCallback implements TbQueueCallback {
... ... @@ -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 638 private class MsgPackCallback implements TbQueueCallback {
596 639 private final AtomicInteger msgCount;
597 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 45 </dependency>
46 46 <dependency>
47 47 <groupId>org.thingsboard.common</groupId>
  48 + <artifactId>stats</artifactId>
  49 + </dependency>
  50 + <dependency>
  51 + <groupId>org.thingsboard.common</groupId>
48 52 <artifactId>dao-api</artifactId>
49 53 </dependency>
50 54 <dependency>
... ...
... ... @@ -24,6 +24,9 @@ import org.springframework.beans.factory.annotation.Value;
24 24 import org.springframework.scheduling.annotation.Scheduled;
25 25 import org.springframework.stereotype.Component;
26 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 30 import org.thingsboard.server.dao.entity.EntityService;
28 31 import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
29 32 import org.thingsboard.server.dao.util.AsyncTaskContext;
... ... @@ -57,48 +60,58 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor<
57 60 @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled,
58 61 @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration,
59 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 66 this.printTenantNames = printTenantNames;
63 67 }
64 68
65 69 @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
66 70 public void printStats() {
67 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 117 @PreDestroy
... ...
... ... @@ -31,7 +31,7 @@ import java.util.regex.Pattern;
31 31 @Slf4j
32 32 public abstract class DataValidator<D extends BaseData<?>> {
33 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 36 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
37 37 try {
... ... @@ -64,7 +64,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
64 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 68 if (!doValidateEmail(email)) {
69 69 throw new DataValidationException("Invalid email address format '" + email + "'!");
70 70 }
... ...
... ... @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import com.google.common.util.concurrent.SettableFuture;
20 20 import lombok.extern.slf4j.Slf4j;
21 21 import org.thingsboard.common.util.ThingsBoardThreadFactory;
  22 +import org.thingsboard.server.common.stats.MessagesStats;
22 23
23 24 import java.util.ArrayList;
24 25 import java.util.List;
... ... @@ -27,7 +28,6 @@ import java.util.concurrent.ExecutorService;
27 28 import java.util.concurrent.Executors;
28 29 import java.util.concurrent.LinkedBlockingQueue;
29 30 import java.util.concurrent.TimeUnit;
30   -import java.util.concurrent.atomic.AtomicInteger;
31 31 import java.util.function.Consumer;
32 32 import java.util.stream.Collectors;
33 33
... ... @@ -35,22 +35,19 @@ import java.util.stream.Collectors;
35 35 public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
36 36
37 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 38 private final TbSqlBlockingQueueParams params;
42 39
43 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 44 this.params = params;
  45 + this.stats = stats;
48 46 }
49 47
50 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 51 executor.submit(() -> {
55 52 String logName = params.getLogName();
56 53 int batchSize = params.getBatchSize();
... ... @@ -70,7 +67,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
70 67 log.debug("[{}] Going to save {} entities", logName, entities.size());
71 68 saveFunction.accept(entities.stream().map(TbSqlQueueElement::getEntity).collect(Collectors.toList()));
72 69 entities.forEach(v -> v.getFuture().set(null));
73   - savedCount.addAndGet(entities.size());
  70 + stats.incrementSuccessful(entities.size());
74 71 if (!fullPack) {
75 72 long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs);
76 73 if (remainingDelay > 0) {
... ... @@ -78,7 +75,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
78 75 }
79 76 }
80 77 } catch (Exception e) {
81   - failedCount.addAndGet(entities.size());
  78 + stats.incrementFailed(entities.size());
82 79 entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(e));
83 80 if (e instanceof InterruptedException) {
84 81 log.info("[{}] Queue polling was interrupted", logName);
... ... @@ -93,9 +90,10 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
93 90 });
94 91
95 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 98 }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS);
101 99 }
... ... @@ -111,7 +109,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
111 109 public ListenableFuture<Void> add(E element) {
112 110 SettableFuture<Void> future = SettableFuture.create();
113 111 queue.add(new TbSqlQueueElement<>(future, element));
114   - addedCount.incrementAndGet();
  112 + stats.incrementTotal();
115 113 return future;
116 114 }
117 115 }
... ...
... ... @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql;
18 18 import lombok.Builder;
19 19 import lombok.Data;
20 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 24 @Slf4j
23 25 @Data
... ... @@ -28,4 +30,5 @@ public class TbSqlBlockingQueueParams {
28 30 private final int batchSize;
29 31 private final long maxDelay;
30 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 22
23 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 27 void destroy();
28 28
... ...
... ... @@ -26,14 +26,15 @@ import org.thingsboard.server.common.data.UUIDConverter;
26 26 import org.thingsboard.server.common.data.id.EntityId;
27 27 import org.thingsboard.server.common.data.id.TenantId;
28 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  29 +import org.thingsboard.server.common.stats.StatsFactory;
29 30 import org.thingsboard.server.dao.DaoUtil;
30 31 import org.thingsboard.server.dao.attributes.AttributesDao;
31 32 import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
32 33 import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
33 34 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
34 35 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
35   -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;
36 36 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  37 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
37 38 import org.thingsboard.server.dao.util.SqlDao;
38 39
39 40 import javax.annotation.PostConstruct;
... ... @@ -41,6 +42,7 @@ import javax.annotation.PreDestroy;
41 42 import java.util.Collection;
42 43 import java.util.List;
43 44 import java.util.Optional;
  45 +import java.util.function.Function;
44 46 import java.util.stream.Collectors;
45 47
46 48 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
... ... @@ -59,6 +61,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
59 61 @Autowired
60 62 private AttributeKvInsertRepository attributeKvInsertRepository;
61 63
  64 + @Autowired
  65 + private StatsFactory statsFactory;
  66 +
62 67 @Value("${sql.attributes.batch_size:1000}")
63 68 private int batchSize;
64 69
... ... @@ -68,7 +73,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
68 73 @Value("${sql.attributes.stats_print_interval_ms:1000}")
69 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 81 @PostConstruct
74 82 private void init() {
... ... @@ -77,8 +85,11 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
77 85 .batchSize(batchSize)
78 86 .maxDelay(maxDelay)
79 87 .statsPrintIntervalMs(statsPrintIntervalMs)
  88 + .statsNamePrefix("attributes")
80 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 93 queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v));
83 94 }
84 95
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts;
17 17
18 18 import com.google.common.util.concurrent.Futures;
19 19 import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.ListeningExecutorService;
20 21 import com.google.common.util.concurrent.MoreExecutors;
21 22 import com.google.common.util.concurrent.SettableFuture;
22 23 import lombok.extern.slf4j.Slf4j;
... ... @@ -29,10 +30,12 @@ import org.thingsboard.server.common.data.kv.Aggregation;
29 30 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
30 31 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
31 32 import org.thingsboard.server.common.data.kv.TsKvEntry;
  33 +import org.thingsboard.server.common.stats.StatsFactory;
32 34 import org.thingsboard.server.dao.DaoUtil;
33 35 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
34 36 import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;
35 37 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  38 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
36 39 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
37 40 import org.thingsboard.server.dao.sqlts.ts.TsKvRepository;
38 41 import org.thingsboard.server.dao.timeseries.TimeseriesDao;
... ... @@ -43,6 +46,7 @@ import java.util.ArrayList;
43 46 import java.util.List;
44 47 import java.util.Optional;
45 48 import java.util.concurrent.CompletableFuture;
  49 +import java.util.function.Function;
46 50 import java.util.stream.Collectors;
47 51
48 52 @Slf4j
... ... @@ -54,7 +58,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
54 58 @Autowired
55 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 65 @PostConstruct
60 66 protected void init() {
... ... @@ -64,8 +70,11 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
64 70 .batchSize(tsBatchSize)
65 71 .maxDelay(tsMaxDelay)
66 72 .statsPrintIntervalMs(tsStatsPrintIntervalMs)
  73 + .statsNamePrefix("ts")
67 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 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 34 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
35 35 import org.thingsboard.server.common.data.kv.StringDataEntry;
36 36 import org.thingsboard.server.common.data.kv.TsKvEntry;
  37 +import org.thingsboard.server.common.stats.StatsFactory;
37 38 import org.thingsboard.server.dao.DaoUtil;
38 39 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary;
39 40 import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey;
... ... @@ -41,8 +42,8 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey;
41 42 import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
42 43 import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
43 44 import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
44   -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;
45 45 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  46 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
46 47 import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository;
47 48 import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
48 49 import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository;
... ... @@ -52,7 +53,10 @@ import org.thingsboard.server.dao.timeseries.SimpleListenableFuture;
52 53 import javax.annotation.Nullable;
53 54 import javax.annotation.PostConstruct;
54 55 import javax.annotation.PreDestroy;
  56 +import java.util.ArrayList;
  57 +import java.util.HashMap;
55 58 import java.util.List;
  59 +import java.util.Map;
56 60 import java.util.Objects;
57 61 import java.util.Optional;
58 62 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -82,7 +86,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
82 86 @Autowired
83 87 private TsKvDictionaryRepository dictionaryRepository;
84 88
85   - private TbSqlBlockingQueue<TsKvLatestEntity> tsLatestQueue;
  89 + private TbSqlBlockingQueueWrapper<TsKvLatestEntity> tsLatestQueue;
86 90
87 91 @Value("${sql.ts_latest.batch_size:1000}")
88 92 private int tsLatestBatchSize;
... ... @@ -93,9 +97,15 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
93 97 @Value("${sql.ts_latest.stats_print_interval_ms:1000}")
94 98 private long tsLatestStatsPrintIntervalMs;
95 99
  100 + @Value("${sql.ts_latest.batch_threads:4}")
  101 + private int tsLatestBatchThreads;
  102 +
96 103 @Autowired
97 104 protected ScheduledLogExecutorComponent logExecutor;
98 105
  106 + @Autowired
  107 + private StatsFactory statsFactory;
  108 +
99 109 @Value("${sql.ts.batch_size:1000}")
100 110 protected int tsBatchSize;
101 111
... ... @@ -105,6 +115,12 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
105 115 @Value("${sql.ts.stats_print_interval_ms:1000}")
106 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 124 @PostConstruct
109 125 protected void init() {
110 126 TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder()
... ... @@ -112,9 +128,24 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
112 128 .batchSize(tsLatestBatchSize)
113 129 .maxDelay(tsLatestMaxDelay)
114 130 .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs)
  131 + .statsNamePrefix("ts.latest")
115 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 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 31 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
32 32 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
33 33 import org.thingsboard.server.common.data.kv.TsKvEntry;
  34 +import org.thingsboard.server.common.stats.StatsFactory;
34 35 import org.thingsboard.server.dao.DaoUtil;
35 36 import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity;
36   -import org.thingsboard.server.dao.sql.TbSqlBlockingQueue;
37 37 import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
  38 +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
38 39 import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao;
39 40 import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
40 41 import org.thingsboard.server.dao.timeseries.TimeseriesDao;
... ... @@ -48,6 +49,7 @@ import java.util.List;
48 49 import java.util.Optional;
49 50 import java.util.UUID;
50 51 import java.util.concurrent.CompletableFuture;
  52 +import java.util.function.Function;
51 53
52 54 @Component
53 55 @Slf4j
... ... @@ -61,9 +63,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
61 63 private AggregationRepository aggregationRepository;
62 64
63 65 @Autowired
  66 + private StatsFactory statsFactory;
  67 +
  68 + @Autowired
64 69 protected InsertTsRepository<TimescaleTsKvEntity> insertRepository;
65 70
66   - protected TbSqlBlockingQueue<TimescaleTsKvEntity> tsQueue;
  71 + protected TbSqlBlockingQueueWrapper<TimescaleTsKvEntity> tsQueue;
67 72
68 73 @PostConstruct
69 74 protected void init() {
... ... @@ -73,8 +78,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
73 78 .batchSize(tsBatchSize)
74 79 .maxDelay(tsMaxDelay)
75 80 .statsPrintIntervalMs(tsStatsPrintIntervalMs)
  81 + .statsNamePrefix("ts.timescale")
76 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 87 tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v));
79 88 }
80 89
... ... @@ -277,4 +286,5 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
277 286 startTs,
278 287 endTs);
279 288 }
  289 +
280 290 }
... ...
... ... @@ -23,6 +23,8 @@ import com.google.common.util.concurrent.SettableFuture;
23 23 import lombok.extern.slf4j.Slf4j;
24 24 import org.thingsboard.common.util.ThingsBoardThreadFactory;
25 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 28 import org.thingsboard.server.common.msg.tools.TbRateLimits;
27 29 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
28 30
... ... @@ -38,6 +40,8 @@ import java.util.regex.Matcher;
38 40 @Slf4j
39 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 45 private final long maxWaitTime;
42 46 private final long pollMs;
43 47 private final BlockingQueue<AsyncTaskContext<T, V>> queue;
... ... @@ -49,20 +53,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
49 53 private final boolean perTenantLimitsEnabled;
50 54 private final String perTenantLimitsConfiguration;
51 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 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 64 this.maxWaitTime = maxWaitTime;
67 65 this.pollMs = pollMs;
68 66 this.concurrencyLimit = concurrencyLimit;
... ... @@ -73,6 +71,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
73 71 this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout"));
74 72 this.perTenantLimitsEnabled = perTenantLimitsEnabled;
75 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 78 for (int i = 0; i < dispatcherThreads; i++) {
77 79 dispatcherExecutor.submit(this::dispatch);
78 80 }
... ... @@ -89,8 +91,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
89 91 } else if (!task.getTenantId().isNullUid()) {
90 92 TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(task.getTenantId(), id -> new TbRateLimits(perTenantLimitsConfiguration));
91 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 96 settableFuture.setException(new TenantRateLimitException());
95 97 perTenantLimitReached = true;
96 98 }
... ... @@ -98,10 +100,10 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
98 100 }
99 101 if (!perTenantLimitReached) {
100 102 try {
101   - totalAdded.incrementAndGet();
  103 + stats.getTotalAdded().increment();
102 104 queue.add(new AsyncTaskContext<>(UUID.randomUUID(), task, settableFuture, System.currentTimeMillis()));
103 105 } catch (IllegalStateException e) {
104   - totalRejected.incrementAndGet();
  106 + stats.getTotalRejected().increment();
105 107 settableFuture.setException(e);
106 108 }
107 109 }
... ... @@ -146,14 +148,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
146 148 concurrencyLevel.incrementAndGet();
147 149 long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis();
148 150 if (timeout > 0) {
149   - totalLaunched.incrementAndGet();
  151 + stats.getTotalLaunched().increment();
150 152 ListenableFuture<V> result = execute(finalTaskCtx);
151 153 result = Futures.withTimeout(result, timeout, TimeUnit.MILLISECONDS, timeoutExecutor);
152 154 Futures.addCallback(result, new FutureCallback<V>() {
153 155 @Override
154 156 public void onSuccess(@Nullable V result) {
155 157 logTask("Releasing", finalTaskCtx);
156   - totalReleased.incrementAndGet();
  158 + stats.getTotalReleased().increment();
157 159 concurrencyLevel.decrementAndGet();
158 160 finalTaskCtx.getFuture().set(result);
159 161 }
... ... @@ -165,7 +167,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
165 167 } else {
166 168 logTask("Failed", finalTaskCtx);
167 169 }
168   - totalFailed.incrementAndGet();
  170 + stats.getTotalFailed().increment();
169 171 concurrencyLevel.decrementAndGet();
170 172 finalTaskCtx.getFuture().setException(t);
171 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 175 }, callbackExecutor);
174 176 } else {
175 177 logTask("Expired Before Execution", finalTaskCtx);
176   - totalExpired.incrementAndGet();
  178 + stats.getTotalExpired().increment();
177 179 concurrencyLevel.decrementAndGet();
178 180 taskCtx.getFuture().setException(new TimeoutException());
179 181 }
... ... @@ -185,7 +187,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
185 187 } catch (Throwable e) {
186 188 if (taskCtx != null) {
187 189 log.debug("[{}] Failed to execute task: {}", taskCtx.getId(), taskCtx, e);
188   - totalFailed.incrementAndGet();
  190 + stats.getTotalFailed().increment();
189 191 concurrencyLevel.decrementAndGet();
190 192 } else {
191 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 105 <ua-parser.version>1.4.3</ua-parser.version>
106 106 <commons-beanutils.version>1.9.4</commons-beanutils.version>
107 107 <commons-collections.version>3.2.2</commons-collections.version>
  108 + <micrometer.version>1.5.2</micrometer.version>
108 109 </properties>
109 110
110 111 <modules>
... ... @@ -843,6 +844,11 @@
843 844 <version>${project.version}</version>
844 845 </dependency>
845 846 <dependency>
  847 + <groupId>org.thingsboard.common</groupId>
  848 + <artifactId>stats</artifactId>
  849 + <version>${project.version}</version>
  850 + </dependency>
  851 + <dependency>
846 852 <groupId>org.thingsboard</groupId>
847 853 <artifactId>tools</artifactId>
848 854 <version>${project.version}</version>
... ... @@ -1376,6 +1382,21 @@
1376 1382 <artifactId>struts-tiles</artifactId>
1377 1383 <version>${struts.version}</version>
1378 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 1400 </dependencies>
1380 1401 </dependencyManagement>
1381 1402
... ...
... ... @@ -40,7 +40,7 @@ import org.thingsboard.server.common.msg.TbMsg;
40 40 nodeDetails =
41 41 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
42 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 44 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " +
45 45 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.",
46 46 uiResources = {"static/rulenode/rulenode-core-config.js"},
... ...
... ... @@ -46,7 +46,7 @@ import java.util.List;
46 46 nodeDetails =
47 47 "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
48 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 50 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>. " +
51 51 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>.",
52 52 uiResources = {"static/rulenode/rulenode-core-config.js"},
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.rule.engine.edge;
17 17
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
19 20 import com.fasterxml.jackson.databind.ObjectMapper;
20 21 import com.google.common.util.concurrent.FutureCallback;
21 22 import com.google.common.util.concurrent.Futures;
... ... @@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsg;
45 46 import org.thingsboard.server.common.msg.session.SessionMsgType;
46 47
47 48 import javax.annotation.Nullable;
  49 +import java.util.HashMap;
48 50 import java.util.List;
  51 +import java.util.Map;
  52 +import java.util.UUID;
49 53
50 54 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
51 55
... ... @@ -84,30 +88,33 @@ public class TbMsgPushToEdgeNode implements TbNode {
84 88 Futures.addCallback(getEdgeIdFuture, new FutureCallback<EdgeId>() {
85 89 @Override
86 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 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 112 } catch (JsonProcessingException e) {
96 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 118 @Override
112 119 public void onFailure(Throwable t) {
113 120 ctx.tellFailure(msg, t);
... ... @@ -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 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 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 185 private ActionType getActionTypeByMsgType(String msgType) {
139 186 ActionType actionType;
140 187 if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)) {
... ... @@ -161,14 +208,11 @@ public class TbMsgPushToEdgeNode implements TbNode {
161 208 }
162 209
163 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 212 || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)
166 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 218 private ListenableFuture<EdgeId> getEdgeIdByOriginatorId(TbContext ctx, TenantId tenantId, EntityId originatorId) {
... ...
... ... @@ -67,7 +67,7 @@ public class TbCheckAlarmStatusNode implements TbNode {
67 67 if (result != null) {
68 68 boolean isPresent = false;
69 69 for (AlarmStatus alarmStatus : config.getAlarmStatusList()) {
70   - if (alarm.getStatus() == alarmStatus) {
  70 + if (result.getStatus() == alarmStatus) {
71 71 isPresent = true;
72 72 break;
73 73 }
... ...
... ... @@ -16,8 +16,6 @@
16 16 package org.thingsboard.rule.engine.mqtt;
17 17
18 18 import io.netty.buffer.Unpooled;
19   -import io.netty.channel.EventLoopGroup;
20   -import io.netty.channel.nio.NioEventLoopGroup;
21 19 import io.netty.handler.codec.mqtt.MqttQoS;
22 20 import io.netty.handler.ssl.SslContext;
23 21 import io.netty.handler.ssl.SslContextBuilder;
... ... @@ -37,7 +35,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
37 35 import javax.net.ssl.SSLException;
38 36 import java.nio.charset.Charset;
39 37 import java.util.Optional;
40   -import java.util.concurrent.ExecutionException;
41 38 import java.util.concurrent.TimeUnit;
42 39 import java.util.concurrent.TimeoutException;
43 40
... ... @@ -58,14 +55,14 @@ public class TbMqttNode implements TbNode {
58 55
59 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 62 @Override
66 63 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
67 64 try {
68   - this.config = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class);
  65 + this.mqttNodeConfiguration = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class);
69 66 this.mqttClient = initClient(ctx);
70 67 } catch (Exception e) {
71 68 throw new TbNodeException(e);
... ... @@ -74,7 +71,7 @@ public class TbMqttNode implements TbNode {
74 71
75 72 @Override
76 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 75 this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE)
79 76 .addListener(future -> {
80 77 if (future.isSuccess()) {
... ... @@ -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 101 Optional<SslContext> sslContextOpt = initSslContext();
105 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 108 MqttClient client = MqttClient.create(config, null);
112 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 111 MqttConnectResult result;
115 112 try {
116   - result = connectFuture.get(this.config.getConnectTimeoutSec(), TimeUnit.SECONDS);
  113 + result = connectFuture.get(this.mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS);
117 114 } catch (TimeoutException ex) {
118 115 connectFuture.cancel(true);
119 116 client.disconnect();
120   - String hostPort = this.config.getHost() + ":" + this.config.getPort();
  117 + String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
121 118 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort));
122 119 }
123 120 if (!result.isSuccess()) {
124 121 connectFuture.cancel(true);
125 122 client.disconnect();
126   - String hostPort = this.config.getHost() + ":" + this.config.getPort();
  123 + String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
127 124 throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode()));
128 125 }
129 126 return client;
130 127 }
131 128
132 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 132 result = Optional.of(SslContextBuilder.forClient().build());
136 133 }
137 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 163
164 164 private KeySpec getKeySpec(byte[] encodedKey) throws Exception {
165 165 KeySpec keySpec;
166   - if (password == null) {
  166 + if (password == null || password.isEmpty()) {
167 167 keySpec = new PKCS8EncodedKeySpec(encodedKey);
168 168 } else {
169 169 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
... ...
... ... @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
19 19 import com.fasterxml.jackson.annotation.JsonTypeInfo;
20 20 import io.netty.handler.ssl.SslContext;
21 21 import org.thingsboard.mqtt.MqttClientConfig;
  22 +import org.thingsboard.rule.engine.mqtt.azure.AzureIotHubSasCredentials;
22 23
23 24 import java.util.Optional;
24 25
... ... @@ -29,6 +30,7 @@ import java.util.Optional;
29 30 @JsonSubTypes({
30 31 @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"),
31 32 @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"),
  33 + @JsonSubTypes.Type(value = AzureIotHubSasCredentials.class, name = "sas"),
32 34 @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")})
33 35 public interface MqttClientCredentials {
34 36
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.telemetry;
17 17
18 18 import com.google.gson.JsonParser;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.util.StringUtils;
20 21 import org.thingsboard.rule.engine.api.RuleNode;
21 22 import org.thingsboard.rule.engine.api.TbContext;
22 23 import org.thingsboard.rule.engine.api.TbNode;
... ... @@ -51,6 +52,8 @@ public class TbMsgAttributesNode implements TbNode {
51 52
52 53 private TbMsgAttributesNodeConfiguration config;
53 54
  55 + private static final String SCOPE = "scope";
  56 +
54 57 @Override
55 58 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
56 59 this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class);
... ... @@ -64,7 +67,12 @@ public class TbMsgAttributesNode implements TbNode {
64 67 }
65 68 String src = msg.getData();
66 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 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 6 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...
... ... @@ -14,8 +14,16 @@
14 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 28 # Zookeeper connection parameters. Used for service discovery.
21 29 zk:
... ... @@ -205,10 +213,22 @@ queue:
205 213 transport:
206 214 # For high priority notifications that require minimum latency and processing time
207 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 218 service:
211 219 type: "${TB_SERVICE_TYPE:tb-transport}"
212 220 # Unique id for this service (autogenerated if empty)
213 221 id: "${TB_SERVICE_ID:}"
214   - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
\ No newline at end of file
  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}'
\ No newline at end of file
... ...
... ... @@ -206,10 +206,22 @@ queue:
206 206 transport:
207 207 # For high priority notifications that require minimum latency and processing time
208 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 211 service:
212 212 type: "${TB_SERVICE_TYPE:tb-transport}"
213 213 # Unique id for this service (autogenerated if empty)
214 214 id: "${TB_SERVICE_ID:}"
215   - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
\ No newline at end of file
  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}'
\ No newline at end of file
... ...
... ... @@ -14,8 +14,16 @@
14 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 28 # Zookeeper connection parameters. Used for service discovery.
21 29 zk:
... ... @@ -226,10 +234,21 @@ queue:
226 234 transport:
227 235 # For high priority notifications that require minimum latency and processing time
228 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 239 service:
232 240 type: "${TB_SERVICE_TYPE:tb-transport}"
233 241 # Unique id for this service (autogenerated if empty)
234 242 id: "${TB_SERVICE_ID:}"
235   - tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
\ No newline at end of file
  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}'
\ No newline at end of file
... ...
... ... @@ -25,7 +25,7 @@ const DAY = 24 * HOUR;
25 25 const MIN_INTERVAL = SECOND;
26 26 const MAX_INTERVAL = 365 * 20 * DAY;
27 27
28   -const MIN_LIMIT = 10;
  28 +const MIN_LIMIT = 7;
29 29 //const AVG_LIMIT = 200;
30 30 //const MAX_LIMIT = 500;
31 31
... ...