Commit e4f2c8dffa10c1d4213c8c914fcd44dbbe364012

Authored by Andrew Shvayka
2 parents fc0a2db0 30ba90f5

Refactoring and merge with develop/1.5

Showing 76 changed files with 3453 additions and 418 deletions

Too many changes to show.

To preserve performance only 76 of 94 files are displayed.

... ... @@ -321,23 +321,23 @@ public class ActorSystemContext {
321 321 return discoveryService.getCurrentServer().getServerAddress().toString();
322 322 }
323 323
324   - public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
325   - persistDebug(tenantId, entityId, "IN", tbMsg, null);
  324 + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) {
  325 + persistDebug(tenantId, entityId, "IN", tbMsg, relationType, null);
326 326 }
327 327
328   - public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
329   - persistDebug(tenantId, entityId, "IN", tbMsg, error);
  328 + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) {
  329 + persistDebug(tenantId, entityId, "IN", tbMsg, relationType, error);
330 330 }
331 331
332   - public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
333   - persistDebug(tenantId, entityId, "OUT", tbMsg, error);
  332 + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) {
  333 + persistDebug(tenantId, entityId, "OUT", tbMsg, relationType, error);
334 334 }
335 335
336   - public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
337   - persistDebug(tenantId, entityId, "OUT", tbMsg, null);
  336 + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) {
  337 + persistDebug(tenantId, entityId, "OUT", tbMsg, relationType, null);
338 338 }
339 339
340   - private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) {
  340 + private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, String relationType, Throwable error) {
341 341 try {
342 342 Event event = new Event();
343 343 event.setTenantId(tenantId);
... ... @@ -354,6 +354,7 @@ public class ActorSystemContext {
354 354 .put("msgId", tbMsg.getId().toString())
355 355 .put("msgType", tbMsg.getType())
356 356 .put("dataType", tbMsg.getDataType().name())
  357 + .put("relationType", relationType)
357 358 .put("data", tbMsg.getData())
358 359 .put("metadata", metadata);
359 360
... ...
... ... @@ -356,16 +356,15 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
356 356
357 357 for (Map.Entry<Long, List<KvEntry>> entry : tsData.entrySet()) {
358 358 JsonObject json = new JsonObject();
359   - json.addProperty("ts", entry.getKey());
360   - JsonObject values = new JsonObject();
361 359 for (KvEntry kv : entry.getValue()) {
362   - kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
363   - kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
364   - kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
365   - kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
  360 + kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  361 + kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  362 + kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  363 + kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
366 364 }
367   - json.add("values", values);
368   - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
  365 + TbMsgMetaData metaData = defaultMetaData.copy();
  366 + metaData.putValue("ts", entry.getKey()+"");
  367 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
369 368 pushToRuleEngineWithTimeout(context, tbMsg, msgData);
370 369 }
371 370 }
... ...
... ... @@ -29,15 +29,12 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
29 29 @Slf4j
30 30 public class BasicRpcSessionListener implements GrpcSessionListener {
31 31
32   - public static final String SESSION_RECEIVED_SESSION_ACTOR_MSG = "{} session [{}] received session actor msg {}";
33   - private final ActorSystemContext context;
34 32 private final ActorService service;
35 33 private final ActorRef manager;
36 34 private final ActorRef self;
37 35
38   - public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) {
39   - this.context = context;
40   - this.service = context.getActorService();
  36 + public BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
  37 + this.service = service;
41 38 this.manager = manager;
42 39 this.self = self;
43 40 }
... ... @@ -64,7 +61,6 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
64 61 service.onRecievedMsg(clusterMessage);
65 62 }
66 63
67   -
68 64 @Override
69 65 public void onError(GrpcSession session, Throwable t) {
70 66 log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t);
... ...
... ... @@ -69,7 +69,7 @@ class DefaultTbContext implements TbContext {
69 69 @Override
70 70 public void tellNext(TbMsg msg, String relationType, Throwable th) {
71 71 if (nodeCtx.getSelf().isDebugMode()) {
72   - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
  72 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th);
73 73 }
74 74 nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
75 75 }
... ... @@ -102,7 +102,7 @@ class DefaultTbContext implements TbContext {
102 102 @Override
103 103 public void tellError(TbMsg msg, Throwable th) {
104 104 if (nodeCtx.getSelf().isDebugMode()) {
105   - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
  105 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, "", th);
106 106 }
107 107 nodeCtx.getSelfActor().tell(new RuleNodeToSelfErrorMsg(msg, th), nodeCtx.getSelfActor());
108 108 }
... ...
... ... @@ -96,12 +96,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
96 96 private void reprocess(List<RuleNode> ruleNodeList) {
97 97 for (RuleNode ruleNode : ruleNodeList) {
98 98 for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), systemContext.getQueuePartitionId())) {
99   - pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg);
  99 + pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, "");
100 100 }
101 101 }
102 102 if (firstNode != null) {
103 103 for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), systemContext.getQueuePartitionId())) {
104   - pushMsgToNode(firstNode, tbMsg);
  104 + pushMsgToNode(firstNode, tbMsg, "");
105 105 }
106 106 }
107 107 }
... ... @@ -183,13 +183,13 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
183 183
184 184 void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
185 185 checkActive();
186   - putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg));
  186 + putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, ""));
187 187 }
188 188
189 189 void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
190 190 checkActive();
191 191 putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
192   - pushMsgToNode(firstNode, msg);
  192 + pushMsgToNode(firstNode, msg, "");
193 193 envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
194 194 });
195 195 }
... ... @@ -197,9 +197,9 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
197 197 void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
198 198 checkActive();
199 199 if (envelope.isEnqueue()) {
200   - putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg));
  200 + putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, envelope.getFromRelationType()));
201 201 } else {
202   - pushMsgToNode(firstNode, envelope.getMsg());
  202 + pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType());
203 203 }
204 204 }
205 205
... ... @@ -218,17 +218,17 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
218 218 queue.ack(msg, ackId.getId(), msg.getClusterPartition());
219 219 } else if (relationsCount == 1) {
220 220 for (RuleNodeRelation relation : relations) {
221   - pushToTarget(msg, relation.getOut());
  221 + pushToTarget(msg, relation.getOut(), relation.getType());
222 222 }
223 223 } else {
224 224 for (RuleNodeRelation relation : relations) {
225 225 EntityId target = relation.getOut();
226 226 switch (target.getEntityType()) {
227 227 case RULE_NODE:
228   - enqueueAndForwardMsgCopyToNode(msg, target);
  228 + enqueueAndForwardMsgCopyToNode(msg, target, relation.getType());
229 229 break;
230 230 case RULE_CHAIN:
231   - enqueueAndForwardMsgCopyToChain(msg, target);
  231 + enqueueAndForwardMsgCopyToChain(msg, target, relation.getType());
232 232 break;
233 233 }
234 234 }
... ... @@ -237,33 +237,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
237 237 }
238 238 }
239 239
240   - private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) {
  240 + private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target, String fromRelationType) {
241 241 RuleChainId targetRCId = new RuleChainId(target.getId());
242 242 TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION);
243   - parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, true), self);
  243 + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, fromRelationType, true), self);
244 244 }
245 245
246   - private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) {
  246 + private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target, String fromRelationType) {
247 247 RuleNodeId targetId = new RuleNodeId(target.getId());
248 248 RuleNodeCtx targetNodeCtx = nodeActors.get(targetId);
249 249 TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION);
250   - putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg));
  250 + putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg, fromRelationType));
251 251 }
252 252
253   - private void pushToTarget(TbMsg msg, EntityId target) {
  253 + private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) {
254 254 switch (target.getEntityType()) {
255 255 case RULE_NODE:
256   - pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg);
  256 + pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType);
257 257 break;
258 258 case RULE_CHAIN:
259   - parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self);
  259 + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType, false), self);
260 260 break;
261 261 }
262 262 }
263 263
264   - private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg) {
  264 + private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) {
265 265 if (nodeCtx != null) {
266   - nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg), self);
  266 + nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self);
267 267 }
268 268 }
269 269
... ...
... ... @@ -31,6 +31,7 @@ public final class RuleChainToRuleChainMsg implements TbActorMsg {
31 31 private final RuleChainId target;
32 32 private final RuleChainId source;
33 33 private final TbMsg msg;
  34 + private final String fromRelationType;
34 35 private final boolean enqueue;
35 36
36 37 @Override
... ...
... ... @@ -29,6 +29,7 @@ final class RuleChainToRuleNodeMsg implements TbActorMsg {
29 29
30 30 private final TbContext ctx;
31 31 private final TbMsg msg;
  32 + private final String fromRelationType;
32 33
33 34 @Override
34 35 public MsgType getMsgType() {
... ...
... ... @@ -93,7 +93,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
93 93 public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
94 94 checkActive();
95 95 if (ruleNode.isDebugMode()) {
96   - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
  96 + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self");
97 97 }
98 98 tbNode.onMsg(defaultCtx, msg.getMsg());
99 99 }
... ... @@ -101,7 +101,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
101 101 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
102 102 checkActive();
103 103 if (ruleNode.isDebugMode()) {
104   - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
  104 + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
105 105 }
106 106 tbNode.onMsg(msg.getCtx(), msg.getMsg());
107 107 }
... ...
1 1 /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3   - *
  3 + * <p>
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
  7 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ... @@ -28,7 +28,6 @@ import org.thingsboard.server.actors.app.AppActor;
28 28 import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
29 29 import org.thingsboard.server.actors.rpc.RpcManagerActor;
30 30 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
31   -import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
32 31 import org.thingsboard.server.actors.session.SessionManagerActor;
33 32 import org.thingsboard.server.actors.stats.StatsActor;
34 33 import org.thingsboard.server.common.data.id.*;
... ... @@ -39,14 +38,10 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
39 38 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
40 39 import org.thingsboard.server.common.msg.cluster.ServerAddress;
41 40 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
42   -import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
43 41 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
44 42 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
45   -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
46 43 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
47 44 import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
48   -import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
49   -import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
50 45 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
51 46 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
52 47 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
... ... @@ -59,10 +54,10 @@ import scala.concurrent.duration.Duration;
59 54
60 55 import javax.annotation.PostConstruct;
61 56 import javax.annotation.PreDestroy;
62   -import java.util.Optional;
63 57
  58 +import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE;
  59 +import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE_VALUE;
64 60 import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_NETWORK_SERVER_DATA_MESSAGE;
65   -import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.RPC_BROADCAST_MSG;
66 61
67 62 @Service
68 63 @Slf4j
... ... @@ -157,7 +152,6 @@ public class DefaultActorService implements ActorService {
157 152 }
158 153
159 154
160   -
161 155 @Override
162 156 public void onServerAdded(ServerInstance server) {
163 157 log.trace("Processing onServerAdded msg: {}", server);
... ... @@ -204,8 +198,8 @@ public class DefaultActorService implements ActorService {
204 198 rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage
205 199 .newBuilder()
206 200 .setPayload(ByteString
207   - .copyFrom(actorContext.getEncodingService().encode(msg)))
208   - .setMessageType(CLUSTER_NETWORK_SERVER_DATA_MESSAGE)
  201 + .copyFrom(actorContext.getEncodingService().encode(msg)))
  202 + .setMessageType(CLUSTER_ACTOR_MESSAGE)
209 203 .build()));
210 204 appActor.tell(msg, ActorRef.noSender());
211 205 }
... ... @@ -218,8 +212,9 @@ public class DefaultActorService implements ActorService {
218 212
219 213 @Override
220 214 public void onRecievedMsg(ClusterAPIProtos.ClusterMessage msg) {
221   - switch(msg.getMessageType()) {
222   - case CLUSTER_NETWORK_SERVER_DATA_MESSAGE:
  215 + ServerAddress serverAddress = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort());
  216 + switch (msg.getMessageType()) {
  217 + case CLUSTER_ACTOR_MESSAGE:
223 218 java.util.Optional<TbActorMsg> decodedMsg = actorContext.getEncodingService()
224 219 .decode(msg.getPayload().toByteArray());
225 220 if (decodedMsg.isPresent()) {
... ... @@ -229,7 +224,25 @@ public class DefaultActorService implements ActorService {
229 224 }
230 225 break;
231 226 case TO_ALL_NODES_MSG:
232   - //ToDo
  227 + //TODO
  228 + break;
  229 + case CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE:
  230 + actorContext.getTsSubService().onNewRemoteSubscription(serverAddress, msg.getPayload().toByteArray());
  231 + break;
  232 + case CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE:
  233 + actorContext.getTsSubService().onRemoteSubscriptionUpdate(serverAddress, msg.getPayload().toByteArray());
  234 + break;
  235 + case CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE:
  236 + actorContext.getTsSubService().onRemoteSubscriptionClose(serverAddress, msg.getPayload().toByteArray());
  237 + break;
  238 + case CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE:
  239 + actorContext.getTsSubService().onRemoteSessionClose(serverAddress, msg.getPayload().toByteArray());
  240 + break;
  241 + case CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE:
  242 + actorContext.getTsSubService().onRemoteAttributesUpdate(serverAddress, msg.getPayload().toByteArray());
  243 + break;
  244 + case CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE:
  245 + actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray());
233 246 break;
234 247 }
235 248 }
... ...
... ... @@ -93,7 +93,7 @@ public class AlarmController extends BaseController {
93 93 try {
94 94 AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
95 95 checkAlarmId(alarmId);
96   - alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get();
  96 + alarmService.clearAlarm(alarmId, null, System.currentTimeMillis()).get();
97 97 } catch (Exception e) {
98 98 throw handleException(e);
99 99 }
... ...
... ... @@ -607,7 +607,7 @@ public abstract class BaseController {
607 607 }
608 608
609 609 public static Exception toException(Throwable error) {
610   - return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
  610 + return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
611 611 }
612 612
613 613 }
... ...
... ... @@ -21,14 +21,18 @@ import com.fasterxml.jackson.databind.JsonNode;
21 21 import com.fasterxml.jackson.databind.ObjectMapper;
22 22 import com.fasterxml.jackson.databind.node.ObjectNode;
23 23 import lombok.extern.slf4j.Slf4j;
  24 +import org.springframework.beans.factory.annotation.Autowired;
24 25 import org.springframework.http.HttpStatus;
25 26 import org.springframework.security.access.prepost.PreAuthorize;
26 27 import org.springframework.util.StringUtils;
27 28 import org.springframework.web.bind.annotation.*;
28 29 import org.thingsboard.rule.engine.api.ScriptEngine;
  30 +import org.thingsboard.server.common.data.DataConstants;
29 31 import org.thingsboard.server.common.data.EntityType;
  32 +import org.thingsboard.server.common.data.Event;
30 33 import org.thingsboard.server.common.data.audit.ActionType;
31 34 import org.thingsboard.server.common.data.id.RuleChainId;
  35 +import org.thingsboard.server.common.data.id.RuleNodeId;
32 36 import org.thingsboard.server.common.data.id.TenantId;
33 37 import org.thingsboard.server.common.data.page.TextPageData;
34 38 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -38,6 +42,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
38 42 import org.thingsboard.server.common.data.security.Authority;
39 43 import org.thingsboard.server.common.msg.TbMsg;
40 44 import org.thingsboard.server.common.msg.TbMsgMetaData;
  45 +import org.thingsboard.server.dao.event.EventService;
41 46 import org.thingsboard.server.dao.model.ModelConstants;
42 47 import org.thingsboard.server.common.data.exception.ThingsboardException;
43 48 import org.thingsboard.server.service.script.NashornJsEngine;
... ... @@ -52,9 +57,13 @@ import java.util.Set;
52 57 public class RuleChainController extends BaseController {
53 58
54 59 public static final String RULE_CHAIN_ID = "ruleChainId";
  60 + public static final String RULE_NODE_ID = "ruleNodeId";
55 61
56 62 private static final ObjectMapper objectMapper = new ObjectMapper();
57 63
  64 + @Autowired
  65 + private EventService eventService;
  66 +
58 67 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
59 68 @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
60 69 @ResponseBody
... ... @@ -217,6 +226,31 @@ public class RuleChainController extends BaseController {
217 226 }
218 227 }
219 228
  229 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  230 + @RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET)
  231 + @ResponseBody
  232 + public JsonNode getLatestRuleNodeDebugInput(@PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException {
  233 + checkParameter(RULE_NODE_ID, strRuleNodeId);
  234 + try {
  235 + RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId));
  236 + TenantId tenantId = getCurrentUser().getTenantId();
  237 + List<Event> events = eventService.findLatestEvents(tenantId, ruleNodeId, DataConstants.DEBUG_RULE_NODE, 2);
  238 + JsonNode result = null;
  239 + if (events != null) {
  240 + for (Event event : events) {
  241 + JsonNode body = event.getBody();
  242 + if (body.has("type") && body.get("type").asText().equals("IN")) {
  243 + result = body;
  244 + break;
  245 + }
  246 + }
  247 + }
  248 + return result;
  249 + } catch (Exception e) {
  250 + throw handleException(e);
  251 + }
  252 + }
  253 +
220 254 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
221 255 @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
222 256 @ResponseBody
... ...
1 1 /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3   - *
  3 + * <p>
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
  7 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ... @@ -26,13 +26,18 @@ import org.springframework.util.SerializationUtils;
26 26 import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
27 27 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
28 28 import org.thingsboard.server.common.data.id.EntityId;
  29 +import org.thingsboard.server.common.msg.TbActorMsg;
  30 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
29 31 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
30 32
  33 +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
  34 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
31 35 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
32 36
33 37 import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
34 38 import org.thingsboard.server.service.cluster.discovery.ServerInstance;
35 39 import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
  40 +import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
36 41
37 42 import javax.annotation.PreDestroy;
38 43 import java.io.IOException;
... ... @@ -52,6 +57,9 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
52 57 @Autowired
53 58 private ServerInstanceService instanceService;
54 59
  60 + @Autowired
  61 + private DataDecodingEncodingService encodingService;
  62 +
55 63 private RpcMsgListener listener;
56 64
57 65 private Server server;
... ... @@ -77,8 +85,8 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
77 85 }
78 86
79 87 @Override
80   - public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) {
81   - BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid);
  88 + public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) {
  89 + BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid);
82 90 if (queue != null) {
83 91 try {
84 92 queue.put(inputStream);
... ... @@ -120,7 +128,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
120 128 listener.onBroadcastMsg(msg);
121 129 }
122 130
123   - private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) {
  131 + private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) {
124 132 BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = new ArrayBlockingQueue<>(1);
125 133 pendingSessionMap.put(msg.getMsgUid(), queue);
126 134 listener.onRpcSessionCreateRequestMsg(msg);
... ... @@ -139,5 +147,22 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
139 147 listener.onSendMsg(message);
140 148 }
141 149
  150 + @Override
  151 + public void tell(ServerAddress serverAddress, TbActorMsg actorMsg) {
  152 + listener.onSendMsg(encodingService.convertToProtoDataMessage(serverAddress, actorMsg));
  153 + }
142 154
  155 + @Override
  156 + public void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data) {
  157 + ClusterAPIProtos.ClusterMessage msg = ClusterAPIProtos.ClusterMessage
  158 + .newBuilder()
  159 + .setServerAddress(ClusterAPIProtos.ServerAddress
  160 + .newBuilder()
  161 + .setHost(serverAddress.getHost())
  162 + .setPort(serverAddress.getPort())
  163 + .build())
  164 + .setMessageType(msgType)
  165 + .setPayload(ByteString.copyFrom(data)).build();
  166 + listener.onSendMsg(msg);
  167 + }
143 168 }
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
25 25 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
26 26 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
27 27 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
  28 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
28 29 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
29 30 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
30 31
... ... @@ -45,4 +46,5 @@ public interface ClusterRpcService {
45 46
46 47 void tell(ServerAddress serverAddress, TbActorMsg actorMsg);
47 48
  49 + void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data);
48 50 }
... ...
1 1 /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3   - *
  3 + * <p>
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
  7 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ... @@ -18,30 +18,40 @@ package org.thingsboard.server.service.telemetry;
18 18 import com.google.common.util.concurrent.FutureCallback;
19 19 import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
  21 +import com.google.protobuf.InvalidProtocolBufferException;
21 22 import lombok.extern.slf4j.Slf4j;
22 23 import org.springframework.beans.factory.annotation.Autowired;
23 24 import org.springframework.context.annotation.Lazy;
24 25 import org.springframework.stereotype.Service;
25 26 import org.springframework.util.StringUtils;
  27 +import org.thingsboard.rule.engine.DonAsynchron;
26 28 import org.thingsboard.server.common.data.DataConstants;
27 29 import org.thingsboard.server.common.data.EntityType;
28 30 import org.thingsboard.server.common.data.id.DeviceId;
29 31 import org.thingsboard.server.common.data.id.EntityId;
  32 +import org.thingsboard.server.common.data.id.EntityIdFactory;
30 33 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
31 34 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  35 +import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
32 36 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
33 37 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
  38 +import org.thingsboard.server.common.data.kv.DataType;
34 39 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
  40 +import org.thingsboard.server.common.data.kv.KvEntry;
35 41 import org.thingsboard.server.common.data.kv.LongDataEntry;
36 42 import org.thingsboard.server.common.data.kv.StringDataEntry;
37 43 import org.thingsboard.server.common.data.kv.TsKvEntry;
  44 +import org.thingsboard.server.common.data.kv.TsKvQuery;
38 45 import org.thingsboard.server.common.msg.cluster.ServerAddress;
39 46 import org.thingsboard.server.dao.attributes.AttributesService;
40 47 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  48 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
41 49 import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryFeature;
42 50 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
  51 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionErrorCode;
43 52 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
44 53 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
  54 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
45 55 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
46 56 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
47 57 import org.thingsboard.server.service.state.DefaultDeviceStateService;
... ... @@ -54,15 +64,18 @@ import java.util.ArrayList;
54 64 import java.util.Collections;
55 65 import java.util.HashMap;
56 66 import java.util.HashSet;
  67 +import java.util.Iterator;
57 68 import java.util.List;
58 69 import java.util.Map;
59 70 import java.util.Optional;
60 71 import java.util.Set;
  72 +import java.util.TreeMap;
61 73 import java.util.concurrent.ExecutorService;
62 74 import java.util.concurrent.Executors;
63 75 import java.util.function.Consumer;
64 76 import java.util.function.Function;
65 77 import java.util.function.Predicate;
  78 +import java.util.stream.Collectors;
66 79
67 80 /**
68 81 * Created by ashvayka on 27.03.18.
... ... @@ -120,8 +133,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
120 133 ServerAddress address = server.get();
121 134 log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
122 135 subscription = new Subscription(sub, true, address);
123   -// rpcService.tell();
124   -// rpcHandler.onNewSubscription(ctx, address, sessionId, subscription);
  136 + tellNewSubscription(address, sessionId, subscription);
125 137 } else {
126 138 log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
127 139 subscription = new Subscription(sub, true);
... ... @@ -193,6 +205,174 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
193 205 , System.currentTimeMillis())), callback);
194 206 }
195 207
  208 + @Override
  209 + public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) {
  210 + ClusterAPIProtos.SubscriptionProto proto;
  211 + try {
  212 + proto = ClusterAPIProtos.SubscriptionProto.parseFrom(data);
  213 + } catch (InvalidProtocolBufferException e) {
  214 + throw new RuntimeException(e);
  215 + }
  216 + Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect(
  217 + Collectors.toMap(ClusterAPIProtos.SubscriptionKetStateProto::getKey, ClusterAPIProtos.SubscriptionKetStateProto::getTs));
  218 + Subscription subscription = new Subscription(
  219 + new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(),
  220 + EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()),
  221 + TelemetryFeature.valueOf(proto.getType()), proto.getAllKeys(), statesMap, proto.getScope()),
  222 + false, new ServerAddress(serverAddress.getHost(), serverAddress.getPort()));
  223 +
  224 + addRemoteWsSubscription(serverAddress, proto.getSessionId(), subscription);
  225 + }
  226 +
  227 + @Override
  228 + public void onRemoteSubscriptionUpdate(ServerAddress serverAddress, byte[] data) {
  229 + ClusterAPIProtos.SubscriptionUpdateProto proto;
  230 + try {
  231 + proto = ClusterAPIProtos.SubscriptionUpdateProto.parseFrom(data);
  232 + } catch (InvalidProtocolBufferException e) {
  233 + throw new RuntimeException(e);
  234 + }
  235 + SubscriptionUpdate update = convert(proto);
  236 + String sessionId = proto.getSessionId();
  237 + log.trace("[{}] Processing remote subscription onUpdate [{}]", sessionId, update);
  238 + Optional<Subscription> subOpt = getSubscription(sessionId, update.getSubscriptionId());
  239 + if (subOpt.isPresent()) {
  240 + updateSubscriptionState(sessionId, subOpt.get(), update);
  241 + wsService.sendWsMsg(sessionId, update);
  242 + }
  243 + }
  244 +
  245 + @Override
  246 + public void onRemoteSubscriptionClose(ServerAddress serverAddress, byte[] data) {
  247 + ClusterAPIProtos.SubscriptionCloseProto proto;
  248 + try {
  249 + proto = ClusterAPIProtos.SubscriptionCloseProto.parseFrom(data);
  250 + } catch (InvalidProtocolBufferException e) {
  251 + throw new RuntimeException(e);
  252 + }
  253 + removeSubscription(proto.getSessionId(), proto.getSubscriptionId());
  254 + }
  255 +
  256 + @Override
  257 + public void onRemoteSessionClose(ServerAddress serverAddress, byte[] data) {
  258 + ClusterAPIProtos.SessionCloseProto proto;
  259 + try {
  260 + proto = ClusterAPIProtos.SessionCloseProto.parseFrom(data);
  261 + } catch (InvalidProtocolBufferException e) {
  262 + throw new RuntimeException(e);
  263 + }
  264 + cleanupRemoteWsSessionSubscriptions(proto.getSessionId());
  265 + }
  266 +
  267 + @Override
  268 + public void onRemoteAttributesUpdate(ServerAddress serverAddress, byte[] data) {
  269 + ClusterAPIProtos.AttributeUpdateProto proto;
  270 + try {
  271 + proto = ClusterAPIProtos.AttributeUpdateProto.parseFrom(data);
  272 + } catch (InvalidProtocolBufferException e) {
  273 + throw new RuntimeException(e);
  274 + }
  275 + onAttributesUpdate(EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), proto.getScope(),
  276 + proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList()));
  277 + }
  278 +
  279 + @Override
  280 + public void onRemoteTsUpdate(ServerAddress serverAddress, byte[] data) {
  281 + ClusterAPIProtos.TimeseriesUpdateProto proto;
  282 + try {
  283 + proto = ClusterAPIProtos.TimeseriesUpdateProto.parseFrom(data);
  284 + } catch (InvalidProtocolBufferException e) {
  285 + throw new RuntimeException(e);
  286 + }
  287 + onTimeseriesUpdate(EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()),
  288 + proto.getDataList().stream().map(this::toTimeseries).collect(Collectors.toList()));
  289 + }
  290 +
  291 + @Override
  292 + public void onClusterUpdate() {
  293 + log.trace("Processing cluster onUpdate msg!");
  294 + Iterator<Map.Entry<EntityId, Set<Subscription>>> deviceIterator = subscriptionsByEntityId.entrySet().iterator();
  295 + while (deviceIterator.hasNext()) {
  296 + Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next();
  297 + Set<Subscription> subscriptions = e.getValue();
  298 + Optional<ServerAddress> newAddressOptional = routingService.resolveById(e.getKey());
  299 + if (newAddressOptional.isPresent()) {
  300 + newAddressOptional.ifPresent(serverAddress -> checkSubsciptionsNewAddress(serverAddress, subscriptions));
  301 + } else {
  302 + checkSubsciptionsPrevAddress(subscriptions);
  303 + }
  304 + if (subscriptions.size() == 0) {
  305 + log.trace("[{}] No more subscriptions for this device on current server.", e.getKey());
  306 + deviceIterator.remove();
  307 + }
  308 + }
  309 + }
  310 +
  311 + private void checkSubsciptionsNewAddress(ServerAddress newAddress, Set<Subscription> subscriptions) {
  312 + Iterator<Subscription> subscriptionIterator = subscriptions.iterator();
  313 + while (subscriptionIterator.hasNext()) {
  314 + Subscription s = subscriptionIterator.next();
  315 + if (s.isLocal()) {
  316 + if (!newAddress.equals(s.getServer())) {
  317 + log.trace("[{}] Local subscription is now handled on new server [{}]", s.getWsSessionId(), newAddress);
  318 + s.setServer(newAddress);
  319 + tellNewSubscription(newAddress, s.getWsSessionId(), s);
  320 + }
  321 + } else {
  322 + log.trace("[{}] Remote subscription is now handled on new server address: [{}]", s.getWsSessionId(), newAddress);
  323 + subscriptionIterator.remove();
  324 + //TODO: onUpdate state of subscription by WsSessionId and other maps.
  325 + }
  326 + }
  327 + }
  328 +
  329 + private void checkSubsciptionsPrevAddress(Set<Subscription> subscriptions) {
  330 + for (Subscription s : subscriptions) {
  331 + if (s.isLocal() && s.getServer() != null) {
  332 + log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer());
  333 + s.setServer(null);
  334 + } else {
  335 + log.trace("[{}] Remote subscription is on up to date server address.", s.getWsSessionId());
  336 + }
  337 + }
  338 + }
  339 +
  340 + private void addRemoteWsSubscription(ServerAddress address, String sessionId, Subscription subscription) {
  341 + EntityId entityId = subscription.getEntityId();
  342 + log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address);
  343 + registerSubscription(sessionId, entityId, subscription);
  344 + if (subscription.getType() == TelemetryFeature.ATTRIBUTES) {
  345 + final Map<String, Long> keyStates = subscription.getKeyStates();
  346 + DonAsynchron.withCallback(attrService.find(entityId, DataConstants.CLIENT_SCOPE, keyStates.keySet()), values -> {
  347 + List<TsKvEntry> missedUpdates = new ArrayList<>();
  348 + values.forEach(latestEntry -> {
  349 + if (latestEntry.getLastUpdateTs() > keyStates.get(latestEntry.getKey())) {
  350 + missedUpdates.add(new BasicTsKvEntry(latestEntry.getLastUpdateTs(), latestEntry));
  351 + }
  352 + });
  353 + if (!missedUpdates.isEmpty()) {
  354 + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
  355 + }
  356 + },
  357 + e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
  358 + } else if (subscription.getType() == TelemetryFeature.TIMESERIES) {
  359 + long curTs = System.currentTimeMillis();
  360 + List<TsKvQuery> queries = new ArrayList<>();
  361 + subscription.getKeyStates().entrySet().forEach(e -> {
  362 + queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
  363 + });
  364 +
  365 + DonAsynchron.withCallback(tsService.findAll(entityId, queries),
  366 + missedUpdates -> {
  367 + if (!missedUpdates.isEmpty()) {
  368 + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
  369 + }
  370 + },
  371 + e -> log.error("Failed to fetch missed updates.", e),
  372 + tsCallBackExecutor);
  373 + }
  374 + }
  375 +
196 376 private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
197 377 Optional<ServerAddress> serverAddress = routingService.resolveById(entityId);
198 378 if (!serverAddress.isPresent()) {
... ... @@ -205,7 +385,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
205 385 }
206 386 }
207 387 } else {
208   -// rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries);
  388 + tellRemoteAttributesUpdate(serverAddress.get(), entityId, scope, attributes);
209 389 }
210 390 }
211 391
... ... @@ -214,7 +394,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
214 394 if (!serverAddress.isPresent()) {
215 395 onLocalTimeseriesUpdate(entityId, ts);
216 396 } else {
217   -// rpcHandler.onTimeseriesUpdate(ctx, serverAddress.get(), entityId, entries);
  397 + tellRemoteTimeseriesUpdate(serverAddress.get(), entityId, ts);
218 398 }
219 399 }
220 400
... ... @@ -260,8 +440,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
260 440 updateSubscriptionState(sessionId, s, update);
261 441 wsService.sendWsMsg(sessionId, update);
262 442 } else {
263   - //TODO: ashvayka
264   -// rpcHandler.onSubscriptionUpdate(ctx, s.getServer(), sessionId, update);
  443 + tellRemoteSubUpdate(s.getServer(), sessionId, update);
265 444 }
266 445 }
267 446 });
... ... @@ -282,11 +461,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
282 461 sessionSubscriptions.put(subscription.getSubscriptionId(), subscription);
283 462 }
284 463
285   - public void cleanupLocalWsSessionSubscriptions(String sessionId) {
  464 + private void cleanupLocalWsSessionSubscriptions(String sessionId) {
286 465 cleanupWsSessionSubscriptions(sessionId, true);
287 466 }
288 467
289   - public void cleanupRemoteWsSessionSubscriptions(String sessionId) {
  468 + private void cleanupRemoteWsSessionSubscriptions(String sessionId) {
290 469 cleanupWsSessionSubscriptions(sessionId, false);
291 470 }
292 471
... ... @@ -324,14 +503,14 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
324 503 }
325 504 for (ServerAddress address : affectedServers) {
326 505 log.debug("[{}] Going to onSubscriptionUpdate [{}] server about session close event", sessionId, address);
327   -// rpcHandler.onSessionClose(ctx, address, sessionId);
  506 + tellRemoteSessionClose(address, sessionId);
328 507 }
329 508 }
330 509
331 510 private void processSubscriptionRemoval(String sessionId, Map<Integer, Subscription> sessionSubscriptions, Subscription subscription) {
332 511 EntityId entityId = subscription.getEntityId();
333 512 if (subscription.isLocal() && subscription.getServer() != null) {
334   -// rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId());
  513 + tellRemoteSubClose(subscription.getServer(), sessionId, subscription.getSubscriptionId());
335 514 }
336 515 if (sessionSubscriptions.isEmpty()) {
337 516 log.debug("[{}] Removed last subscription for particular session.", sessionId);
... ... @@ -383,4 +562,151 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
383 562 }
384 563 }, wsCallBackExecutor);
385 564 }
  565 +
  566 + private void tellNewSubscription(ServerAddress address, String sessionId, Subscription sub) {
  567 + ClusterAPIProtos.SubscriptionProto.Builder builder = ClusterAPIProtos.SubscriptionProto.newBuilder();
  568 + builder.setSessionId(sessionId);
  569 + builder.setSubscriptionId(sub.getSubscriptionId());
  570 + builder.setEntityType(sub.getEntityId().getEntityType().name());
  571 + builder.setEntityId(sub.getEntityId().getId().toString());
  572 + builder.setType(sub.getType().name());
  573 + builder.setAllKeys(sub.isAllKeys());
  574 + builder.setScope(sub.getScope());
  575 + sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(
  576 + ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
  577 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray());
  578 + }
  579 +
  580 + private void tellRemoteSubUpdate(ServerAddress address, String sessionId, SubscriptionUpdate update) {
  581 + ClusterAPIProtos.SubscriptionUpdateProto.Builder builder = ClusterAPIProtos.SubscriptionUpdateProto.newBuilder();
  582 + builder.setSessionId(sessionId);
  583 + builder.setSubscriptionId(update.getSubscriptionId());
  584 + builder.setErrorCode(update.getErrorCode());
  585 + if (update.getErrorMsg() != null) {
  586 + builder.setErrorMsg(update.getErrorMsg());
  587 + }
  588 + update.getData().entrySet().forEach(
  589 + e -> {
  590 + ClusterAPIProtos.SubscriptionUpdateValueListProto.Builder dataBuilder = ClusterAPIProtos.SubscriptionUpdateValueListProto.newBuilder();
  591 +
  592 + dataBuilder.setKey(e.getKey());
  593 + e.getValue().forEach(v -> {
  594 + Object[] array = (Object[]) v;
  595 + dataBuilder.addTs((long) array[0]);
  596 + dataBuilder.addValue((String) array[1]);
  597 + });
  598 +
  599 + builder.addData(dataBuilder.build());
  600 + }
  601 + );
  602 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE, builder.build().toByteArray());
  603 + }
  604 +
  605 + private void tellRemoteAttributesUpdate(ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
  606 + ClusterAPIProtos.AttributeUpdateProto.Builder builder = ClusterAPIProtos.AttributeUpdateProto.newBuilder();
  607 + builder.setEntityId(entityId.getId().toString());
  608 + builder.setEntityType(entityId.getEntityType().name());
  609 + builder.setScope(scope);
  610 + attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build()));
  611 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE, builder.build().toByteArray());
  612 + }
  613 +
  614 + private void tellRemoteTimeseriesUpdate(ServerAddress address, EntityId entityId, List<TsKvEntry> ts) {
  615 + ClusterAPIProtos.TimeseriesUpdateProto.Builder builder = ClusterAPIProtos.TimeseriesUpdateProto.newBuilder();
  616 + builder.setEntityId(entityId.getId().toString());
  617 + builder.setEntityType(entityId.getEntityType().name());
  618 + ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build()));
  619 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE, builder.build().toByteArray());
  620 + }
  621 +
  622 + private void tellRemoteSessionClose(ServerAddress address, String sessionId) {
  623 + ClusterAPIProtos.SessionCloseProto proto = ClusterAPIProtos.SessionCloseProto.newBuilder().setSessionId(sessionId).build();
  624 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE, proto.toByteArray());
  625 + }
  626 +
  627 + private void tellRemoteSubClose(ServerAddress address, String sessionId, int subscriptionId) {
  628 + ClusterAPIProtos.SubscriptionCloseProto proto = ClusterAPIProtos.SubscriptionCloseProto.newBuilder().setSessionId(sessionId).setSubscriptionId(subscriptionId).build();
  629 + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE, proto.toByteArray());
  630 + }
  631 +
  632 + private ClusterAPIProtos.KeyValueProto.Builder toKeyValueProto(long ts, KvEntry attr) {
  633 + ClusterAPIProtos.KeyValueProto.Builder dataBuilder = ClusterAPIProtos.KeyValueProto.newBuilder();
  634 + dataBuilder.setKey(attr.getKey());
  635 + dataBuilder.setTs(ts);
  636 + dataBuilder.setValueType(attr.getDataType().ordinal());
  637 + switch (attr.getDataType()) {
  638 + case BOOLEAN:
  639 + Optional<Boolean> booleanValue = attr.getBooleanValue();
  640 + booleanValue.ifPresent(dataBuilder::setBoolValue);
  641 + break;
  642 + case LONG:
  643 + Optional<Long> longValue = attr.getLongValue();
  644 + longValue.ifPresent(dataBuilder::setLongValue);
  645 + break;
  646 + case DOUBLE:
  647 + Optional<Double> doubleValue = attr.getDoubleValue();
  648 + doubleValue.ifPresent(dataBuilder::setDoubleValue);
  649 + break;
  650 + case STRING:
  651 + Optional<String> stringValue = attr.getStrValue();
  652 + stringValue.ifPresent(dataBuilder::setStrValue);
  653 + break;
  654 + }
  655 + return dataBuilder;
  656 + }
  657 +
  658 + private AttributeKvEntry toAttribute(ClusterAPIProtos.KeyValueProto proto) {
  659 + return new BaseAttributeKvEntry(getKvEntry(proto), proto.getTs());
  660 + }
  661 +
  662 + private TsKvEntry toTimeseries(ClusterAPIProtos.KeyValueProto proto) {
  663 + return new BasicTsKvEntry(proto.getTs(), getKvEntry(proto));
  664 + }
  665 +
  666 + private KvEntry getKvEntry(ClusterAPIProtos.KeyValueProto proto) {
  667 + KvEntry entry = null;
  668 + DataType type = DataType.values()[proto.getValueType()];
  669 + switch (type) {
  670 + case BOOLEAN:
  671 + entry = new BooleanDataEntry(proto.getKey(), proto.getBoolValue());
  672 + break;
  673 + case LONG:
  674 + entry = new LongDataEntry(proto.getKey(), proto.getLongValue());
  675 + break;
  676 + case DOUBLE:
  677 + entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleValue());
  678 + break;
  679 + case STRING:
  680 + entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
  681 + break;
  682 + }
  683 + return entry;
  684 + }
  685 +
  686 + private SubscriptionUpdate convert(ClusterAPIProtos.SubscriptionUpdateProto proto) {
  687 + if (proto.getErrorCode() > 0) {
  688 + return new SubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
  689 + } else {
  690 + Map<String, List<Object>> data = new TreeMap<>();
  691 + proto.getDataList().forEach(v -> {
  692 + List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>());
  693 + for (int i = 0; i < v.getTsCount(); i++) {
  694 + Object[] value = new Object[2];
  695 + value[0] = v.getTs(i);
  696 + value[1] = v.getValue(i);
  697 + values.add(value);
  698 + }
  699 + });
  700 + return new SubscriptionUpdate(proto.getSubscriptionId(), data);
  701 + }
  702 + }
  703 +
  704 + private Optional<Subscription> getSubscription(String sessionId, int subscriptionId) {
  705 + Subscription state = null;
  706 + Map<Integer, Subscription> subMap = subscriptionsByWsSessionId.get(sessionId);
  707 + if (subMap != null) {
  708 + state = subMap.get(subscriptionId);
  709 + }
  710 + return Optional.ofNullable(state);
  711 + }
386 712 }
... ...
... ... @@ -17,7 +17,10 @@ package org.thingsboard.server.service.telemetry;
17 17
18 18 import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
19 19 import org.thingsboard.server.common.data.id.EntityId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  21 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
20 22 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
  23 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
21 24
22 25 /**
23 26 * Created by ashvayka on 27.03.18.
... ... @@ -30,4 +33,17 @@ public interface TelemetrySubscriptionService extends RuleEngineTelemetryService
30 33
31 34 void removeSubscription(String sessionId, int cmdId);
32 35
  36 + void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data);
  37 +
  38 + void onRemoteSubscriptionUpdate(ServerAddress serverAddress, byte[] bytes);
  39 +
  40 + void onRemoteSubscriptionClose(ServerAddress serverAddress, byte[] bytes);
  41 +
  42 + void onRemoteSessionClose(ServerAddress serverAddress, byte[] bytes);
  43 +
  44 + void onRemoteAttributesUpdate(ServerAddress serverAddress, byte[] bytes);
  45 +
  46 + void onRemoteTsUpdate(ServerAddress serverAddress, byte[] bytes);
  47 +
  48 + void onClusterUpdate();
33 49 }
... ...
... ... @@ -25,7 +25,7 @@ service ClusterRpcService {
25 25 message ClusterMessage {
26 26 MessageType messageType = 1;
27 27 MessageMataInfo messageMetaInfo = 2;
28   - ServerAddress serverAdresss = 3;
  28 + ServerAddress serverAddress = 3;
29 29 bytes payload = 4;
30 30 }
31 31
... ... @@ -49,6 +49,75 @@ enum MessageType {
49 49 CONNECT_RPC_MESSAGE =4;
50 50
51 51 CLUSTER_ACTOR_MESSAGE = 5;
52   - CLUSTER_TELEMETRY_MESSAGE = 6;
53   - CLUSTER_DEVICE_RPC_MESSAGE = 7;
  52 + // Messages related to TelemetrySubscriptionService
  53 + CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE = 6;
  54 + CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE = 7;
  55 + CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE = 8;
  56 + CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE = 9;
  57 + CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE = 10;
  58 + CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE = 11;
54 59 }
  60 +
  61 +// Messages related to CLUSTER_TELEMETRY_MESSAGE
  62 +message SubscriptionProto {
  63 + string sessionId = 1;
  64 + int32 subscriptionId = 2;
  65 + string entityType = 3;
  66 + string entityId = 4;
  67 + string type = 5;
  68 + bool allKeys = 6;
  69 + repeated SubscriptionKetStateProto keyStates = 7;
  70 + string scope = 8;
  71 +}
  72 +
  73 +message SubscriptionUpdateProto {
  74 + string sessionId = 1;
  75 + int32 subscriptionId = 2;
  76 + int32 errorCode = 3;
  77 + string errorMsg = 4;
  78 + repeated SubscriptionUpdateValueListProto data = 5;
  79 +}
  80 +
  81 +message AttributeUpdateProto {
  82 + string entityType = 1;
  83 + string entityId = 2;
  84 + string scope = 3;
  85 + repeated KeyValueProto data = 4;
  86 +}
  87 +
  88 +message TimeseriesUpdateProto {
  89 + string entityType = 1;
  90 + string entityId = 2;
  91 + repeated KeyValueProto data = 4;
  92 +}
  93 +
  94 +message SessionCloseProto {
  95 + string sessionId = 1;
  96 +}
  97 +
  98 +message SubscriptionCloseProto {
  99 + string sessionId = 1;
  100 + int32 subscriptionId = 2;
  101 +}
  102 +
  103 +message SubscriptionKetStateProto {
  104 + string key = 1;
  105 + int64 ts = 2;
  106 +}
  107 +
  108 +message SubscriptionUpdateValueListProto {
  109 + string key = 1;
  110 + repeated int64 ts = 2;
  111 + repeated string value = 3;
  112 +}
  113 +
  114 +message KeyValueProto {
  115 + string key = 1;
  116 + int64 ts = 2;
  117 + int32 valueType = 3;
  118 + string strValue = 4;
  119 + int64 longValue = 5;
  120 + double doubleValue = 6;
  121 + bool boolValue = 7;
  122 +}
  123 +
... ...
... ... @@ -36,9 +36,16 @@ public class JsonConverter {
36 36 return convertToTelemetry(jsonObject, BasicRequest.DEFAULT_REQUEST_ID);
37 37 }
38 38
  39 + public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long ts) throws JsonSyntaxException {
  40 + return convertToTelemetry(jsonObject, ts, BasicRequest.DEFAULT_REQUEST_ID);
  41 + }
  42 +
39 43 public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, int requestId) throws JsonSyntaxException {
  44 + return convertToTelemetry(jsonObject, System.currentTimeMillis(), requestId);
  45 + }
  46 +
  47 + private static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long systemTs, int requestId) throws JsonSyntaxException {
40 48 BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId);
41   - long systemTs = System.currentTimeMillis();
42 49 if (jsonObject.isJsonObject()) {
43 50 parseObject(request, systemTs, jsonObject);
44 51 } else if (jsonObject.isJsonArray()) {
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.alarm;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
18 19 import com.google.common.util.concurrent.ListenableFuture;
19 20 import org.thingsboard.server.common.data.alarm.*;
20 21 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -30,7 +31,7 @@ public interface AlarmService {
30 31
31 32 ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs);
32 33
33   - ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
  34 + ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, JsonNode details, long ackTs);
34 35
35 36 ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
36 37
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.alarm;
17 17
18 18
  19 +import com.fasterxml.jackson.databind.JsonNode;
19 20 import com.google.common.base.Function;
20 21 import com.google.common.util.concurrent.AsyncFunction;
21 22 import com.google.common.util.concurrent.Futures;
... ... @@ -187,7 +188,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
187 188 }
188 189
189 190 @Override
190   - public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) {
  191 + public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, JsonNode details, long clearTime) {
191 192 return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
192 193 @Nullable
193 194 @Override
... ... @@ -199,6 +200,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
199 200 AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK;
200 201 alarm.setStatus(newStatus);
201 202 alarm.setClearTs(clearTime);
  203 + if (details != null) {
  204 + alarm.setDetails(details);
  205 + }
202 206 alarmDao.save(alarm);
203 207 updateRelations(alarm, oldStatus, newStatus);
204 208 return true;
... ...
... ... @@ -82,6 +82,11 @@ public class BaseEventService implements EventService {
82 82 return new TimePageData<>(events, pageLink);
83 83 }
84 84
  85 + @Override
  86 + public List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit) {
  87 + return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit);
  88 + }
  89 +
85 90 private DataValidator<Event> eventValidator =
86 91 new DataValidator<Event>() {
87 92 @Override
... ...
... ... @@ -134,6 +134,21 @@ public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao<EventE
134 134 return DaoUtil.convertDataList(entities);
135 135 }
136 136
  137 + @Override
  138 + public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) {
  139 + log.trace("Try to find latest events by tenant [{}], entity [{}], type [{}] and limit [{}]", tenantId, entityId, eventType, limit);
  140 + Select select = select().from(EVENT_BY_TYPE_AND_ID_VIEW_NAME);
  141 + Select.Where query = select.where();
  142 + query.and(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId));
  143 + query.and(eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()));
  144 + query.and(eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId()));
  145 + query.and(eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType));
  146 + query.limit(limit);
  147 + query.orderBy(QueryBuilder.desc(ModelConstants.EVENT_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY));
  148 + List<EventEntity> entities = findListByStatement(query);
  149 + return DaoUtil.convertDataList(entities);
  150 + }
  151 +
137 152 private Optional<Event> save(EventEntity entity, boolean ifNotExists) {
138 153 if (entity.getId() == null) {
139 154 entity.setId(UUIDs.timeBased());
... ...
... ... @@ -76,4 +76,16 @@ public interface EventDao extends Dao<Event> {
76 76 * @return the event list
77 77 */
78 78 List<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
  79 +
  80 + /**
  81 + * Find latest events by tenantId, entityId and eventType.
  82 + *
  83 + * @param tenantId the tenantId
  84 + * @param entityId the entityId
  85 + * @param eventType the eventType
  86 + * @param limit the limit
  87 + * @return the event list
  88 + */
  89 + List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit);
  90 +
79 91 }
... ...
... ... @@ -21,7 +21,9 @@ import org.thingsboard.server.common.data.id.TenantId;
21 21 import org.thingsboard.server.common.data.page.TimePageData;
22 22 import org.thingsboard.server.common.data.page.TimePageLink;
23 23
  24 +import java.util.List;
24 25 import java.util.Optional;
  26 +import java.util.UUID;
25 27
26 28 public interface EventService {
27 29
... ... @@ -34,4 +36,7 @@ public interface EventService {
34 36 TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
35 37
36 38 TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
  39 +
  40 + List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
  41 +
37 42 }
... ...
... ... @@ -15,12 +15,18 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.event;
17 17
  18 +import org.springframework.data.domain.Pageable;
18 19 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
  20 +import org.springframework.data.jpa.repository.Query;
19 21 import org.springframework.data.repository.CrudRepository;
  22 +import org.springframework.data.repository.query.Param;
20 23 import org.thingsboard.server.common.data.EntityType;
  24 +import org.thingsboard.server.dao.model.sql.AlarmEntity;
21 25 import org.thingsboard.server.dao.model.sql.EventEntity;
22 26 import org.thingsboard.server.dao.util.SqlDao;
23 27
  28 +import java.util.List;
  29 +
24 30 /**
25 31 * Created by Valerii Sosliuk on 5/3/2017.
26 32 */
... ... @@ -36,4 +42,14 @@ public interface EventRepository extends CrudRepository<EventEntity, String>, Jp
36 42 EventEntity findByTenantIdAndEntityTypeAndEntityId(String tenantId,
37 43 EntityType entityType,
38 44 String entityId);
  45 +
  46 + @Query("SELECT e FROM EventEntity e WHERE e.tenantId = :tenantId AND e.entityType = :entityType " +
  47 + "AND e.entityId = :entityId AND e.eventType = :eventType ORDER BY e.eventType DESC, e.id DESC")
  48 + List<EventEntity> findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
  49 + @Param("tenantId") String tenantId,
  50 + @Param("entityType") EntityType entityType,
  51 + @Param("entityId") String entityId,
  52 + @Param("eventType") String eventType,
  53 + Pageable pageable);
  54 +
39 55 }
... ...
... ... @@ -109,6 +109,17 @@ public class JpaBaseEventDao extends JpaAbstractSearchTimeDao<EventEntity, Event
109 109 return DaoUtil.convertDataList(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent());
110 110 }
111 111
  112 + @Override
  113 + public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) {
  114 + List<EventEntity> latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
  115 + UUIDConverter.fromTimeUUID(tenantId),
  116 + entityId.getEntityType(),
  117 + UUIDConverter.fromTimeUUID(entityId.getId()),
  118 + eventType,
  119 + new PageRequest(0, limit));
  120 + return DaoUtil.convertDataList(latest);
  121 + }
  122 +
112 123 public Optional<Event> save(EventEntity entity, boolean ifNotExists) {
113 124 log.debug("Save event [{}] ", entity);
114 125 if (entity.getTenantId() == null) {
... ...
... ... @@ -168,7 +168,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
168 168 Assert.assertNotNull(alarms.getData());
169 169 Assert.assertEquals(0, alarms.getData().size());
170 170
171   - alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get();
  171 + alarmService.clearAlarm(created.getId(), null, System.currentTimeMillis()).get();
172 172 created = alarmService.findAlarmByIdAsync(created.getId()).get();
173 173
174 174 alarms = alarmService.findAlarms(AlarmQuery.builder()
... ...
... ... @@ -31,12 +31,14 @@ public class SubscriptionUpdate {
31 31 super();
32 32 this.subscriptionId = subscriptionId;
33 33 this.data = new TreeMap<>();
34   - for (TsKvEntry tsEntry : data) {
35   - List<Object> values = this.data.computeIfAbsent(tsEntry.getKey(), k -> new ArrayList<>());
36   - Object[] value = new Object[2];
37   - value[0] = tsEntry.getTs();
38   - value[1] = tsEntry.getValueAsString();
39   - values.add(value);
  34 + if (data != null) {
  35 + for (TsKvEntry tsEntry : data) {
  36 + List<Object> values = this.data.computeIfAbsent(tsEntry.getKey(), k -> new ArrayList<>());
  37 + Object[] value = new Object[2];
  38 + value[0] = tsEntry.getTs();
  39 + value[1] = tsEntry.getValueAsString();
  40 + values.add(value);
  41 + }
40 42 }
41 43 }
42 44
... ...
  1 +.idea/
  2 +*.ipr
  3 +*.iws
  4 +*.ids
  5 +*.iml
  6 +logs
  7 +target
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  19 + <modelVersion>4.0.0</modelVersion>
  20 + <parent>
  21 + <groupId>org.thingsboard</groupId>
  22 + <version>1.4.1-SNAPSHOT</version>
  23 + <artifactId>thingsboard</artifactId>
  24 + </parent>
  25 + <groupId>org.thingsboard</groupId>
  26 + <artifactId>netty-mqtt</artifactId>
  27 + <version>1.4.1-SNAPSHOT</version>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Netty MQTT Client</name>
  31 + <url>https://thingsboard.io</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/..</main.dir>
  36 + </properties>
  37 +
  38 + <dependencies>
  39 + <dependency>
  40 + <groupId>io.netty</groupId>
  41 + <artifactId>netty-codec-mqtt</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>io.netty</groupId>
  45 + <artifactId>netty-handler</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>com.google.code.findbugs</groupId>
  49 + <artifactId>jsr305</artifactId>
  50 + <version>3.0.1</version>
  51 + <optional>true</optional>
  52 + </dependency>
  53 + <dependency>
  54 + <groupId>com.google.guava</groupId>
  55 + <artifactId>guava</artifactId>
  56 + </dependency>
  57 + </dependencies>
  58 +
  59 + <distributionManagement>
  60 + <repository>
  61 + <id>jk-5-maven</id>
  62 + <name>jk-5's maven server</name>
  63 + <url>sftp://10.2.1.2/opt/maven</url>
  64 + </repository>
  65 + </distributionManagement>
  66 +
  67 + <build>
  68 + <extensions>
  69 + <extension>
  70 + <groupId>org.apache.maven.wagon</groupId>
  71 + <artifactId>wagon-ssh</artifactId>
  72 + <version>2.6</version>
  73 + </extension>
  74 + </extensions>
  75 + <plugins>
  76 + <plugin>
  77 + <groupId>org.apache.maven.plugins</groupId>
  78 + <artifactId>maven-compiler-plugin</artifactId>
  79 + <version>3.1</version>
  80 + <configuration>
  81 + <source>1.8</source>
  82 + <target>1.8</target>
  83 + </configuration>
  84 + </plugin>
  85 + <plugin>
  86 + <groupId>org.apache.maven.plugins</groupId>
  87 + <artifactId>maven-jar-plugin</artifactId>
  88 + <version>2.4</version>
  89 + <configuration>
  90 + <archive>
  91 + <manifest>
  92 + <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
  93 + </manifest>
  94 + </archive>
  95 + </configuration>
  96 + </plugin>
  97 + </plugins>
  98 + </build>
  99 +</project>
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +/**
  19 + * Created by Valerii Sosliuk on 12/26/2017.
  20 + */
  21 +public class ChannelClosedException extends RuntimeException {
  22 +
  23 + private static final long serialVersionUID = 6266638352424706909L;
  24 +
  25 + public ChannelClosedException() {
  26 + }
  27 +
  28 + public ChannelClosedException(String message) {
  29 + super(message);
  30 + }
  31 +
  32 + public ChannelClosedException(String message, Throwable cause) {
  33 + super(message, cause);
  34 + }
  35 +
  36 + public ChannelClosedException(Throwable cause) {
  37 + super(cause);
  38 + }
  39 +
  40 + public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
  41 + super(message, cause, enableSuppression, writableStackTrace);
  42 + }
  43 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import com.google.common.collect.ImmutableSet;
  19 +import io.netty.channel.Channel;
  20 +import io.netty.channel.ChannelHandlerContext;
  21 +import io.netty.channel.SimpleChannelInboundHandler;
  22 +import io.netty.handler.codec.mqtt.*;
  23 +import io.netty.util.CharsetUtil;
  24 +import io.netty.util.concurrent.Promise;
  25 +
  26 +final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> {
  27 +
  28 + private final MqttClientImpl client;
  29 + private final Promise<MqttConnectResult> connectFuture;
  30 +
  31 + MqttChannelHandler(MqttClientImpl client, Promise<MqttConnectResult> connectFuture) {
  32 + this.client = client;
  33 + this.connectFuture = connectFuture;
  34 + }
  35 +
  36 + @Override
  37 + protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception {
  38 + switch (msg.fixedHeader().messageType()) {
  39 + case CONNACK:
  40 + handleConack(ctx.channel(), (MqttConnAckMessage) msg);
  41 + break;
  42 + case SUBACK:
  43 + handleSubAck((MqttSubAckMessage) msg);
  44 + break;
  45 + case PUBLISH:
  46 + handlePublish(ctx.channel(), (MqttPublishMessage) msg);
  47 + break;
  48 + case UNSUBACK:
  49 + handleUnsuback((MqttUnsubAckMessage) msg);
  50 + break;
  51 + case PUBACK:
  52 + handlePuback((MqttPubAckMessage) msg);
  53 + break;
  54 + case PUBREC:
  55 + handlePubrec(ctx.channel(), msg);
  56 + break;
  57 + case PUBREL:
  58 + handlePubrel(ctx.channel(), msg);
  59 + break;
  60 + case PUBCOMP:
  61 + handlePubcomp(msg);
  62 + break;
  63 + }
  64 + }
  65 +
  66 + @Override
  67 + public void channelActive(ChannelHandlerContext ctx) throws Exception {
  68 + super.channelActive(ctx);
  69 +
  70 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
  71 + MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader(
  72 + this.client.getClientConfig().getProtocolVersion().protocolName(), // Protocol Name
  73 + this.client.getClientConfig().getProtocolVersion().protocolLevel(), // Protocol Level
  74 + this.client.getClientConfig().getUsername() != null, // Has Username
  75 + this.client.getClientConfig().getPassword() != null, // Has Password
  76 + this.client.getClientConfig().getLastWill() != null // Will Retain
  77 + && this.client.getClientConfig().getLastWill().isRetain(),
  78 + this.client.getClientConfig().getLastWill() != null // Will QOS
  79 + ? this.client.getClientConfig().getLastWill().getQos().value()
  80 + : 0,
  81 + this.client.getClientConfig().getLastWill() != null, // Has Will
  82 + this.client.getClientConfig().isCleanSession(), // Clean Session
  83 + this.client.getClientConfig().getTimeoutSeconds() // Timeout
  84 + );
  85 + MqttConnectPayload payload = new MqttConnectPayload(
  86 + this.client.getClientConfig().getClientId(),
  87 + this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getTopic() : null,
  88 + this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getMessage().getBytes(CharsetUtil.UTF_8) : null,
  89 + this.client.getClientConfig().getUsername(),
  90 + this.client.getClientConfig().getPassword() != null ? this.client.getClientConfig().getPassword().getBytes(CharsetUtil.UTF_8) : null
  91 + );
  92 + ctx.channel().writeAndFlush(new MqttConnectMessage(fixedHeader, variableHeader, payload));
  93 + }
  94 +
  95 + @Override
  96 + public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  97 + super.channelInactive(ctx);
  98 + }
  99 +
  100 + private void invokeHandlersForIncomingPublish(MqttPublishMessage message) {
  101 + for (MqttSubscribtion subscribtion : ImmutableSet.copyOf(this.client.getSubscriptions().values())) {
  102 + if (subscribtion.matches(message.variableHeader().topicName())) {
  103 + if (subscribtion.isOnce() && subscribtion.isCalled()) {
  104 + continue;
  105 + }
  106 + message.payload().markReaderIndex();
  107 + subscribtion.setCalled(true);
  108 + subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload());
  109 + if (subscribtion.isOnce()) {
  110 + this.client.off(subscribtion.getTopic(), subscribtion.getHandler());
  111 + }
  112 + message.payload().resetReaderIndex();
  113 + }
  114 + }
  115 + /*Set<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.client.getSubscriptions().get(message.variableHeader().topicName()));
  116 + for (MqttSubscribtion subscribtion : subscribtions) {
  117 + if(subscribtion.isOnce() && subscribtion.isCalled()){
  118 + continue;
  119 + }
  120 + message.payload().markReaderIndex();
  121 + subscribtion.setCalled(true);
  122 + subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload());
  123 + if(subscribtion.isOnce()){
  124 + this.client.off(subscribtion.getTopic(), subscribtion.getHandler());
  125 + }
  126 + message.payload().resetReaderIndex();
  127 + }*/
  128 + message.payload().release();
  129 + }
  130 +
  131 + private void handleConack(Channel channel, MqttConnAckMessage message) {
  132 + switch (message.variableHeader().connectReturnCode()) {
  133 + case CONNECTION_ACCEPTED:
  134 + this.connectFuture.setSuccess(new MqttConnectResult(true, MqttConnectReturnCode.CONNECTION_ACCEPTED, channel.closeFuture()));
  135 +
  136 + this.client.getPendingSubscribtions().entrySet().stream().filter((e) -> !e.getValue().isSent()).forEach((e) -> {
  137 + channel.write(e.getValue().getSubscribeMessage());
  138 + e.getValue().setSent(true);
  139 + });
  140 +
  141 + this.client.getPendingPublishes().forEach((id, publish) -> {
  142 + if (publish.isSent()) return;
  143 + channel.write(publish.getMessage());
  144 + publish.setSent(true);
  145 + if (publish.getQos() == MqttQoS.AT_MOST_ONCE) {
  146 + publish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
  147 + this.client.getPendingPublishes().remove(publish.getMessageId());
  148 + }
  149 + });
  150 + channel.flush();
  151 + break;
  152 +
  153 + case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD:
  154 + case CONNECTION_REFUSED_IDENTIFIER_REJECTED:
  155 + case CONNECTION_REFUSED_NOT_AUTHORIZED:
  156 + case CONNECTION_REFUSED_SERVER_UNAVAILABLE:
  157 + case CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION:
  158 + this.connectFuture.setSuccess(new MqttConnectResult(false, message.variableHeader().connectReturnCode(), channel.closeFuture()));
  159 + channel.close();
  160 + // Don't start reconnect logic here
  161 + break;
  162 + }
  163 + }
  164 +
  165 + private void handleSubAck(MqttSubAckMessage message) {
  166 + MqttPendingSubscribtion pendingSubscription = this.client.getPendingSubscribtions().remove(message.variableHeader().messageId());
  167 + if (pendingSubscription == null) {
  168 + return;
  169 + }
  170 + pendingSubscription.onSubackReceived();
  171 + for (MqttPendingSubscribtion.MqttPendingHandler handler : pendingSubscription.getHandlers()) {
  172 + MqttSubscribtion subscribtion = new MqttSubscribtion(pendingSubscription.getTopic(), handler.getHandler(), handler.isOnce());
  173 + this.client.getSubscriptions().put(pendingSubscription.getTopic(), subscribtion);
  174 + this.client.getHandlerToSubscribtion().put(handler.getHandler(), subscribtion);
  175 + }
  176 + this.client.getPendingSubscribeTopics().remove(pendingSubscription.getTopic());
  177 +
  178 + this.client.getServerSubscribtions().add(pendingSubscription.getTopic());
  179 +
  180 + if (!pendingSubscription.getFuture().isDone()) {
  181 + pendingSubscription.getFuture().setSuccess(null);
  182 + }
  183 + }
  184 +
  185 + private void handlePublish(Channel channel, MqttPublishMessage message) {
  186 + switch (message.fixedHeader().qosLevel()) {
  187 + case AT_MOST_ONCE:
  188 + invokeHandlersForIncomingPublish(message);
  189 + break;
  190 +
  191 + case AT_LEAST_ONCE:
  192 + invokeHandlersForIncomingPublish(message);
  193 + if (message.variableHeader().messageId() != -1) {
  194 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
  195 + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId());
  196 + channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader));
  197 + }
  198 + break;
  199 +
  200 + case EXACTLY_ONCE:
  201 + if (message.variableHeader().messageId() != -1) {
  202 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0);
  203 + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId());
  204 + MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader);
  205 +
  206 + MqttIncomingQos2Publish incomingQos2Publish = new MqttIncomingQos2Publish(message, pubrecMessage);
  207 + this.client.getQos2PendingIncomingPublishes().put(message.variableHeader().messageId(), incomingQos2Publish);
  208 + message.payload().retain();
  209 + incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket);
  210 +
  211 + channel.writeAndFlush(pubrecMessage);
  212 + }
  213 + break;
  214 + }
  215 + }
  216 +
  217 + private void handleUnsuback(MqttUnsubAckMessage message) {
  218 + MqttPendingUnsubscribtion unsubscribtion = this.client.getPendingServerUnsubscribes().get(message.variableHeader().messageId());
  219 + if (unsubscribtion == null) {
  220 + return;
  221 + }
  222 + unsubscribtion.onUnsubackReceived();
  223 + this.client.getServerSubscribtions().remove(unsubscribtion.getTopic());
  224 + unsubscribtion.getFuture().setSuccess(null);
  225 + this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId());
  226 + }
  227 +
  228 + private void handlePuback(MqttPubAckMessage message) {
  229 + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(message.variableHeader().messageId());
  230 + pendingPublish.getFuture().setSuccess(null);
  231 + pendingPublish.onPubackReceived();
  232 + this.client.getPendingPublishes().remove(message.variableHeader().messageId());
  233 + pendingPublish.getPayload().release();
  234 + }
  235 +
  236 + private void handlePubrec(Channel channel, MqttMessage message) {
  237 + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId());
  238 + pendingPublish.onPubackReceived();
  239 +
  240 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0);
  241 + MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader();
  242 + MqttMessage pubrelMessage = new MqttMessage(fixedHeader, variableHeader);
  243 + channel.writeAndFlush(pubrelMessage);
  244 +
  245 + pendingPublish.setPubrelMessage(pubrelMessage);
  246 + pendingPublish.startPubrelRetransmissionTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket);
  247 + }
  248 +
  249 + private void handlePubrel(Channel channel, MqttMessage message) {
  250 + if (this.client.getQos2PendingIncomingPublishes().containsKey(((MqttMessageIdVariableHeader) message.variableHeader()).messageId())) {
  251 + MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId());
  252 + this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish());
  253 + incomingQos2Publish.onPubrelReceived();
  254 + this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().messageId());
  255 + }
  256 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0);
  257 + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId());
  258 + channel.writeAndFlush(new MqttMessage(fixedHeader, variableHeader));
  259 + }
  260 +
  261 + private void handlePubcomp(MqttMessage message) {
  262 + MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader();
  263 + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(variableHeader.messageId());
  264 + pendingPublish.getFuture().setSuccess(null);
  265 + this.client.getPendingPublishes().remove(variableHeader.messageId());
  266 + pendingPublish.getPayload().release();
  267 + pendingPublish.onPubcompReceived();
  268 + }
  269 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.buffer.ByteBuf;
  19 +import io.netty.channel.Channel;
  20 +import io.netty.channel.EventLoopGroup;
  21 +import io.netty.channel.nio.NioEventLoopGroup;
  22 +import io.netty.handler.codec.mqtt.MqttQoS;
  23 +import io.netty.util.concurrent.Future;
  24 +
  25 +public interface MqttClient {
  26 +
  27 + /**
  28 + * Connect to the specified hostname/ip. By default uses port 1883.
  29 + * If you want to change the port number, see {@link #connect(String, int)}
  30 + *
  31 + * @param host The ip address or host to connect to
  32 + * @return A future which will be completed when the connection is opened and we received an CONNACK
  33 + */
  34 + Future<MqttConnectResult> connect(String host);
  35 +
  36 + /**
  37 + * Connect to the specified hostname/ip using the specified port
  38 + *
  39 + * @param host The ip address or host to connect to
  40 + * @param port The tcp port to connect to
  41 + * @return A future which will be completed when the connection is opened and we received an CONNACK
  42 + */
  43 + Future<MqttConnectResult> connect(String host, int port);
  44 +
  45 + /**
  46 + *
  47 + * @return boolean value indicating if channel is active
  48 + */
  49 + boolean isConnected();
  50 +
  51 + /**
  52 + * Attempt reconnect to the host that was attempted with {@link #connect(String, int)} method before
  53 + *
  54 + * @return A future which will be completed when the connection is opened and we received an CONNACK
  55 + * @throws IllegalStateException if no previous {@link #connect(String, int)} calls were attempted
  56 + */
  57 + Future<MqttConnectResult> reconnect();
  58 +
  59 + /**
  60 + * Retrieve the netty {@link EventLoopGroup} we are using
  61 + * @return The netty {@link EventLoopGroup} we use for the connection
  62 + */
  63 + EventLoopGroup getEventLoop();
  64 +
  65 + /**
  66 + * By default we use the netty {@link NioEventLoopGroup}.
  67 + * If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)}
  68 + * If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)}
  69 + *
  70 + * @param eventLoop The new eventloop to use
  71 + */
  72 + void setEventLoop(EventLoopGroup eventLoop);
  73 +
  74 + /**
  75 + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  76 + *
  77 + * @param topic The topic filter to subscribe to
  78 + * @param handler The handler to invoke when we receive a message
  79 + * @return A future which will be completed when the server acknowledges our subscribe request
  80 + */
  81 + Future<Void> on(String topic, MqttHandler handler);
  82 +
  83 + /**
  84 + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  85 + *
  86 + * @param topic The topic filter to subscribe to
  87 + * @param handler The handler to invoke when we receive a message
  88 + * @param qos The qos to request to the server
  89 + * @return A future which will be completed when the server acknowledges our subscribe request
  90 + */
  91 + Future<Void> on(String topic, MqttHandler handler, MqttQoS qos);
  92 +
  93 + /**
  94 + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  95 + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed
  96 + *
  97 + * @param topic The topic filter to subscribe to
  98 + * @param handler The handler to invoke when we receive a message
  99 + * @return A future which will be completed when the server acknowledges our subscribe request
  100 + */
  101 + Future<Void> once(String topic, MqttHandler handler);
  102 +
  103 + /**
  104 + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  105 + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed
  106 + *
  107 + * @param topic The topic filter to subscribe to
  108 + * @param handler The handler to invoke when we receive a message
  109 + * @param qos The qos to request to the server
  110 + * @return A future which will be completed when the server acknowledges our subscribe request
  111 + */
  112 + Future<Void> once(String topic, MqttHandler handler, MqttQoS qos);
  113 +
  114 + /**
  115 + * Remove the subscribtion for the given topic and handler
  116 + * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
  117 + *
  118 + * @param topic The topic to unsubscribe for
  119 + * @param handler The handler to unsubscribe
  120 + * @return A future which will be completed when the server acknowledges our unsubscribe request
  121 + */
  122 + Future<Void> off(String topic, MqttHandler handler);
  123 +
  124 + /**
  125 + * Remove all subscribtions for the given topic.
  126 + * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
  127 + *
  128 + * @param topic The topic to unsubscribe for
  129 + * @return A future which will be completed when the server acknowledges our unsubscribe request
  130 + */
  131 + Future<Void> off(String topic);
  132 +
  133 + /**
  134 + * Publish a message to the given payload
  135 + * @param topic The topic to publish to
  136 + * @param payload The payload to send
  137 + * @return A future which will be completed when the message is sent out of the MqttClient
  138 + */
  139 + Future<Void> publish(String topic, ByteBuf payload);
  140 +
  141 + /**
  142 + * Publish a message to the given payload, using the given qos
  143 + * @param topic The topic to publish to
  144 + * @param payload The payload to send
  145 + * @param qos The qos to use while publishing
  146 + * @return A future which will be completed when the message is delivered to the server
  147 + */
  148 + Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos);
  149 +
  150 + /**
  151 + * Publish a message to the given payload, using optional retain
  152 + * @param topic The topic to publish to
  153 + * @param payload The payload to send
  154 + * @param retain true if you want to retain the message on the server, false otherwise
  155 + * @return A future which will be completed when the message is sent out of the MqttClient
  156 + */
  157 + Future<Void> publish(String topic, ByteBuf payload, boolean retain);
  158 +
  159 + /**
  160 + * Publish a message to the given payload, using the given qos and optional retain
  161 + * @param topic The topic to publish to
  162 + * @param payload The payload to send
  163 + * @param qos The qos to use while publishing
  164 + * @param retain true if you want to retain the message on the server, false otherwise
  165 + * @return A future which will be completed when the message is delivered to the server
  166 + */
  167 + Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain);
  168 +
  169 + /**
  170 + * Retrieve the MqttClient configuration
  171 + * @return The {@link MqttClientConfig} instance we use
  172 + */
  173 + MqttClientConfig getClientConfig();
  174 +
  175 + /**
  176 + * Construct the MqttClientImpl with default config
  177 + */
  178 + static MqttClient create(){
  179 + return new MqttClientImpl();
  180 + }
  181 +
  182 + /**
  183 + * Construct the MqttClientImpl with additional config.
  184 + * This config can also be changed using the {@link #getClientConfig()} function
  185 + *
  186 + * @param config The config object to use while looking for settings
  187 + */
  188 + static MqttClient create(MqttClientConfig config){
  189 + return new MqttClientImpl(config);
  190 + }
  191 +
  192 +
  193 + /**
  194 + * Send disconnect and close channel
  195 + *
  196 + */
  197 + void disconnect();
  198 +
  199 + /**
  200 + * Sets the {@see #MqttClientCallback} object for this MqttClient
  201 + * @param callback The callback to be set
  202 + */
  203 + void setCallback(MqttClientCallback callback);
  204 +
  205 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +/**
  19 + * Created by Valerii Sosliuk on 12/30/2017.
  20 + */
  21 +public interface MqttClientCallback {
  22 +
  23 + /**
  24 + * This method is called when the connection to the server is lost.
  25 + *
  26 + * @param cause the reason behind the loss of connection.
  27 + */
  28 + public void connectionLost(Throwable cause);
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.Channel;
  19 +import io.netty.channel.socket.nio.NioSocketChannel;
  20 +import io.netty.handler.codec.mqtt.MqttVersion;
  21 +import io.netty.handler.ssl.SslContext;
  22 +
  23 +import javax.annotation.Nonnull;
  24 +import javax.annotation.Nullable;
  25 +import java.util.Random;
  26 +
  27 +@SuppressWarnings({"WeakerAccess", "unused"})
  28 +public final class MqttClientConfig {
  29 +
  30 + private final SslContext sslContext;
  31 + private final String randomClientId;
  32 +
  33 + private String clientId;
  34 + private int timeoutSeconds = 60;
  35 + private MqttVersion protocolVersion = MqttVersion.MQTT_3_1;
  36 + @Nullable private String username = null;
  37 + @Nullable private String password = null;
  38 + private boolean cleanSession = true;
  39 + @Nullable private MqttLastWill lastWill;
  40 + private Class<? extends Channel> channelClass = NioSocketChannel.class;
  41 +
  42 + private boolean reconnect = true;
  43 +
  44 + public MqttClientConfig() {
  45 + this(null);
  46 + }
  47 +
  48 + public MqttClientConfig(SslContext sslContext) {
  49 + this.sslContext = sslContext;
  50 + Random random = new Random();
  51 + String id = "netty-mqtt/";
  52 + String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
  53 + for(int i = 0; i < 8; i++){
  54 + id += options[random.nextInt(options.length)];
  55 + }
  56 + this.clientId = id;
  57 + this.randomClientId = id;
  58 + }
  59 +
  60 + @Nonnull
  61 + public String getClientId() {
  62 + return clientId;
  63 + }
  64 +
  65 + public void setClientId(@Nullable String clientId) {
  66 + if(clientId == null){
  67 + this.clientId = randomClientId;
  68 + }else{
  69 + this.clientId = clientId;
  70 + }
  71 + }
  72 +
  73 + public int getTimeoutSeconds() {
  74 + return timeoutSeconds;
  75 + }
  76 +
  77 + public void setTimeoutSeconds(int timeoutSeconds) {
  78 + if(timeoutSeconds != -1 && timeoutSeconds <= 0){
  79 + throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1");
  80 + }
  81 + this.timeoutSeconds = timeoutSeconds;
  82 + }
  83 +
  84 + public MqttVersion getProtocolVersion() {
  85 + return protocolVersion;
  86 + }
  87 +
  88 + public void setProtocolVersion(MqttVersion protocolVersion) {
  89 + if(protocolVersion == null){
  90 + throw new NullPointerException("protocolVersion");
  91 + }
  92 + this.protocolVersion = protocolVersion;
  93 + }
  94 +
  95 + @Nullable
  96 + public String getUsername() {
  97 + return username;
  98 + }
  99 +
  100 + public void setUsername(@Nullable String username) {
  101 + this.username = username;
  102 + }
  103 +
  104 + @Nullable
  105 + public String getPassword() {
  106 + return password;
  107 + }
  108 +
  109 + public void setPassword(@Nullable String password) {
  110 + this.password = password;
  111 + }
  112 +
  113 + public boolean isCleanSession() {
  114 + return cleanSession;
  115 + }
  116 +
  117 + public void setCleanSession(boolean cleanSession) {
  118 + this.cleanSession = cleanSession;
  119 + }
  120 +
  121 + @Nullable
  122 + public MqttLastWill getLastWill() {
  123 + return lastWill;
  124 + }
  125 +
  126 + public void setLastWill(@Nullable MqttLastWill lastWill) {
  127 + this.lastWill = lastWill;
  128 + }
  129 +
  130 + public Class<? extends Channel> getChannelClass() {
  131 + return channelClass;
  132 + }
  133 +
  134 + public void setChannelClass(Class<? extends Channel> channelClass) {
  135 + this.channelClass = channelClass;
  136 + }
  137 +
  138 + public SslContext getSslContext() {
  139 + return sslContext;
  140 + }
  141 +
  142 + public boolean isReconnect() {
  143 + return reconnect;
  144 + }
  145 +
  146 + public void setReconnect(boolean reconnect) {
  147 + this.reconnect = reconnect;
  148 + }
  149 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import com.google.common.collect.HashMultimap;
  19 +import com.google.common.collect.ImmutableSet;
  20 +import io.netty.bootstrap.Bootstrap;
  21 +import io.netty.buffer.ByteBuf;
  22 +import io.netty.channel.*;
  23 +import io.netty.channel.nio.NioEventLoopGroup;
  24 +import io.netty.channel.socket.SocketChannel;
  25 +import io.netty.handler.codec.mqtt.*;
  26 +import io.netty.handler.ssl.SslContext;
  27 +import io.netty.handler.timeout.IdleStateHandler;
  28 +import io.netty.util.collection.IntObjectHashMap;
  29 +import io.netty.util.concurrent.DefaultPromise;
  30 +import io.netty.util.concurrent.Future;
  31 +import io.netty.util.concurrent.Promise;
  32 +
  33 +import java.util.*;
  34 +import java.util.concurrent.TimeUnit;
  35 +import java.util.concurrent.atomic.AtomicInteger;
  36 +
  37 +/**
  38 + * Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times
  39 + */
  40 +@SuppressWarnings({"WeakerAccess", "unused"})
  41 +final class MqttClientImpl implements MqttClient {
  42 +
  43 + private final Set<String> serverSubscribtions = new HashSet<>();
  44 + private final IntObjectHashMap<MqttPendingUnsubscribtion> pendingServerUnsubscribes = new IntObjectHashMap<>();
  45 + private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>();
  46 + private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>();
  47 + private final HashMultimap<String, MqttSubscribtion> subscriptions = HashMultimap.create();
  48 + private final IntObjectHashMap<MqttPendingSubscribtion> pendingSubscribtions = new IntObjectHashMap<>();
  49 + private final Set<String> pendingSubscribeTopics = new HashSet<>();
  50 + private final HashMultimap<MqttHandler, MqttSubscribtion> handlerToSubscribtion = HashMultimap.create();
  51 + private final AtomicInteger nextMessageId = new AtomicInteger(1);
  52 +
  53 + private final MqttClientConfig clientConfig;
  54 +
  55 + private EventLoopGroup eventLoop;
  56 +
  57 + private Channel channel;
  58 +
  59 + private boolean disconnected = false;
  60 + private String host;
  61 + private int port;
  62 + private MqttClientCallback callback;
  63 +
  64 +
  65 + /**
  66 + * Construct the MqttClientImpl with default config
  67 + */
  68 + public MqttClientImpl() {
  69 + this.clientConfig = new MqttClientConfig();
  70 + }
  71 +
  72 + /**
  73 + * Construct the MqttClientImpl with additional config.
  74 + * This config can also be changed using the {@link #getClientConfig()} function
  75 + *
  76 + * @param clientConfig The config object to use while looking for settings
  77 + */
  78 + public MqttClientImpl(MqttClientConfig clientConfig) {
  79 + this.clientConfig = clientConfig;
  80 + }
  81 +
  82 + /**
  83 + * Connect to the specified hostname/ip. By default uses port 1883.
  84 + * If you want to change the port number, see {@link #connect(String, int)}
  85 + *
  86 + * @param host The ip address or host to connect to
  87 + * @return A future which will be completed when the connection is opened and we received an CONNACK
  88 + */
  89 + @Override
  90 + public Future<MqttConnectResult> connect(String host) {
  91 + return connect(host, 1883);
  92 + }
  93 +
  94 + /**
  95 + * Connect to the specified hostname/ip using the specified port
  96 + *
  97 + * @param host The ip address or host to connect to
  98 + * @param port The tcp port to connect to
  99 + * @return A future which will be completed when the connection is opened and we received an CONNACK
  100 + */
  101 + @Override
  102 + public Future<MqttConnectResult> connect(String host, int port) {
  103 + if (this.eventLoop == null) {
  104 + this.eventLoop = new NioEventLoopGroup();
  105 + }
  106 + this.host = host;
  107 + this.port = port;
  108 +
  109 + Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next());
  110 + Bootstrap bootstrap = new Bootstrap();
  111 + bootstrap.group(this.eventLoop);
  112 + bootstrap.channel(clientConfig.getChannelClass());
  113 + bootstrap.remoteAddress(host, port);
  114 + bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext()));
  115 + ChannelFuture future = bootstrap.connect();
  116 + future.addListener((ChannelFutureListener) f -> {
  117 + if (f.isSuccess()) {
  118 + MqttClientImpl.this.channel = f.channel();
  119 + } else if (clientConfig.isReconnect() && !disconnected) {
  120 + eventLoop.schedule((Runnable) () -> connect(host, port), 1L, TimeUnit.SECONDS);
  121 + }
  122 + });
  123 + return connectFuture;
  124 + }
  125 +
  126 + @Override
  127 + public boolean isConnected() {
  128 + if (!disconnected) {
  129 + return channel == null ? false : channel.isActive();
  130 + };
  131 + return false;
  132 + }
  133 +
  134 + @Override
  135 + public Future<MqttConnectResult> reconnect() {
  136 + if (host == null) {
  137 + throw new IllegalStateException("Cannot reconnect. Call connect() first");
  138 + }
  139 + return connect(host, port);
  140 + }
  141 +
  142 + /**
  143 + * Retrieve the netty {@link EventLoopGroup} we are using
  144 + *
  145 + * @return The netty {@link EventLoopGroup} we use for the connection
  146 + */
  147 + @Override
  148 + public EventLoopGroup getEventLoop() {
  149 + return eventLoop;
  150 + }
  151 +
  152 + /**
  153 + * By default we use the netty {@link NioEventLoopGroup}.
  154 + * If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)}
  155 + * If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)}
  156 + *
  157 + * @param eventLoop The new eventloop to use
  158 + */
  159 + @Override
  160 + public void setEventLoop(EventLoopGroup eventLoop) {
  161 + this.eventLoop = eventLoop;
  162 + }
  163 +
  164 + /**
  165 + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  166 + *
  167 + * @param topic The topic filter to subscribe to
  168 + * @param handler The handler to invoke when we receive a message
  169 + * @return A future which will be completed when the server acknowledges our subscribe request
  170 + */
  171 + @Override
  172 + public Future<Void> on(String topic, MqttHandler handler) {
  173 + return on(topic, handler, MqttQoS.AT_MOST_ONCE);
  174 + }
  175 +
  176 + /**
  177 + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  178 + *
  179 + * @param topic The topic filter to subscribe to
  180 + * @param handler The handler to invoke when we receive a message
  181 + * @param qos The qos to request to the server
  182 + * @return A future which will be completed when the server acknowledges our subscribe request
  183 + */
  184 + @Override
  185 + public Future<Void> on(String topic, MqttHandler handler, MqttQoS qos) {
  186 + return createSubscribtion(topic, handler, false, qos);
  187 + }
  188 +
  189 + /**
  190 + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  191 + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed
  192 + *
  193 + * @param topic The topic filter to subscribe to
  194 + * @param handler The handler to invoke when we receive a message
  195 + * @return A future which will be completed when the server acknowledges our subscribe request
  196 + */
  197 + @Override
  198 + public Future<Void> once(String topic, MqttHandler handler) {
  199 + return once(topic, handler, MqttQoS.AT_MOST_ONCE);
  200 + }
  201 +
  202 + /**
  203 + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
  204 + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed
  205 + *
  206 + * @param topic The topic filter to subscribe to
  207 + * @param handler The handler to invoke when we receive a message
  208 + * @param qos The qos to request to the server
  209 + * @return A future which will be completed when the server acknowledges our subscribe request
  210 + */
  211 + @Override
  212 + public Future<Void> once(String topic, MqttHandler handler, MqttQoS qos) {
  213 + return createSubscribtion(topic, handler, true, qos);
  214 + }
  215 +
  216 + /**
  217 + * Remove the subscribtion for the given topic and handler
  218 + * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
  219 + *
  220 + * @param topic The topic to unsubscribe for
  221 + * @param handler The handler to unsubscribe
  222 + * @return A future which will be completed when the server acknowledges our unsubscribe request
  223 + */
  224 + @Override
  225 + public Future<Void> off(String topic, MqttHandler handler) {
  226 + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
  227 + for (MqttSubscribtion subscribtion : this.handlerToSubscribtion.get(handler)) {
  228 + this.subscriptions.remove(topic, subscribtion);
  229 + }
  230 + this.handlerToSubscribtion.removeAll(handler);
  231 + this.checkSubscribtions(topic, future);
  232 + return future;
  233 + }
  234 +
  235 + /**
  236 + * Remove all subscribtions for the given topic.
  237 + * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
  238 + *
  239 + * @param topic The topic to unsubscribe for
  240 + * @return A future which will be completed when the server acknowledges our unsubscribe request
  241 + */
  242 + @Override
  243 + public Future<Void> off(String topic) {
  244 + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
  245 + ImmutableSet<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.subscriptions.get(topic));
  246 + for (MqttSubscribtion subscribtion : subscribtions) {
  247 + for (MqttSubscribtion handSub : this.handlerToSubscribtion.get(subscribtion.getHandler())) {
  248 + this.subscriptions.remove(topic, handSub);
  249 + }
  250 + this.handlerToSubscribtion.remove(subscribtion.getHandler(), subscribtion);
  251 + }
  252 + this.checkSubscribtions(topic, future);
  253 + return future;
  254 + }
  255 +
  256 + /**
  257 + * Publish a message to the given payload
  258 + *
  259 + * @param topic The topic to publish to
  260 + * @param payload The payload to send
  261 + * @return A future which will be completed when the message is sent out of the MqttClient
  262 + */
  263 + @Override
  264 + public Future<Void> publish(String topic, ByteBuf payload) {
  265 + return publish(topic, payload, MqttQoS.AT_MOST_ONCE, false);
  266 + }
  267 +
  268 + /**
  269 + * Publish a message to the given payload, using the given qos
  270 + *
  271 + * @param topic The topic to publish to
  272 + * @param payload The payload to send
  273 + * @param qos The qos to use while publishing
  274 + * @return A future which will be completed when the message is delivered to the server
  275 + */
  276 + @Override
  277 + public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos) {
  278 + return publish(topic, payload, qos, false);
  279 + }
  280 +
  281 + /**
  282 + * Publish a message to the given payload, using optional retain
  283 + *
  284 + * @param topic The topic to publish to
  285 + * @param payload The payload to send
  286 + * @param retain true if you want to retain the message on the server, false otherwise
  287 + * @return A future which will be completed when the message is sent out of the MqttClient
  288 + */
  289 + @Override
  290 + public Future<Void> publish(String topic, ByteBuf payload, boolean retain) {
  291 + return publish(topic, payload, MqttQoS.AT_MOST_ONCE, retain);
  292 + }
  293 +
  294 + /**
  295 + * Publish a message to the given payload, using the given qos and optional retain
  296 + *
  297 + * @param topic The topic to publish to
  298 + * @param payload The payload to send
  299 + * @param qos The qos to use while publishing
  300 + * @param retain true if you want to retain the message on the server, false otherwise
  301 + * @return A future which will be completed when the message is delivered to the server
  302 + */
  303 + @Override
  304 + public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain) {
  305 + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
  306 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0);
  307 + MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId());
  308 + MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload);
  309 + MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.messageId(), future, payload.retain(), message, qos);
  310 + ChannelFuture channelFuture = this.sendAndFlushPacket(message);
  311 +
  312 + if (channelFuture != null) {
  313 + pendingPublish.setSent(channelFuture != null);
  314 + if (channelFuture.cause() != null) {
  315 + future.setFailure(channelFuture.cause());
  316 + return future;
  317 + }
  318 + }
  319 + if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {
  320 + pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
  321 + } else if (pendingPublish.isSent()) {
  322 + this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish);
  323 + pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket);
  324 + }
  325 + return future;
  326 + }
  327 +
  328 + /**
  329 + * Retrieve the MqttClient configuration
  330 + *
  331 + * @return The {@link MqttClientConfig} instance we use
  332 + */
  333 + @Override
  334 + public MqttClientConfig getClientConfig() {
  335 + return clientConfig;
  336 + }
  337 +
  338 + @Override
  339 + public void disconnect() {
  340 + disconnected = true;
  341 + if (this.channel != null) {
  342 + MqttMessage message = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0));
  343 + this.sendAndFlushPacket(message).addListener(future1 -> channel.close());
  344 + }
  345 + }
  346 +
  347 + @Override
  348 + public void setCallback(MqttClientCallback callback) {
  349 + this.callback = callback;
  350 + }
  351 +
  352 +
  353 + ///////////////////////////////////////////// PRIVATE API /////////////////////////////////////////////
  354 +
  355 + ChannelFuture sendAndFlushPacket(Object message) {
  356 + if (this.channel == null) {
  357 + return null;
  358 + }
  359 + if (this.channel.isActive()) {
  360 + return this.channel.writeAndFlush(message);
  361 + }
  362 + ChannelClosedException e = new ChannelClosedException("Channel is closed");
  363 + if (callback != null) {
  364 + callback.connectionLost(e);
  365 + }
  366 + return this.channel.newFailedFuture(e);
  367 + }
  368 +
  369 + private MqttMessageIdVariableHeader getNewMessageId() {
  370 + this.nextMessageId.compareAndSet(0xffff, 1);
  371 + return MqttMessageIdVariableHeader.from(this.nextMessageId.getAndIncrement());
  372 + }
  373 +
  374 + private Future<Void> createSubscribtion(String topic, MqttHandler handler, boolean once, MqttQoS qos) {
  375 + if (this.pendingSubscribeTopics.contains(topic)) {
  376 + Optional<Map.Entry<Integer, MqttPendingSubscribtion>> subscribtionEntry = this.pendingSubscribtions.entrySet().stream().filter((e) -> e.getValue().getTopic().equals(topic)).findAny();
  377 + if (subscribtionEntry.isPresent()) {
  378 + subscribtionEntry.get().getValue().addHandler(handler, once);
  379 + return subscribtionEntry.get().getValue().getFuture();
  380 + }
  381 + }
  382 + if (this.serverSubscribtions.contains(topic)) {
  383 + MqttSubscribtion subscribtion = new MqttSubscribtion(topic, handler, once);
  384 + this.subscriptions.put(topic, subscribtion);
  385 + this.handlerToSubscribtion.put(handler, subscribtion);
  386 + return this.channel.newSucceededFuture();
  387 + }
  388 +
  389 + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
  390 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
  391 + MqttTopicSubscription subscription = new MqttTopicSubscription(topic, qos);
  392 + MqttMessageIdVariableHeader variableHeader = getNewMessageId();
  393 + MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription));
  394 + MqttSubscribeMessage message = new MqttSubscribeMessage(fixedHeader, variableHeader, payload);
  395 +
  396 + final MqttPendingSubscribtion pendingSubscribtion = new MqttPendingSubscribtion(future, topic, message);
  397 + pendingSubscribtion.addHandler(handler, once);
  398 + this.pendingSubscribtions.put(variableHeader.messageId(), pendingSubscribtion);
  399 + this.pendingSubscribeTopics.add(topic);
  400 + pendingSubscribtion.setSent(this.sendAndFlushPacket(message) != null); //If not sent, we will send it when the connection is opened
  401 +
  402 + pendingSubscribtion.startRetransmitTimer(this.eventLoop.next(), this::sendAndFlushPacket);
  403 +
  404 + return future;
  405 + }
  406 +
  407 + private void checkSubscribtions(String topic, Promise<Void> promise) {
  408 + if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscribtions.contains(topic)) {
  409 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
  410 + MqttMessageIdVariableHeader variableHeader = getNewMessageId();
  411 + MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic));
  412 + MqttUnsubscribeMessage message = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload);
  413 +
  414 + MqttPendingUnsubscribtion pendingUnsubscribtion = new MqttPendingUnsubscribtion(promise, topic, message);
  415 + this.pendingServerUnsubscribes.put(variableHeader.messageId(), pendingUnsubscribtion);
  416 + pendingUnsubscribtion.startRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket);
  417 +
  418 + this.sendAndFlushPacket(message);
  419 + } else {
  420 + promise.setSuccess(null);
  421 + }
  422 + }
  423 +
  424 + IntObjectHashMap<MqttPendingSubscribtion> getPendingSubscribtions() {
  425 + return pendingSubscribtions;
  426 + }
  427 +
  428 + HashMultimap<String, MqttSubscribtion> getSubscriptions() {
  429 + return subscriptions;
  430 + }
  431 +
  432 + Set<String> getPendingSubscribeTopics() {
  433 + return pendingSubscribeTopics;
  434 + }
  435 +
  436 + HashMultimap<MqttHandler, MqttSubscribtion> getHandlerToSubscribtion() {
  437 + return handlerToSubscribtion;
  438 + }
  439 +
  440 + Set<String> getServerSubscribtions() {
  441 + return serverSubscribtions;
  442 + }
  443 +
  444 + IntObjectHashMap<MqttPendingUnsubscribtion> getPendingServerUnsubscribes() {
  445 + return pendingServerUnsubscribes;
  446 + }
  447 +
  448 + IntObjectHashMap<MqttPendingPublish> getPendingPublishes() {
  449 + return pendingPublishes;
  450 + }
  451 +
  452 + IntObjectHashMap<MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() {
  453 + return qos2PendingIncomingPublishes;
  454 + }
  455 +
  456 + private class MqttChannelInitializer extends ChannelInitializer<SocketChannel> {
  457 +
  458 + private final Promise<MqttConnectResult> connectFuture;
  459 + private final String host;
  460 + private final int port;
  461 + private final SslContext sslContext;
  462 +
  463 +
  464 + public MqttChannelInitializer(Promise<MqttConnectResult> connectFuture, String host, int port, SslContext sslContext) {
  465 + this.connectFuture = connectFuture;
  466 + this.host = host;
  467 + this.port = port;
  468 + this.sslContext = sslContext;
  469 + }
  470 +
  471 + @Override
  472 + protected void initChannel(SocketChannel ch) throws Exception {
  473 + if (sslContext != null) {
  474 + ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port));
  475 + }
  476 +
  477 + ch.pipeline().addLast("mqttDecoder", new MqttDecoder());
  478 + ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE);
  479 + ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0));
  480 + ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));
  481 + ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture));
  482 + }
  483 + }
  484 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.ChannelFuture;
  19 +import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
  20 +
  21 +@SuppressWarnings({"WeakerAccess", "unused"})
  22 +public final class MqttConnectResult {
  23 +
  24 + private final boolean success;
  25 + private final MqttConnectReturnCode returnCode;
  26 + private final ChannelFuture closeFuture;
  27 +
  28 + MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) {
  29 + this.success = success;
  30 + this.returnCode = returnCode;
  31 + this.closeFuture = closeFuture;
  32 + }
  33 +
  34 + public boolean isSuccess() {
  35 + return success;
  36 + }
  37 +
  38 + public MqttConnectReturnCode getReturnCode() {
  39 + return returnCode;
  40 + }
  41 +
  42 + public ChannelFuture getCloseFuture() {
  43 + return closeFuture;
  44 + }
  45 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.buffer.ByteBuf;
  19 +
  20 +public interface MqttHandler {
  21 +
  22 + void onMessage(String topic, ByteBuf payload);
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.EventLoop;
  19 +import io.netty.handler.codec.mqtt.*;
  20 +
  21 +import java.util.function.Consumer;
  22 +
  23 +final class MqttIncomingQos2Publish {
  24 +
  25 + private final MqttPublishMessage incomingPublish;
  26 +
  27 + private final RetransmissionHandler<MqttMessage> retransmissionHandler = new RetransmissionHandler<>();
  28 +
  29 + MqttIncomingQos2Publish(MqttPublishMessage incomingPublish, MqttMessage originalMessage) {
  30 + this.incomingPublish = incomingPublish;
  31 +
  32 + this.retransmissionHandler.setOriginalMessage(originalMessage);
  33 + }
  34 +
  35 + MqttPublishMessage getIncomingPublish() {
  36 + return incomingPublish;
  37 + }
  38 +
  39 + void startPubrecRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) {
  40 + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) ->
  41 + sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader())));
  42 + this.retransmissionHandler.start(eventLoop);
  43 + }
  44 +
  45 + void onPubrelReceived() {
  46 + this.retransmissionHandler.stop();
  47 + }
  48 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttQoS;
  19 +
  20 +@SuppressWarnings({"WeakerAccess", "unused", "SimplifiableIfStatement", "StringBufferReplaceableByString"})
  21 +public final class MqttLastWill {
  22 +
  23 + private final String topic;
  24 + private final String message;
  25 + private final boolean retain;
  26 + private final MqttQoS qos;
  27 +
  28 + public MqttLastWill(String topic, String message, boolean retain, MqttQoS qos) {
  29 + if(topic == null){
  30 + throw new NullPointerException("topic");
  31 + }
  32 + if(message == null){
  33 + throw new NullPointerException("message");
  34 + }
  35 + if(qos == null){
  36 + throw new NullPointerException("qos");
  37 + }
  38 + this.topic = topic;
  39 + this.message = message;
  40 + this.retain = retain;
  41 + this.qos = qos;
  42 + }
  43 +
  44 + public String getTopic() {
  45 + return topic;
  46 + }
  47 +
  48 + public String getMessage() {
  49 + return message;
  50 + }
  51 +
  52 + public boolean isRetain() {
  53 + return retain;
  54 + }
  55 +
  56 + public MqttQoS getQos() {
  57 + return qos;
  58 + }
  59 +
  60 + public static MqttLastWill.Builder builder(){
  61 + return new MqttLastWill.Builder();
  62 + }
  63 +
  64 + public static final class Builder {
  65 +
  66 + private String topic;
  67 + private String message;
  68 + private boolean retain;
  69 + private MqttQoS qos;
  70 +
  71 + public String getTopic() {
  72 + return topic;
  73 + }
  74 +
  75 + public Builder setTopic(String topic) {
  76 + if(topic == null){
  77 + throw new NullPointerException("topic");
  78 + }
  79 + this.topic = topic;
  80 + return this;
  81 + }
  82 +
  83 + public String getMessage() {
  84 + return message;
  85 + }
  86 +
  87 + public Builder setMessage(String message) {
  88 + if(message == null){
  89 + throw new NullPointerException("message");
  90 + }
  91 + this.message = message;
  92 + return this;
  93 + }
  94 +
  95 + public boolean isRetain() {
  96 + return retain;
  97 + }
  98 +
  99 + public Builder setRetain(boolean retain) {
  100 + this.retain = retain;
  101 + return this;
  102 + }
  103 +
  104 + public MqttQoS getQos() {
  105 + return qos;
  106 + }
  107 +
  108 + public Builder setQos(MqttQoS qos) {
  109 + if(qos == null){
  110 + throw new NullPointerException("qos");
  111 + }
  112 + this.qos = qos;
  113 + return this;
  114 + }
  115 +
  116 + public MqttLastWill build(){
  117 + return new MqttLastWill(topic, message, retain, qos);
  118 + }
  119 + }
  120 +
  121 + @Override
  122 + public boolean equals(Object o) {
  123 + if (this == o) return true;
  124 + if (o == null || getClass() != o.getClass()) return false;
  125 +
  126 + MqttLastWill that = (MqttLastWill) o;
  127 +
  128 + if (retain != that.retain) return false;
  129 + if (!topic.equals(that.topic)) return false;
  130 + if (!message.equals(that.message)) return false;
  131 + return qos == that.qos;
  132 +
  133 + }
  134 +
  135 + @Override
  136 + public int hashCode() {
  137 + int result = topic.hashCode();
  138 + result = 31 * result + message.hashCode();
  139 + result = 31 * result + (retain ? 1 : 0);
  140 + result = 31 * result + qos.hashCode();
  141 + return result;
  142 + }
  143 +
  144 + @Override
  145 + public String toString() {
  146 + final StringBuilder sb = new StringBuilder("MqttLastWill{");
  147 + sb.append("topic='").append(topic).append('\'');
  148 + sb.append(", message='").append(message).append('\'');
  149 + sb.append(", retain=").append(retain);
  150 + sb.append(", qos=").append(qos.name());
  151 + sb.append('}');
  152 + return sb.toString();
  153 + }
  154 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.buffer.ByteBuf;
  19 +import io.netty.channel.EventLoop;
  20 +import io.netty.handler.codec.mqtt.MqttMessage;
  21 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  22 +import io.netty.handler.codec.mqtt.MqttQoS;
  23 +import io.netty.util.concurrent.Promise;
  24 +
  25 +import java.util.function.Consumer;
  26 +
  27 +final class MqttPendingPublish {
  28 +
  29 + private final int messageId;
  30 + private final Promise<Void> future;
  31 + private final ByteBuf payload;
  32 + private final MqttPublishMessage message;
  33 + private final MqttQoS qos;
  34 +
  35 + private final RetransmissionHandler<MqttPublishMessage> publishRetransmissionHandler = new RetransmissionHandler<>();
  36 + private final RetransmissionHandler<MqttMessage> pubrelRetransmissionHandler = new RetransmissionHandler<>();
  37 +
  38 + private boolean sent = false;
  39 +
  40 + MqttPendingPublish(int messageId, Promise<Void> future, ByteBuf payload, MqttPublishMessage message, MqttQoS qos) {
  41 + this.messageId = messageId;
  42 + this.future = future;
  43 + this.payload = payload;
  44 + this.message = message;
  45 + this.qos = qos;
  46 +
  47 + this.publishRetransmissionHandler.setOriginalMessage(message);
  48 + }
  49 +
  50 + int getMessageId() {
  51 + return messageId;
  52 + }
  53 +
  54 + Promise<Void> getFuture() {
  55 + return future;
  56 + }
  57 +
  58 + ByteBuf getPayload() {
  59 + return payload;
  60 + }
  61 +
  62 + boolean isSent() {
  63 + return sent;
  64 + }
  65 +
  66 + void setSent(boolean sent) {
  67 + this.sent = sent;
  68 + }
  69 +
  70 + MqttPublishMessage getMessage() {
  71 + return message;
  72 + }
  73 +
  74 + MqttQoS getQos() {
  75 + return qos;
  76 + }
  77 +
  78 + void startPublishRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) {
  79 + this.publishRetransmissionHandler.setHandle(((fixedHeader, originalMessage) ->
  80 + sendPacket.accept(new MqttPublishMessage(fixedHeader, originalMessage.variableHeader(), this.payload.retain()))));
  81 + this.publishRetransmissionHandler.start(eventLoop);
  82 + }
  83 +
  84 + void onPubackReceived() {
  85 + this.publishRetransmissionHandler.stop();
  86 + }
  87 +
  88 + void setPubrelMessage(MqttMessage pubrelMessage) {
  89 + this.pubrelRetransmissionHandler.setOriginalMessage(pubrelMessage);
  90 + }
  91 +
  92 + void startPubrelRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) {
  93 + this.pubrelRetransmissionHandler.setHandle((fixedHeader, originalMessage) ->
  94 + sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader())));
  95 + this.pubrelRetransmissionHandler.start(eventLoop);
  96 + }
  97 +
  98 + void onPubcompReceived() {
  99 + this.pubrelRetransmissionHandler.stop();
  100 + }
  101 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.EventLoop;
  19 +import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
  20 +import io.netty.util.concurrent.Promise;
  21 +
  22 +import java.util.HashSet;
  23 +import java.util.Set;
  24 +import java.util.function.Consumer;
  25 +
  26 +final class MqttPendingSubscribtion {
  27 +
  28 + private final Promise<Void> future;
  29 + private final String topic;
  30 + private final Set<MqttPendingHandler> handlers = new HashSet<>();
  31 + private final MqttSubscribeMessage subscribeMessage;
  32 +
  33 + private final RetransmissionHandler<MqttSubscribeMessage> retransmissionHandler = new RetransmissionHandler<>();
  34 +
  35 + private boolean sent = false;
  36 +
  37 + MqttPendingSubscribtion(Promise<Void> future, String topic, MqttSubscribeMessage message) {
  38 + this.future = future;
  39 + this.topic = topic;
  40 + this.subscribeMessage = message;
  41 +
  42 + this.retransmissionHandler.setOriginalMessage(message);
  43 + }
  44 +
  45 + Promise<Void> getFuture() {
  46 + return future;
  47 + }
  48 +
  49 + String getTopic() {
  50 + return topic;
  51 + }
  52 +
  53 + boolean isSent() {
  54 + return sent;
  55 + }
  56 +
  57 + void setSent(boolean sent) {
  58 + this.sent = sent;
  59 + }
  60 +
  61 + MqttSubscribeMessage getSubscribeMessage() {
  62 + return subscribeMessage;
  63 + }
  64 +
  65 + void addHandler(MqttHandler handler, boolean once){
  66 + this.handlers.add(new MqttPendingHandler(handler, once));
  67 + }
  68 +
  69 + Set<MqttPendingHandler> getHandlers() {
  70 + return handlers;
  71 + }
  72 +
  73 + void startRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) {
  74 + if(this.sent){ //If the packet is sent, we can start the retransmit timer
  75 + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) ->
  76 + sendPacket.accept(new MqttSubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload())));
  77 + this.retransmissionHandler.start(eventLoop);
  78 + }
  79 + }
  80 +
  81 + void onSubackReceived(){
  82 + this.retransmissionHandler.stop();
  83 + }
  84 +
  85 + final class MqttPendingHandler {
  86 + private final MqttHandler handler;
  87 + private final boolean once;
  88 +
  89 + MqttPendingHandler(MqttHandler handler, boolean once) {
  90 + this.handler = handler;
  91 + this.once = once;
  92 + }
  93 +
  94 + MqttHandler getHandler() {
  95 + return handler;
  96 + }
  97 +
  98 + boolean isOnce() {
  99 + return once;
  100 + }
  101 + }
  102 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.EventLoop;
  19 +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;
  20 +import io.netty.util.concurrent.Promise;
  21 +
  22 +import java.util.function.Consumer;
  23 +
  24 +final class MqttPendingUnsubscribtion {
  25 +
  26 + private final Promise<Void> future;
  27 + private final String topic;
  28 +
  29 + private final RetransmissionHandler<MqttUnsubscribeMessage> retransmissionHandler = new RetransmissionHandler<>();
  30 +
  31 + MqttPendingUnsubscribtion(Promise<Void> future, String topic, MqttUnsubscribeMessage unsubscribeMessage) {
  32 + this.future = future;
  33 + this.topic = topic;
  34 +
  35 + this.retransmissionHandler.setOriginalMessage(unsubscribeMessage);
  36 + }
  37 +
  38 + Promise<Void> getFuture() {
  39 + return future;
  40 + }
  41 +
  42 + String getTopic() {
  43 + return topic;
  44 + }
  45 +
  46 + void startRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) {
  47 + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) ->
  48 + sendPacket.accept(new MqttUnsubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload())));
  49 + this.retransmissionHandler.start(eventLoop);
  50 + }
  51 +
  52 + void onUnsubackReceived(){
  53 + this.retransmissionHandler.stop();
  54 + }
  55 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.Channel;
  19 +import io.netty.channel.ChannelFutureListener;
  20 +import io.netty.channel.ChannelHandlerContext;
  21 +import io.netty.channel.ChannelInboundHandlerAdapter;
  22 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  23 +import io.netty.handler.codec.mqtt.MqttMessage;
  24 +import io.netty.handler.codec.mqtt.MqttMessageType;
  25 +import io.netty.handler.codec.mqtt.MqttQoS;
  26 +import io.netty.handler.timeout.IdleStateEvent;
  27 +import io.netty.util.ReferenceCountUtil;
  28 +import io.netty.util.concurrent.ScheduledFuture;
  29 +
  30 +import java.util.concurrent.TimeUnit;
  31 +
  32 +final class MqttPingHandler extends ChannelInboundHandlerAdapter {
  33 +
  34 + private final int keepaliveSeconds;
  35 +
  36 + private ScheduledFuture<?> pingRespTimeout;
  37 +
  38 + MqttPingHandler(int keepaliveSeconds) {
  39 + this.keepaliveSeconds = keepaliveSeconds;
  40 + }
  41 +
  42 + @Override
  43 + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  44 + if (!(msg instanceof MqttMessage)) {
  45 + ctx.fireChannelRead(msg);
  46 + return;
  47 + }
  48 + MqttMessage message = (MqttMessage) msg;
  49 + if(message.fixedHeader().messageType() == MqttMessageType.PINGREQ){
  50 + this.handlePingReq(ctx.channel());
  51 + } else if(message.fixedHeader().messageType() == MqttMessageType.PINGRESP){
  52 + this.handlePingResp();
  53 + }else{
  54 + ctx.fireChannelRead(ReferenceCountUtil.retain(msg));
  55 + }
  56 + }
  57 +
  58 + @Override
  59 + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
  60 + super.userEventTriggered(ctx, evt);
  61 +
  62 + if(evt instanceof IdleStateEvent){
  63 + IdleStateEvent event = (IdleStateEvent) evt;
  64 + switch(event.state()){
  65 + case READER_IDLE:
  66 + break;
  67 + case WRITER_IDLE:
  68 + this.sendPingReq(ctx.channel());
  69 + break;
  70 + }
  71 + }
  72 + }
  73 +
  74 + private void sendPingReq(Channel channel){
  75 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0);
  76 + channel.writeAndFlush(new MqttMessage(fixedHeader));
  77 +
  78 + if(this.pingRespTimeout != null){
  79 + this.pingRespTimeout = channel.eventLoop().schedule(() -> {
  80 + MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
  81 + channel.writeAndFlush(new MqttMessage(fixedHeader2)).addListener(ChannelFutureListener.CLOSE);
  82 + //TODO: what do when the connection is closed ?
  83 + }, this.keepaliveSeconds, TimeUnit.SECONDS);
  84 + }
  85 + }
  86 +
  87 + private void handlePingReq(Channel channel){
  88 + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0);
  89 + channel.writeAndFlush(new MqttMessage(fixedHeader));
  90 + }
  91 +
  92 + private void handlePingResp(){
  93 + if(this.pingRespTimeout != null && !this.pingRespTimeout.isCancelled() && !this.pingRespTimeout.isDone()){
  94 + this.pingRespTimeout.cancel(true);
  95 + this.pingRespTimeout = null;
  96 + }
  97 + }
  98 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import java.util.regex.Pattern;
  19 +
  20 +final class MqttSubscribtion {
  21 +
  22 + private final String topic;
  23 + private final Pattern topicRegex;
  24 + private final MqttHandler handler;
  25 +
  26 + private final boolean once;
  27 +
  28 + private boolean called;
  29 +
  30 + MqttSubscribtion(String topic, MqttHandler handler, boolean once) {
  31 + if(topic == null){
  32 + throw new NullPointerException("topic");
  33 + }
  34 + if(handler == null){
  35 + throw new NullPointerException("handler");
  36 + }
  37 + this.topic = topic;
  38 + this.handler = handler;
  39 + this.once = once;
  40 + this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$");
  41 + }
  42 +
  43 + String getTopic() {
  44 + return topic;
  45 + }
  46 +
  47 + public MqttHandler getHandler() {
  48 + return handler;
  49 + }
  50 +
  51 + boolean isOnce() {
  52 + return once;
  53 + }
  54 +
  55 + boolean isCalled() {
  56 + return called;
  57 + }
  58 +
  59 + boolean matches(String topic){
  60 + return this.topicRegex.matcher(topic).matches();
  61 + }
  62 +
  63 + @Override
  64 + public boolean equals(Object o) {
  65 + if (this == o) return true;
  66 + if (o == null || getClass() != o.getClass()) return false;
  67 +
  68 + MqttSubscribtion that = (MqttSubscribtion) o;
  69 +
  70 + return once == that.once && topic.equals(that.topic) && handler.equals(that.handler);
  71 + }
  72 +
  73 + @Override
  74 + public int hashCode() {
  75 + int result = topic.hashCode();
  76 + result = 31 * result + handler.hashCode();
  77 + result = 31 * result + (once ? 1 : 0);
  78 + return result;
  79 + }
  80 +
  81 + void setCalled(boolean called) {
  82 + this.called = called;
  83 + }
  84 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.mqtt;
  17 +
  18 +import io.netty.channel.EventLoop;
  19 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  20 +import io.netty.handler.codec.mqtt.MqttMessage;
  21 +import io.netty.util.concurrent.ScheduledFuture;
  22 +
  23 +import java.util.concurrent.TimeUnit;
  24 +import java.util.function.BiConsumer;
  25 +
  26 +final class RetransmissionHandler<T extends MqttMessage> {
  27 +
  28 + private ScheduledFuture<?> timer;
  29 + private int timeout = 10;
  30 + private BiConsumer<MqttFixedHeader, T> handler;
  31 + private T originalMessage;
  32 +
  33 + void start(EventLoop eventLoop){
  34 + if(eventLoop == null){
  35 + throw new NullPointerException("eventLoop");
  36 + }
  37 + if(this.handler == null){
  38 + throw new NullPointerException("handler");
  39 + }
  40 + this.timeout = 10;
  41 + this.startTimer(eventLoop);
  42 + }
  43 +
  44 + private void startTimer(EventLoop eventLoop){
  45 + this.timer = eventLoop.schedule(() -> {
  46 + this.timeout += 5;
  47 + MqttFixedHeader fixedHeader = new MqttFixedHeader(this.originalMessage.fixedHeader().messageType(), true, this.originalMessage.fixedHeader().qosLevel(), this.originalMessage.fixedHeader().isRetain(), this.originalMessage.fixedHeader().remainingLength());
  48 + handler.accept(fixedHeader, originalMessage);
  49 + startTimer(eventLoop);
  50 + }, timeout, TimeUnit.SECONDS);
  51 + }
  52 +
  53 + void stop(){
  54 + if(this.timer != null){
  55 + this.timer.cancel(true);
  56 + }
  57 + }
  58 +
  59 + void setHandle(BiConsumer<MqttFixedHeader, T> runnable) {
  60 + this.handler = runnable;
  61 + }
  62 +
  63 + void setOriginalMessage(T originalMessage) {
  64 + this.originalMessage = originalMessage;
  65 + }
  66 +}
... ...
... ... @@ -79,7 +79,6 @@
79 79 <dbunit.version>2.5.3</dbunit.version>
80 80 <spring-test-dbunit.version>1.2.1</spring-test-dbunit.version>
81 81 <postgresql.driver.version>9.4.1211</postgresql.driver.version>
82   - <netty-mqtt-client.version>2.0.0TB</netty-mqtt-client.version>
83 82 <sonar.exclusions>org/thingsboard/server/gen/**/*,
84 83 org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
85 84 </sonar.exclusions>
... ... @@ -87,6 +86,7 @@
87 86 </properties>
88 87
89 88 <modules>
  89 + <module>netty-mqtt</module>
90 90 <module>common</module>
91 91 <module>rule-engine</module>
92 92 <module>dao</module>
... ... @@ -326,6 +326,11 @@
326 326 <dependencies>
327 327 <dependency>
328 328 <groupId>org.thingsboard</groupId>
  329 + <artifactId>netty-mqtt</artifactId>
  330 + <version>${project.version}</version>
  331 + </dependency>
  332 + <dependency>
  333 + <groupId>org.thingsboard</groupId>
329 334 <artifactId>extensions-api</artifactId>
330 335 <version>${project.version}</version>
331 336 </dependency>
... ... @@ -569,6 +574,11 @@
569 574 <version>${netty.version}</version>
570 575 </dependency>
571 576 <dependency>
  577 + <groupId>io.netty</groupId>
  578 + <artifactId>netty-codec-mqtt</artifactId>
  579 + <version>${netty.version}</version>
  580 + </dependency>
  581 + <dependency>
572 582 <groupId>com.datastax.cassandra</groupId>
573 583 <artifactId>cassandra-driver-core</artifactId>
574 584 <version>${cassandra.version}</version>
... ... @@ -820,11 +830,6 @@
820 830 <scope>provided</scope>
821 831 </dependency>
822 832 <dependency>
823   - <groupId>nl.jk5.netty-mqtt</groupId>
824   - <artifactId>netty-mqtt</artifactId>
825   - <version>${netty-mqtt-client.version}</version>
826   - </dependency>
827   - <dependency>
828 833 <groupId>org.elasticsearch.client</groupId>
829 834 <artifactId>rest</artifactId>
830 835 <version>${elasticsearch.version}</version>
... ...
... ... @@ -69,6 +69,10 @@
69 69 <artifactId>rule-engine-api</artifactId>
70 70 </dependency>
71 71 <dependency>
  72 + <groupId>org.thingsboard</groupId>
  73 + <artifactId>netty-mqtt</artifactId>
  74 + </dependency>
  75 + <dependency>
72 76 <groupId>com.google.guava</groupId>
73 77 <artifactId>guava</artifactId>
74 78 </dependency>
... ... @@ -91,10 +95,6 @@
91 95 <artifactId>amqp-client</artifactId>
92 96 </dependency>
93 97 <dependency>
94   - <groupId>nl.jk5.netty-mqtt</groupId>
95   - <artifactId>netty-mqtt</artifactId>
96   - </dependency>
97   - <dependency>
98 98 <groupId>org.bouncycastle</groupId>
99 99 <artifactId>bcpkix-jdk15on</artifactId>
100 100 </dependency>
... ...
... ... @@ -20,12 +20,19 @@ import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21
22 22 import javax.annotation.Nullable;
  23 +import java.util.concurrent.Executor;
23 24 import java.util.function.Consumer;
24 25
25 26 public class DonAsynchron {
26 27
27   - public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, Consumer<Throwable> onFailure) {
28   - Futures.addCallback(future, new FutureCallback<T>() {
  28 + public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
  29 + Consumer<Throwable> onFailure) {
  30 + withCallback(future, onSuccess, onFailure, null);
  31 + }
  32 +
  33 + public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
  34 + Consumer<Throwable> onFailure, Executor executor) {
  35 + FutureCallback<T> callback = new FutureCallback<T>() {
29 36 @Override
30 37 public void onSuccess(@Nullable T result) {
31 38 try {
... ... @@ -33,13 +40,17 @@ public class DonAsynchron {
33 40 } catch (Throwable th) {
34 41 onFailure(th);
35 42 }
36   -
37 43 }
38 44
39 45 @Override
40 46 public void onFailure(Throwable t) {
41 47 onFailure.accept(t);
42 48 }
43   - });
  49 + };
  50 + if (executor != null) {
  51 + Futures.addCallback(future, callback, executor);
  52 + } else {
  53 + Futures.addCallback(future, callback);
  54 + }
44 55 }
45 56 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.action;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.thingsboard.rule.engine.api.*;
  23 +import org.thingsboard.server.common.data.alarm.Alarm;
  24 +import org.thingsboard.server.common.msg.TbMsg;
  25 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  26 +
  27 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  28 +
  29 +@Slf4j
  30 +public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfiguration> implements TbNode {
  31 +
  32 + static final String PREV_ALARM_DETAILS = "prevAlarmDetails";
  33 +
  34 + static final String IS_NEW_ALARM = "isNewAlarm";
  35 + static final String IS_EXISTING_ALARM = "isExistingAlarm";
  36 + static final String IS_CLEARED_ALARM = "isClearedAlarm";
  37 +
  38 + private final ObjectMapper mapper = new ObjectMapper();
  39 +
  40 + protected C config;
  41 + private ScriptEngine buildDetailsJsEngine;
  42 +
  43 + @Override
  44 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  45 + this.config = loadAlarmNodeConfig(configuration);
  46 + this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs(), "Details");
  47 + }
  48 +
  49 + protected abstract C loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException;
  50 +
  51 + @Override
  52 + public void onMsg(TbContext ctx, TbMsg msg) {
  53 + withCallback(processAlarm(ctx, msg),
  54 + alarmResult -> {
  55 + if (alarmResult.alarm == null) {
  56 + ctx.tellNext(msg, "False");
  57 + } else if (alarmResult.isCreated) {
  58 + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created");
  59 + } else if (alarmResult.isUpdated) {
  60 + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated");
  61 + } else if (alarmResult.isCleared) {
  62 + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared");
  63 + }
  64 + },
  65 + t -> ctx.tellError(msg, t));
  66 + }
  67 +
  68 + protected abstract ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg);
  69 +
  70 + protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) {
  71 + return ctx.getJsExecutor().executeAsync(() -> {
  72 + TbMsg theMsg = msg;
  73 + if (previousDetails != null) {
  74 + TbMsgMetaData metaData = msg.getMetaData().copy();
  75 + metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails));
  76 + theMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), metaData, msg.getData());
  77 + }
  78 + return buildDetailsJsEngine.executeJson(theMsg);
  79 + });
  80 + }
  81 +
  82 + private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) {
  83 + JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm);
  84 + String data = jsonNodes.toString();
  85 + TbMsgMetaData metaData = originalMsg.getMetaData().copy();
  86 + if (alarmResult.isCreated) {
  87 + metaData.putValue(IS_NEW_ALARM, Boolean.TRUE.toString());
  88 + } else if (alarmResult.isUpdated) {
  89 + metaData.putValue(IS_EXISTING_ALARM, Boolean.TRUE.toString());
  90 + } else if (alarmResult.isCleared) {
  91 + metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString());
  92 + }
  93 + return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data);
  94 + }
  95 +
  96 +
  97 + @Override
  98 + public void destroy() {
  99 + if (buildDetailsJsEngine != null) {
  100 + buildDetailsJsEngine.destroy();
  101 + }
  102 + }
  103 +
  104 + protected static class AlarmResult {
  105 + boolean isCreated;
  106 + boolean isUpdated;
  107 + boolean isCleared;
  108 + Alarm alarm;
  109 +
  110 + AlarmResult(boolean isCreated, boolean isUpdated, boolean isCleared, Alarm alarm) {
  111 + this.isCreated = isCreated;
  112 + this.isUpdated = isUpdated;
  113 + this.isCleared = isCleared;
  114 + this.alarm = alarm;
  115 + }
  116 + }
  117 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.action;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public abstract class TbAbstractAlarmNodeConfiguration {
  22 +
  23 + private String alarmType;
  24 + private String alarmDetailsBuildJs;
  25 +
  26 +}
... ...
1   -/**
2   - * Copyright © 2016-2018 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.action;
17   -
18   -import com.fasterxml.jackson.databind.JsonNode;
19   -import com.fasterxml.jackson.databind.ObjectMapper;
20   -import com.google.common.base.Function;
21   -import com.google.common.util.concurrent.AsyncFunction;
22   -import com.google.common.util.concurrent.Futures;
23   -import com.google.common.util.concurrent.ListenableFuture;
24   -import lombok.extern.slf4j.Slf4j;
25   -import org.thingsboard.rule.engine.TbNodeUtils;
26   -import org.thingsboard.rule.engine.api.*;
27   -import org.thingsboard.server.common.data.alarm.Alarm;
28   -import org.thingsboard.server.common.data.alarm.AlarmStatus;
29   -import org.thingsboard.server.common.data.id.TenantId;
30   -import org.thingsboard.server.common.data.plugin.ComponentType;
31   -import org.thingsboard.server.common.msg.TbMsg;
32   -import org.thingsboard.server.common.msg.TbMsgMetaData;
33   -
34   -import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
35   -
36   -@Slf4j
37   -@RuleNode(
38   - type = ComponentType.ACTION,
39   - name = "alarm", relationTypes = {"Created", "Updated", "Cleared", "False"},
40   - configClazz = TbAlarmNodeConfiguration.class,
41   - nodeDescription = "Create/Update/Clear Alarm",
42   - nodeDetails = "isAlarm - JS function that verifies if Alarm should be CREATED for incoming message.\n" +
43   - "isCleared - JS function that verifies if Alarm should be CLEARED for incoming message.\n" +
44   - "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
45   - "Node output:\n" +
46   - "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/isClearedAlarm' " +
47   - "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
48   - "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
49   - uiResources = {"static/rulenode/rulenode-core-config.js"},
50   - configDirective = "tbActionNodeAlarmConfig",
51   - icon = "notifications_active"
52   -)
53   -
54   -public class TbAlarmNode implements TbNode {
55   -
56   - static final String IS_NEW_ALARM = "isNewAlarm";
57   - static final String IS_EXISTING_ALARM = "isExistingAlarm";
58   - static final String IS_CLEARED_ALARM = "isClearedAlarm";
59   -
60   - private final ObjectMapper mapper = new ObjectMapper();
61   -
62   - private TbAlarmNodeConfiguration config;
63   - private ScriptEngine createJsEngine;
64   - private ScriptEngine clearJsEngine;
65   - private ScriptEngine buildDetailsJsEngine;
66   -
67   - @Override
68   - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
69   - this.config = TbNodeUtils.convert(configuration, TbAlarmNodeConfiguration.class);
70   - this.createJsEngine = ctx.createJsScriptEngine(config.getCreateConditionJs(), "isAlarm");
71   - this.clearJsEngine = ctx.createJsScriptEngine(config.getClearConditionJs(), "isCleared");
72   - this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs(), "Details");
73   - }
74   -
75   - @Override
76   - public void onMsg(TbContext ctx, TbMsg msg) {
77   - ListeningExecutor jsExecutor = ctx.getJsExecutor();
78   -
79   - ListenableFuture<Boolean> shouldCreate = jsExecutor.executeAsync(() -> createJsEngine.executeFilter(msg));
80   - ListenableFuture<AlarmResult> transform = Futures.transform(shouldCreate, (AsyncFunction<Boolean, AlarmResult>) create -> {
81   - if (create) {
82   - return createOrUpdate(ctx, msg);
83   - } else {
84   - return checkForClearIfExist(ctx, msg);
85   - }
86   - }, ctx.getDbCallbackExecutor());
87   -
88   - withCallback(transform,
89   - alarmResult -> {
90   - if (alarmResult.alarm == null) {
91   - ctx.tellNext(msg, "False");
92   - } else if (alarmResult.isCreated) {
93   - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created");
94   - } else if (alarmResult.isUpdated) {
95   - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated");
96   - } else if (alarmResult.isCleared) {
97   - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared");
98   - }
99   - },
100   - t -> ctx.tellError(msg, t));
101   -
102   - }
103   -
104   - private ListenableFuture<AlarmResult> createOrUpdate(TbContext ctx, TbMsg msg) {
105   - ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
106   - return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> {
107   - if (a == null || a.getStatus().isCleared()) {
108   - return createNewAlarm(ctx, msg);
109   - } else {
110   - return updateAlarm(ctx, msg, a);
111   - }
112   - }, ctx.getDbCallbackExecutor());
113   - }
114   -
115   - private ListenableFuture<AlarmResult> checkForClearIfExist(TbContext ctx, TbMsg msg) {
116   - ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
117   - return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> {
118   - if (a != null && !a.getStatus().isCleared()) {
119   - return clearAlarm(ctx, msg, a);
120   - }
121   - return Futures.immediateFuture(new AlarmResult(false, false, false, null));
122   - }, ctx.getDbCallbackExecutor());
123   - }
124   -
125   - private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) {
126   - ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg),
127   - (Function<JsonNode, Alarm>) details -> buildAlarm(msg, details, ctx.getTenantId()));
128   - ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm,
129   - (Function<Alarm, Alarm>) alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
130   - return Futures.transform(asyncCreated, (Function<Alarm, AlarmResult>) alarm -> new AlarmResult(true, false, false, alarm));
131   - }
132   -
133   - private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
134   - ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg), (Function<JsonNode, Alarm>) details -> {
135   - alarm.setSeverity(config.getSeverity());
136   - alarm.setPropagate(config.isPropagate());
137   - alarm.setDetails(details);
138   - alarm.setEndTs(System.currentTimeMillis());
139   - return ctx.getAlarmService().createOrUpdateAlarm(alarm);
140   - }, ctx.getDbCallbackExecutor());
141   -
142   - return Futures.transform(asyncUpdated, (Function<Alarm, AlarmResult>) a -> new AlarmResult(false, true, false, a));
143   - }
144   -
145   - private ListenableFuture<AlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
146   - ListenableFuture<Boolean> shouldClear = ctx.getJsExecutor().executeAsync(() -> clearJsEngine.executeFilter(msg));
147   - return Futures.transform(shouldClear, (AsyncFunction<Boolean, AlarmResult>) clear -> {
148   - if (clear) {
149   - ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(alarm.getId(), System.currentTimeMillis());
150   - return Futures.transform(clearFuture, (Function<Boolean, AlarmResult>) cleared -> {
151   - alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
152   - return new AlarmResult(false, false, true, alarm);
153   - });
154   - }
155   - return Futures.immediateFuture(new AlarmResult(false, false, false, null));
156   - });
157   - }
158   -
159   - private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) {
160   - return Alarm.builder()
161   - .tenantId(tenantId)
162   - .originator(msg.getOriginator())
163   - .status(AlarmStatus.ACTIVE_UNACK)
164   - .severity(config.getSeverity())
165   - .propagate(config.isPropagate())
166   - .type(config.getAlarmType())
167   - //todo-vp: alarm date should be taken from Message or current Time should be used?
168   -// .startTs(System.currentTimeMillis())
169   -// .endTs(System.currentTimeMillis())
170   - .details(details)
171   - .build();
172   - }
173   -
174   - private ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg) {
175   - return ctx.getJsExecutor().executeAsync(() -> buildDetailsJsEngine.executeJson(msg));
176   - }
177   -
178   - private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) {
179   - JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm);
180   - String data = jsonNodes.toString();
181   - TbMsgMetaData metaData = originalMsg.getMetaData().copy();
182   - if (alarmResult.isCreated) {
183   - metaData.putValue(IS_NEW_ALARM, Boolean.TRUE.toString());
184   - } else if (alarmResult.isUpdated) {
185   - metaData.putValue(IS_EXISTING_ALARM, Boolean.TRUE.toString());
186   - } else if (alarmResult.isCleared) {
187   - metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString());
188   - }
189   - return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data);
190   - }
191   -
192   -
193   - @Override
194   - public void destroy() {
195   - if (createJsEngine != null) {
196   - createJsEngine.destroy();
197   - }
198   - if (clearJsEngine != null) {
199   - clearJsEngine.destroy();
200   - }
201   - if (buildDetailsJsEngine != null) {
202   - buildDetailsJsEngine.destroy();
203   - }
204   - }
205   -
206   - private static class AlarmResult {
207   - boolean isCreated;
208   - boolean isUpdated;
209   - boolean isCleared;
210   - Alarm alarm;
211   -
212   - AlarmResult(boolean isCreated, boolean isUpdated, boolean isCleared, Alarm alarm) {
213   - this.isCreated = isCreated;
214   - this.isUpdated = isUpdated;
215   - this.isCleared = isCleared;
216   - this.alarm = alarm;
217   - }
218   - }
219   -}
  1 +/**
  2 + * Copyright © 2016-2018 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.action;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.common.util.concurrent.AsyncFunction;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.rule.engine.TbNodeUtils;
  24 +import org.thingsboard.rule.engine.api.RuleNode;
  25 +import org.thingsboard.rule.engine.api.TbContext;
  26 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  27 +import org.thingsboard.rule.engine.api.TbNodeException;
  28 +import org.thingsboard.server.common.data.alarm.Alarm;
  29 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  30 +import org.thingsboard.server.common.data.plugin.ComponentType;
  31 +import org.thingsboard.server.common.msg.TbMsg;
  32 +
  33 +@Slf4j
  34 +@RuleNode(
  35 + type = ComponentType.ACTION,
  36 + name = "clear alarm", relationTypes = {"Cleared", "False"},
  37 + configClazz = TbClearAlarmNodeConfiguration.class,
  38 + nodeDescription = "Clear Alarm",
  39 + nodeDetails =
  40 + "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
  41 + "Node output:\n" +
  42 + "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 + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
  44 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
  45 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  46 + configDirective = "tbActionNodeClearAlarmConfig",
  47 + icon = "notifications_off"
  48 +)
  49 +public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfiguration> {
  50 +
  51 + @Override
  52 + protected TbClearAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
  53 + return TbNodeUtils.convert(configuration, TbClearAlarmNodeConfiguration.class);
  54 + }
  55 +
  56 + @Override
  57 + protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
  58 + ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
  59 + return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> {
  60 + if (a != null && !a.getStatus().isCleared()) {
  61 + return clearAlarm(ctx, msg, a);
  62 + }
  63 + return Futures.immediateFuture(new AlarmResult(false, false, false, null));
  64 + }, ctx.getDbCallbackExecutor());
  65 + }
  66 +
  67 + private ListenableFuture<AlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
  68 + ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails());
  69 + return Futures.transform(asyncDetails, (AsyncFunction<JsonNode, AlarmResult>) details -> {
  70 + ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(alarm.getId(), details, System.currentTimeMillis());
  71 + return Futures.transform(clearFuture, (AsyncFunction<Boolean, AlarmResult>) cleared -> {
  72 + if (cleared && details != null) {
  73 + alarm.setDetails(details);
  74 + }
  75 + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
  76 + return Futures.immediateFuture(new AlarmResult(false, false, true, alarm));
  77 + });
  78 + }, ctx.getDbCallbackExecutor());
  79 + }
  80 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.action;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  21 +
  22 +@Data
  23 +public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbClearAlarmNodeConfiguration> {
  24 +
  25 + @Override
  26 + public TbClearAlarmNodeConfiguration defaultConfiguration() {
  27 + TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration();
  28 + configuration.setAlarmDetailsBuildJs("var details = {};\n" +
  29 + "if (metadata.prevAlarmDetails) {\n" +
  30 + " details = JSON.parse(metadata.prevAlarmDetails);\n" +
  31 + "}\n" +
  32 + "return details;");
  33 + configuration.setAlarmType("General Alarm");
  34 + return configuration;
  35 + }
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.action;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.common.base.Function;
  20 +import com.google.common.util.concurrent.AsyncFunction;
  21 +import com.google.common.util.concurrent.Futures;
  22 +import com.google.common.util.concurrent.ListenableFuture;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.thingsboard.rule.engine.TbNodeUtils;
  25 +import org.thingsboard.rule.engine.api.RuleNode;
  26 +import org.thingsboard.rule.engine.api.TbContext;
  27 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  28 +import org.thingsboard.rule.engine.api.TbNodeException;
  29 +import org.thingsboard.server.common.data.alarm.Alarm;
  30 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  31 +import org.thingsboard.server.common.data.id.TenantId;
  32 +import org.thingsboard.server.common.data.plugin.ComponentType;
  33 +import org.thingsboard.server.common.msg.TbMsg;
  34 +
  35 +@Slf4j
  36 +@RuleNode(
  37 + type = ComponentType.ACTION,
  38 + name = "create alarm", relationTypes = {"Created", "Updated", "False"},
  39 + configClazz = TbCreateAlarmNodeConfiguration.class,
  40 + nodeDescription = "Create or Update Alarm",
  41 + nodeDetails =
  42 + "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" +
  43 + "Node output:\n" +
  44 + "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' " +
  45 + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
  46 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
  47 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  48 + configDirective = "tbActionNodeCreateAlarmConfig",
  49 + icon = "notifications_active"
  50 +)
  51 +public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
  52 +
  53 + @Override
  54 + protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
  55 + return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class);
  56 + }
  57 +
  58 + @Override
  59 + protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
  60 + ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
  61 + return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> {
  62 + if (a == null || a.getStatus().isCleared()) {
  63 + return createNewAlarm(ctx, msg);
  64 + } else {
  65 + return updateAlarm(ctx, msg, a);
  66 + }
  67 + }, ctx.getDbCallbackExecutor());
  68 +
  69 + }
  70 +
  71 + private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) {
  72 + ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
  73 + (Function<JsonNode, Alarm>) details -> buildAlarm(msg, details, ctx.getTenantId()));
  74 + ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm,
  75 + (Function<Alarm, Alarm>) alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
  76 + return Futures.transform(asyncCreated, (Function<Alarm, AlarmResult>) alarm -> new AlarmResult(true, false, false, alarm));
  77 + }
  78 +
  79 + private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
  80 + ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, alarm.getDetails()), (Function<JsonNode, Alarm>) details -> {
  81 + alarm.setSeverity(config.getSeverity());
  82 + alarm.setPropagate(config.isPropagate());
  83 + alarm.setDetails(details);
  84 + alarm.setEndTs(System.currentTimeMillis());
  85 + return ctx.getAlarmService().createOrUpdateAlarm(alarm);
  86 + }, ctx.getDbCallbackExecutor());
  87 +
  88 + return Futures.transform(asyncUpdated, (Function<Alarm, AlarmResult>) a -> new AlarmResult(false, true, false, a));
  89 + }
  90 +
  91 + private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) {
  92 + return Alarm.builder()
  93 + .tenantId(tenantId)
  94 + .originator(msg.getOriginator())
  95 + .status(AlarmStatus.ACTIVE_UNACK)
  96 + .severity(config.getSeverity())
  97 + .propagate(config.isPropagate())
  98 + .type(config.getAlarmType())
  99 + //todo-vp: alarm date should be taken from Message or current Time should be used?
  100 +// .startTs(System.currentTimeMillis())
  101 +// .endTs(System.currentTimeMillis())
  102 + .details(details)
  103 + .build();
  104 + }
  105 +
  106 +}
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNodeConfiguration.java
... ... @@ -20,22 +20,19 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
20 20 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
21 21
22 22 @Data
23   -public class TbAlarmNodeConfiguration implements NodeConfiguration {
  23 +public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> {
24 24
25   - private String createConditionJs;
26   - private String clearConditionJs;
27   - private String alarmDetailsBuildJs;
28   - private String alarmType;
29 25 private AlarmSeverity severity;
30 26 private boolean propagate;
31 27
32   -
33 28 @Override
34   - public TbAlarmNodeConfiguration defaultConfiguration() {
35   - TbAlarmNodeConfiguration configuration = new TbAlarmNodeConfiguration();
36   - configuration.setCreateConditionJs("return 'incoming message = ' + msg + meta;");
37   - configuration.setClearConditionJs("return 'incoming message = ' + msg + meta;");
38   - configuration.setAlarmDetailsBuildJs("return 'incoming message = ' + msg + meta;");
  29 + public TbCreateAlarmNodeConfiguration defaultConfiguration() {
  30 + TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration();
  31 + configuration.setAlarmDetailsBuildJs("var details = {};\n" +
  32 + "if (metadata.prevAlarmDetails) {\n" +
  33 + " details = JSON.parse(metadata.prevAlarmDetails);\n" +
  34 + "}\n"+
  35 + "return details;");
39 36 configuration.setAlarmType("General Alarm");
40 37 configuration.setSeverity(AlarmSeverity.CRITICAL);
41 38 configuration.setPropagate(false);
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.data;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  20 +
  21 +import java.util.List;
  22 +
  23 +@Data
  24 +public class DeviceRelationsQuery {
  25 + private EntitySearchDirection direction;
  26 + private int maxLevel = 1;
  27 + private String relationType;
  28 + private List<String> deviceTypes;
  29 +}
... ...
... ... @@ -52,7 +52,7 @@ public class TbJsFilterNode implements TbNode {
52 52 public void onMsg(TbContext ctx, TbMsg msg) {
53 53 ListeningExecutor jsExecutor = ctx.getJsExecutor();
54 54 withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(msg)),
55   - filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)),
  55 + filterResult -> ctx.tellNext(msg, filterResult.booleanValue() ? "True" : "False"),
56 56 t -> ctx.tellError(msg, t));
57 57 }
58 58
... ...
... ... @@ -26,8 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter
26 26 @Override
27 27 public TbJsFilterNodeConfiguration defaultConfiguration() {
28 28 TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
29   - configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' " +
30   - "&& metadata.temp == 10 && msg.bigObj.prop == 42 && msgType === 'POST_TELEMETRY';");
  29 + configuration.setJsScript("return msg.temperature > 20;");
31 30 return configuration;
32 31 }
33 32 }
... ...
... ... @@ -31,8 +31,8 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch
31 31 TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
32 32 configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
33 33 " return ['one','nine'];\n" +
34   - "};\n" +
35   - "if(msgType === 'POST_TELEMETRY') {\n" +
  34 + "}\n" +
  35 + "if(msgType === 'POST_TELEMETRY_REQUEST') {\n" +
36 36 " return ['two'];\n" +
37 37 "}\n" +
38 38 "return nextRelation(metadata, msg);");
... ...
... ... @@ -45,7 +45,7 @@ public class TbMsgTypeFilterNode implements TbNode {
45 45
46 46 @Override
47 47 public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
48   - ctx.tellNext(msg, Boolean.toString(config.getMessageTypes().contains(msg.getType())));
  48 + ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? "True" : "False");
49 49 }
50 50
51 51 @Override
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.metadata;
  17 +
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.apache.commons.collections.CollectionUtils;
  22 +import org.thingsboard.rule.engine.api.TbContext;
  23 +import org.thingsboard.rule.engine.api.TbNode;
  24 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  25 +import org.thingsboard.rule.engine.api.TbNodeException;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  28 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  29 +import org.thingsboard.server.common.msg.TbMsg;
  30 +
  31 +import java.util.List;
  32 +
  33 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  34 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
  35 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  36 +import static org.thingsboard.server.common.data.DataConstants.*;
  37 +
  38 +public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeConfiguration, T extends EntityId> implements TbNode {
  39 +
  40 + protected C config;
  41 +
  42 + @Override
  43 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  44 + this.config = loadGetAttributesNodeConfig(configuration);
  45 + }
  46 +
  47 + protected abstract C loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException;
  48 +
  49 + @Override
  50 + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
  51 + try {
  52 + withCallback(
  53 + findEntityAsync(ctx, msg.getOriginator()),
  54 + entityId -> safePutAttributes(ctx, msg, entityId),
  55 + t -> ctx.tellError(msg, t), ctx.getDbCallbackExecutor());
  56 + } catch (Throwable th) {
  57 + ctx.tellError(msg, th);
  58 + }
  59 + }
  60 +
  61 + private void safePutAttributes(TbContext ctx, TbMsg msg, T entityId) {
  62 + if(entityId == null || entityId.isNullUid()) {
  63 + ctx.tellNext(msg, FAILURE);
  64 + return;
  65 + }
  66 + ListenableFuture<List<Void>> allFutures = Futures.allAsList(
  67 + putLatestTelemetry(ctx, entityId, msg, config.getLatestTsKeyNames()),
  68 + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"),
  69 + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"),
  70 + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_")
  71 + );
  72 + withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellError(msg, t));
  73 + }
  74 +
  75 + private ListenableFuture<Void> putAttrAsync(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List<String> keys, String prefix) {
  76 + if (CollectionUtils.isEmpty(keys)) {
  77 + return Futures.immediateFuture(null);
  78 + }
  79 + ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, scope, keys);
  80 + return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, Void>) l -> {
  81 + l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString()));
  82 + return null;
  83 + });
  84 + }
  85 +
  86 + private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, List<String> keys) {
  87 + if (CollectionUtils.isEmpty(keys)) {
  88 + return Futures.immediateFuture(null);
  89 + }
  90 + ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(entityId, keys);
  91 + return Futures.transform(latest, (Function<? super List<TsKvEntry>, Void>) l -> {
  92 + l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString()));
  93 + return null;
  94 + });
  95 + }
  96 +
  97 + @Override
  98 + public void destroy() {
  99 +
  100 + }
  101 +
  102 + protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator);
  103 +}
... ...
... ... @@ -15,23 +15,16 @@
15 15 */
16 16 package org.thingsboard.rule.engine.metadata;
17 17
18   -import com.google.common.base.Function;
19 18 import com.google.common.util.concurrent.Futures;
20 19 import com.google.common.util.concurrent.ListenableFuture;
21 20 import lombok.extern.slf4j.Slf4j;
22   -import org.apache.commons.collections.CollectionUtils;
23 21 import org.thingsboard.rule.engine.TbNodeUtils;
24   -import org.thingsboard.rule.engine.api.*;
25   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
26   -import org.thingsboard.server.common.data.kv.TsKvEntry;
  22 +import org.thingsboard.rule.engine.api.RuleNode;
  23 +import org.thingsboard.rule.engine.api.TbContext;
  24 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  25 +import org.thingsboard.rule.engine.api.TbNodeException;
  26 +import org.thingsboard.server.common.data.id.EntityId;
27 27 import org.thingsboard.server.common.data.plugin.ComponentType;
28   -import org.thingsboard.server.common.msg.TbMsg;
29   -
30   -import java.util.List;
31   -
32   -import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
33   -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
34   -import static org.thingsboard.server.common.data.DataConstants.*;
35 28
36 29 /**
37 30 * Created by ashvayka on 19.01.18.
... ... @@ -47,50 +40,15 @@ import static org.thingsboard.server.common.data.DataConstants.*;
47 40 "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ",
48 41 uiResources = {"static/rulenode/rulenode-core-config.js"},
49 42 configDirective = "tbEnrichmentNodeOriginatorAttributesConfig")
50   -public class TbGetAttributesNode implements TbNode {
51   -
52   - private TbGetAttributesNodeConfiguration config;
  43 +public class TbGetAttributesNode extends TbAbstractGetAttributesNode<TbGetAttributesNodeConfiguration, EntityId> {
53 44
54 45 @Override
55   - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
56   - this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class);
  46 + protected TbGetAttributesNodeConfiguration loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
  47 + return TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class);
57 48 }
58 49
59 50 @Override
60   - public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
61   - ListenableFuture<List<Void>> allFutures = Futures.allAsList(
62   - putLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()),
63   - putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"),
64   - putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"),
65   - putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_")
66   - );
67   - withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellError(msg, t));
68   - }
69   -
70   - private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) {
71   - if (CollectionUtils.isEmpty(keys)) {
72   - return Futures.immediateFuture(null);
73   - }
74   - ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys);
75   - return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, Void>) l -> {
76   - l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString()));
77   - return null;
78   - });
79   - }
80   -
81   - private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) {
82   - if (CollectionUtils.isEmpty(keys)) {
83   - return Futures.immediateFuture(null);
84   - }
85   - ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys);
86   - return Futures.transform(latest, (Function<? super List<TsKvEntry>, Void>) l -> {
87   - l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString()));
88   - return null;
89   - });
90   - }
91   -
92   - @Override
93   - public void destroy() {
94   -
  51 + protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) {
  52 + return Futures.immediateFuture(originator);
95 53 }
96 54 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.metadata;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.thingsboard.rule.engine.TbNodeUtils;
  21 +import org.thingsboard.rule.engine.api.RuleNode;
  22 +import org.thingsboard.rule.engine.api.TbContext;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
  25 +import org.thingsboard.rule.engine.util.EntitiesRelatedDeviceIdAsyncLoader;
  26 +import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.EntityId;
  28 +import org.thingsboard.server.common.data.plugin.ComponentType;
  29 +
  30 +@Slf4j
  31 +@RuleNode(type = ComponentType.ENRICHMENT,
  32 + name = "device attributes",
  33 + configClazz = TbGetDeviceAttrNodeConfiguration.class,
  34 + nodeDescription = "Add Originators Related Device Attributes or Latest Telemetry into Message Metadata",
  35 + nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
  36 + "with specific prefix: <i>cs/shared/ss</i>. Latest telemetry value added into metadata without prefix. " +
  37 + "To access those attributes in other nodes this template can be used " +
  38 + "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ",
  39 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  40 + configDirective = "tbEnrichmentNodeDeviceAttributesConfig")
  41 +public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode<TbGetDeviceAttrNodeConfiguration, DeviceId> {
  42 +
  43 + @Override
  44 + protected TbGetDeviceAttrNodeConfiguration loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
  45 + return TbNodeUtils.convert(configuration, TbGetDeviceAttrNodeConfiguration.class);
  46 + }
  47 +
  48 + @Override
  49 + protected ListenableFuture<DeviceId> findEntityAsync(TbContext ctx, EntityId originator) {
  50 + return EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctx, originator, config.getDeviceRelationsQuery());
  51 + }
  52 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.metadata;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.data.DeviceRelationsQuery;
  20 +import org.thingsboard.server.common.data.relation.EntityRelation;
  21 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  22 +
  23 +import java.util.Collections;
  24 +
  25 +@Data
  26 +public class TbGetDeviceAttrNodeConfiguration extends TbGetAttributesNodeConfiguration {
  27 +
  28 + private DeviceRelationsQuery deviceRelationsQuery;
  29 +
  30 + @Override
  31 + public TbGetDeviceAttrNodeConfiguration defaultConfiguration() {
  32 + TbGetDeviceAttrNodeConfiguration configuration = new TbGetDeviceAttrNodeConfiguration();
  33 + configuration.setClientAttributeNames(Collections.emptyList());
  34 + configuration.setSharedAttributeNames(Collections.emptyList());
  35 + configuration.setServerAttributeNames(Collections.emptyList());
  36 + configuration.setLatestTsKeyNames(Collections.emptyList());
  37 +
  38 + DeviceRelationsQuery deviceRelationsQuery = new DeviceRelationsQuery();
  39 + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM);
  40 + deviceRelationsQuery.setMaxLevel(1);
  41 + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE);
  42 + deviceRelationsQuery.setDeviceTypes(Collections.singletonList("default"));
  43 +
  44 + configuration.setDeviceRelationsQuery(deviceRelationsQuery);
  45 +
  46 + return configuration;
  47 + }
  48 +}
... ...
... ... @@ -34,7 +34,7 @@ public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration<TbGet
34 34 Map<String, String> attrMapping = new HashMap<>();
35 35 attrMapping.putIfAbsent("temperature", "tempo");
36 36 configuration.setAttrMapping(attrMapping);
37   - configuration.setTelemetry(true);
  37 + configuration.setTelemetry(false);
38 38 return configuration;
39 39 }
40 40 }
... ...
... ... @@ -36,7 +36,7 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig
36 36 Map<String, String> attrMapping = new HashMap<>();
37 37 attrMapping.putIfAbsent("temperature", "tempo");
38 38 configuration.setAttrMapping(attrMapping);
39   - configuration.setTelemetry(true);
  39 + configuration.setTelemetry(false);
40 40
41 41 RelationsQuery relationsQuery = new RelationsQuery();
42 42 relationsQuery.setDirection(EntitySearchDirection.FROM);
... ...
... ... @@ -24,9 +24,9 @@ import io.netty.handler.ssl.SslContext;
24 24 import io.netty.handler.ssl.SslContextBuilder;
25 25 import io.netty.util.concurrent.Future;
26 26 import lombok.extern.slf4j.Slf4j;
27   -import nl.jk5.mqtt.MqttClient;
28   -import nl.jk5.mqtt.MqttClientConfig;
29   -import nl.jk5.mqtt.MqttConnectResult;
  27 +import org.thingsboard.mqtt.MqttClient;
  28 +import org.thingsboard.mqtt.MqttClientConfig;
  29 +import org.thingsboard.mqtt.MqttConnectResult;
30 30 import org.springframework.util.StringUtils;
31 31 import org.thingsboard.rule.engine.TbNodeUtils;
32 32 import org.thingsboard.rule.engine.api.*;
... ... @@ -80,8 +80,7 @@ public class TbMqttNode implements TbNode {
80 80 this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE)
81 81 .addListener(future -> {
82 82 if (future.isSuccess()) {
83   - TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
84   - ctx.tellNext(next, TbRelationTypes.SUCCESS);
  83 + ctx.tellNext(msg, TbRelationTypes.SUCCESS);
85 84 } else {
86 85 TbMsg next = processException(ctx, msg, future.cause());
87 86 ctx.tellNext(next, TbRelationTypes.FAILURE, future.cause());
... ...
... ... @@ -17,7 +17,7 @@
17 17 package org.thingsboard.rule.engine.mqtt.credentials;
18 18
19 19 import io.netty.handler.ssl.SslContext;
20   -import nl.jk5.mqtt.MqttClientConfig;
  20 +import org.thingsboard.mqtt.MqttClientConfig;
21 21
22 22 import java.util.Optional;
23 23
... ...
... ... @@ -18,7 +18,7 @@ package org.thingsboard.rule.engine.mqtt.credentials;
18 18
19 19 import io.netty.handler.ssl.SslContext;
20 20 import lombok.Data;
21   -import nl.jk5.mqtt.MqttClientConfig;
  21 +import org.thingsboard.mqtt.MqttClientConfig;
22 22
23 23 import java.util.Optional;
24 24
... ...
... ... @@ -22,7 +22,7 @@ import io.netty.handler.ssl.SslContext;
22 22 import io.netty.handler.ssl.SslContextBuilder;
23 23 import lombok.Data;
24 24 import lombok.extern.slf4j.Slf4j;
25   -import nl.jk5.mqtt.MqttClientConfig;
  25 +import org.thingsboard.mqtt.MqttClientConfig;
26 26 import org.apache.commons.codec.binary.Base64;
27 27 import org.bouncycastle.jce.provider.BouncyCastleProvider;
28 28 import org.bouncycastle.openssl.PEMDecryptorProvider;
... ...
... ... @@ -19,7 +19,7 @@ package org.thingsboard.rule.engine.mqtt.credentials;
19 19 import com.fasterxml.jackson.annotation.JsonSubTypes;
20 20 import com.fasterxml.jackson.annotation.JsonTypeInfo;
21 21 import io.netty.handler.ssl.SslContext;
22   -import nl.jk5.mqtt.MqttClientConfig;
  22 +import org.thingsboard.mqtt.MqttClientConfig;
23 23
24 24 import java.util.Optional;
25 25
... ...
... ... @@ -106,7 +106,7 @@ public class TbRabbitMqNode implements TbNode {
106 106 routingKey,
107 107 properties,
108 108 msg.getData().getBytes(UTF8));
109   - return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
  109 + return msg;
110 110 }
111 111
112 112 private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) {
... ...
... ... @@ -63,12 +63,22 @@ public class TbMsgTimeseriesNode implements TbNode {
63 63 ctx.tellError(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType()));
64 64 return;
65 65 }
66   -
  66 + long ts = -1;
  67 + String tsStr = msg.getMetaData().getValue("ts");
  68 + if (!StringUtils.isEmpty(tsStr)) {
  69 + try {
  70 + ts = Long.parseLong(tsStr);
  71 + } catch (NumberFormatException e) {}
  72 + }
  73 + if (ts == -1) {
  74 + ctx.tellError(msg, new IllegalArgumentException("Msg metadata doesn't contain valid ts value: " + msg.getMetaData()));
  75 + return;
  76 + }
67 77 String src = msg.getData();
68   - TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src));
  78 + TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts);
69 79 Map<Long, List<KvEntry>> tsKvMap = telemetryUploadRequest.getData();
70 80 if (tsKvMap == null) {
71   - ctx.tellError(msg, new IllegalArgumentException("Msg body us empty: " + src));
  81 + ctx.tellError(msg, new IllegalArgumentException("Msg body is empty: " + src));
72 82 return;
73 83 }
74 84 List<TsKvEntry> tsKvEntryList = new ArrayList<>();
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.util;
  17 +
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.apache.commons.collections.CollectionUtils;
  22 +import org.thingsboard.rule.engine.api.TbContext;
  23 +import org.thingsboard.rule.engine.data.DeviceRelationsQuery;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  26 +import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.EntityId;
  28 +import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
  29 +import org.thingsboard.server.dao.device.DeviceService;
  30 +
  31 +import java.util.List;
  32 +
  33 +public class EntitiesRelatedDeviceIdAsyncLoader {
  34 +
  35 + public static ListenableFuture<DeviceId> findDeviceAsync(TbContext ctx, EntityId originator,
  36 + DeviceRelationsQuery deviceRelationsQuery) {
  37 + DeviceService deviceService = ctx.getDeviceService();
  38 + DeviceSearchQuery query = buildQuery(originator, deviceRelationsQuery);
  39 +
  40 + ListenableFuture<List<Device>> asyncDevices = deviceService.findDevicesByQuery(query);
  41 +
  42 + return Futures.transform(asyncDevices, (AsyncFunction<List<Device>, DeviceId>)
  43 + d -> CollectionUtils.isNotEmpty(d) ? Futures.immediateFuture(d.get(0).getId())
  44 + : Futures.immediateFuture(null));
  45 + }
  46 +
  47 + private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) {
  48 + DeviceSearchQuery query = new DeviceSearchQuery();
  49 + RelationsSearchParameters parameters = new RelationsSearchParameters(originator,
  50 + deviceRelationsQuery.getDirection(), deviceRelationsQuery.getMaxLevel());
  51 + query.setParameters(parameters);
  52 + query.setRelationType(deviceRelationsQuery.getRelationType());
  53 + query.setDeviceTypes(deviceRelationsQuery.getDeviceTypes());
  54 + return query;
  55 + }
  56 +}
... ...
1   -!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.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,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(62)},function(e,t){},1,1,1,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-create-condition</label> <tb-js-func ng-model=configuration.createConditionJs function-name=isAlarm function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, true)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-clear-condition</label> <tb-js-func ng-model=configuration.clearConditionJs function-name=isCleared function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, false)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <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> <section layout=column layout-gt-sm=row> <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> </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> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </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-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=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> </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.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=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> <label translate class=tb-title>tb.rulenode.headers</label> <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> </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 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> <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> </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> </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> <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 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 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-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-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-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-chips> </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> ";
2   -},19,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\">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> <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 r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testConditionJs=function(e,n){var i=angular.copy(n?r.configuration.createConditionJs:r.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};a.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?r.configuration.createConditionJs=e:r.configuration.clearConditionJs=e,s.$setDirty()})},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(45),i=a(r),o=n(32),l=a(o),s=n(33),u=a(s),d=n(31),c=a(d),m=n(36),g=a(m),p=n(40),f=a(p),b=n(41),v=a(b),y=n(39),q=a(y),T=n(35),h=a(T),$=n(43),k=a($),w=n(44),C=a(w),x=n(38),_=a(x),M=n(37),S=a(M),E=n(42),V=a(E);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeAlarmConfig",c.default).directive("tbActionNodeLogConfig",g.default).directive("tbActionNodeRpcReplyConfig",f.default).directive("tbActionNodeRpcRequestConfig",v.default).directive("tbActionNodeRestApiCallConfig",q.default).directive("tbActionNodeKafkaConfig",h.default).directive("tbActionNodeSnsConfig",k.default).directive("tbActionNodeSqsConfig",C.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",S.default).directive("tbActionNodeSendEmailConfig",V.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),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(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),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)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$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 r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(49),i=a(r),o=n(50),l=a(o),s=n(47),u=a(s),d=n(51),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){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)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(54),i=a(r),o=n(53),l=a(o),s=n(55),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,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,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),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,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(3);var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript),i={passed:12,name:"Vit",bigObj:{prop:42}},o={temp:10};n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(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)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$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),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i);n(4)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(58),i=a(r),o=n(60),l=a(o),s=n(61),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(65),i=a(r),o=n(52),l=a(o),s=n(48),u=a(s),d=n(59),c=a(d),m=n(34),g=a(m),p=n(46),f=a(p),b=n(57),v=a(b),y=n(56),q=a(y),T=n(64),h=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbKvMapConfig",q.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{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","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping",
3   -"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.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","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.","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",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",headers:"Headers",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","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","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","message-attributes":"Message attributes","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","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","smtp-protocol":"SMTP 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 (msec)","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.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 r(e,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(63),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"},ACTIVITY_EVENT:{name:"Activity Event",value:"ACTIVITY_EVENT"},INACTIVITY_EVENT:{name:"Inactivity Event",value:"INACTIVITY_EVENT"},CONNECT_EVENT:{name:"Connect Event",value:"CONNECT_EVENT"},DISCONNECT_EVENT:{name:"Disconnect Event",value:"DISCONNECT_EVENT"}},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"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},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"}}}).name}]));
  1 +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.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,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(68)},function(e,t){},1,1,1,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-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> </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> <section layout=column layout-gt-sm=row> <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> </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> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </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=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> </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.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=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> <label translate class=tb-title>tb.rulenode.headers</label> <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> </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 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> <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> </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> </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> <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 layout=column> <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> <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-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-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-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-chips> </section> ';
  2 +},function(e,t){e.exports=' <section layout=column> <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-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-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-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-chips> </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> "},21,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\">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> <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 r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(49),i=a(r),o=n(34),l=a(o),s=n(37),u=a(s),d=n(36),c=a(d),m=n(35),g=a(m),p=n(40),f=a(p),b=n(44),v=a(b),y=n(45),q=a(y),h=n(43),$=a(h),T=n(39),k=a(T),w=n(47),C=a(w),x=n(48),_=a(x),M=n(42),S=a(M),N=n(41),E=a(N),V=n(46),P=a(V);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",_.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),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(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),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)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$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 r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){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)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(55),i=a(r),o=n(53),l=a(o),s=n(56),u=a(s),d=n(52),c=a(d),m=n(57),g=a(m);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeDeviceAttributesConfig",l.default).directive("tbEnrichmentNodeRelatedAttributesConfig",u.default).directive("tbEnrichmentNodeCustomerAttributesConfig",c.default).directive("tbEnrichmentNodeTenantAttributesConfig",g.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){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)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(60),i=a(r),o=n(59),l=a(o),s=n(61),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,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,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),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,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(3);var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(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)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$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),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i);n(4)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{
  3 +value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(31),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(64),i=a(r),o=n(66),l=a(o),s=n(67),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.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 r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(32),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(71),i=a(r),o=n(58),l=a(o),s=n(54),u=a(s),d=n(65),c=a(d),m=n(38),g=a(m),p=n(51),f=a(p),b=n(63),v=a(b),y=n(50),q=a(y),h=n(62),$=a(h),T=n(70),k=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{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","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","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.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","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.","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",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",headers:"Headers",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","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","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","message-attributes":"Message attributes","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","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","smtp-protocol":"SMTP 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 (msec)","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.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 r(e,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(69),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"},ACTIVITY_EVENT:{name:"Activity Event",value:"ACTIVITY_EVENT"},INACTIVITY_EVENT:{name:"Inactivity Event",value:"INACTIVITY_EVENT"},CONNECT_EVENT:{name:"Connect Event",value:"CONNECT_EVENT"},DISCONNECT_EVENT:{name:"Disconnect Event",value:"DISCONNECT_EVENT"}},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"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},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"}}}).name}]));
4 4 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...