Commit 1d6e1ea2b5da649c0623f84ef717009360b63baf

Authored by Yura
2 parents a89162bb d3d456ae

Merge branch 'develop/cluster-refactoring' of github.com:thingsboard/thingsboard…

… into develop/cluster-refactoring
Showing 94 changed files with 390 additions and 1062 deletions
@@ -94,5 +94,3 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( @@ -94,5 +94,3 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
94 PRIMARY KEY (id) 94 PRIMARY KEY (id)
95 ); 95 );
96 96
97 -ALTER TABLE thingsboard.device ADD last_connect bigint;  
98 -ALTER TABLE thingsboard.device ADD last_update bigint;  
@@ -35,7 +35,4 @@ CREATE TABLE IF NOT EXISTS rule_node ( @@ -35,7 +35,4 @@ CREATE TABLE IF NOT EXISTS rule_node (
35 name varchar(255), 35 name varchar(255),
36 debug_mode boolean, 36 debug_mode boolean,
37 search_text varchar(255) 37 search_text varchar(255)
38 -);  
39 -  
40 -ALTER TABLE device ADD COLUMN IF NOT EXISTS last_connect BIGINT;  
41 -ALTER TABLE device ADD COLUMN IF NOT EXISTS last_update BIGINT;  
  38 +);
@@ -340,7 +340,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso @@ -340,7 +340,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
340 kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); 340 kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
341 } 341 }
342 342
343 - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); 343 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
344 PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), 344 PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
345 SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1); 345 SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1);
346 pushToRuleEngineWithTimeout(context, tbMsg, msgData); 346 pushToRuleEngineWithTimeout(context, tbMsg, msgData);
@@ -365,7 +365,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso @@ -365,7 +365,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
365 kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); 365 kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
366 } 366 }
367 json.add("values", values); 367 json.add("values", values);
368 - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); 368 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
369 pushToRuleEngineWithTimeout(context, tbMsg, msgData); 369 pushToRuleEngineWithTimeout(context, tbMsg, msgData);
370 } 370 }
371 } 371 }
@@ -62,11 +62,6 @@ class DefaultTbContext implements TbContext { @@ -62,11 +62,6 @@ class DefaultTbContext implements TbContext {
62 } 62 }
63 63
64 @Override 64 @Override
65 - public void tellNext(TbMsg msg) {  
66 - tellNext(msg, (String) null);  
67 - }  
68 -  
69 - @Override  
70 public void tellNext(TbMsg msg, String relationType) { 65 public void tellNext(TbMsg msg, String relationType) {
71 tellNext(msg, relationType, null); 66 tellNext(msg, relationType, null);
72 } 67 }
@@ -100,11 +95,6 @@ class DefaultTbContext implements TbContext { @@ -100,11 +95,6 @@ class DefaultTbContext implements TbContext {
100 } 95 }
101 96
102 @Override 97 @Override
103 - public void spawn(TbMsg msg) {  
104 - throw new RuntimeException("Not Implemented!");  
105 - }  
106 -  
107 - @Override  
108 public void ack(TbMsg msg) { 98 public void ack(TbMsg msg) {
109 99
110 } 100 }
@@ -124,12 +114,12 @@ class DefaultTbContext implements TbContext { @@ -124,12 +114,12 @@ class DefaultTbContext implements TbContext {
124 114
125 @Override 115 @Override
126 public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { 116 public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
127 - return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); 117 + return new TbMsg(UUIDs.timeBased(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
128 } 118 }
129 119
130 @Override 120 @Override
131 public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { 121 public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
132 - return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); 122 + return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
133 } 123 }
134 124
135 @Override 125 @Override
@@ -269,6 +269,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -269,6 +269,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
269 269
270 private TbMsg enrichWithRuleChainId(TbMsg tbMsg) { 270 private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
271 // We don't put firstNodeId because it may change over time; 271 // We don't put firstNodeId because it may change over time;
272 - return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData(), tbMsg.getData(), entityId, null, 0L); 272 + return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getData(), entityId, null, 0L);
273 } 273 }
274 } 274 }
@@ -45,7 +45,6 @@ import org.thingsboard.server.dao.audit.AuditLogService; @@ -45,7 +45,6 @@ import org.thingsboard.server.dao.audit.AuditLogService;
45 import org.thingsboard.server.dao.customer.CustomerService; 45 import org.thingsboard.server.dao.customer.CustomerService;
46 import org.thingsboard.server.dao.dashboard.DashboardService; 46 import org.thingsboard.server.dao.dashboard.DashboardService;
47 import org.thingsboard.server.dao.device.DeviceCredentialsService; 47 import org.thingsboard.server.dao.device.DeviceCredentialsService;
48 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
49 import org.thingsboard.server.dao.device.DeviceService; 48 import org.thingsboard.server.dao.device.DeviceService;
50 import org.thingsboard.server.dao.exception.DataValidationException; 49 import org.thingsboard.server.dao.exception.DataValidationException;
51 import org.thingsboard.server.dao.exception.IncorrectParameterException; 50 import org.thingsboard.server.dao.exception.IncorrectParameterException;
@@ -136,9 +135,6 @@ public abstract class BaseController { @@ -136,9 +135,6 @@ public abstract class BaseController {
136 protected AuditLogService auditLogService; 135 protected AuditLogService auditLogService;
137 136
138 @Autowired 137 @Autowired
139 - protected DeviceOfflineService offlineService;  
140 -  
141 - @Autowired  
142 protected DeviceStateService deviceStateService; 138 protected DeviceStateService deviceStateService;
143 139
144 @ExceptionHandler(ThingsboardException.class) 140 @ExceptionHandler(ThingsboardException.class)
@@ -25,9 +25,6 @@ import org.thingsboard.server.common.data.EntitySubtype; @@ -25,9 +25,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
25 import org.thingsboard.server.common.data.EntityType; 25 import org.thingsboard.server.common.data.EntityType;
26 import org.thingsboard.server.common.data.audit.ActionType; 26 import org.thingsboard.server.common.data.audit.ActionType;
27 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 27 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
28 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
29 -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;  
30 -import org.thingsboard.server.common.data.exception.ThingsboardException;  
31 import org.thingsboard.server.common.data.id.CustomerId; 28 import org.thingsboard.server.common.data.id.CustomerId;
32 import org.thingsboard.server.common.data.id.DeviceId; 29 import org.thingsboard.server.common.data.id.DeviceId;
33 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
@@ -37,6 +34,8 @@ import org.thingsboard.server.common.data.security.Authority; @@ -37,6 +34,8 @@ import org.thingsboard.server.common.data.security.Authority;
37 import org.thingsboard.server.common.data.security.DeviceCredentials; 34 import org.thingsboard.server.common.data.security.DeviceCredentials;
38 import org.thingsboard.server.dao.exception.IncorrectParameterException; 35 import org.thingsboard.server.dao.exception.IncorrectParameterException;
39 import org.thingsboard.server.dao.model.ModelConstants; 36 import org.thingsboard.server.dao.model.ModelConstants;
  37 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  38 +import org.thingsboard.server.common.data.exception.ThingsboardException;
40 import org.thingsboard.server.service.security.model.SecurityUser; 39 import org.thingsboard.server.service.security.model.SecurityUser;
41 40
42 import java.util.ArrayList; 41 import java.util.ArrayList;
@@ -70,7 +69,7 @@ public class DeviceController extends BaseController { @@ -70,7 +69,7 @@ public class DeviceController extends BaseController {
70 device.setTenantId(getCurrentUser().getTenantId()); 69 device.setTenantId(getCurrentUser().getTenantId());
71 if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) { 70 if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
72 if (device.getId() == null || device.getId().isNullUid() || 71 if (device.getId() == null || device.getId().isNullUid() ||
73 - device.getCustomerId() == null || device.getCustomerId().isNullUid()) { 72 + device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
74 throw new ThingsboardException("You don't have permission to perform this operation!", 73 throw new ThingsboardException("You don't have permission to perform this operation!",
75 ThingsboardErrorCode.PERMISSION_DENIED); 74 ThingsboardErrorCode.PERMISSION_DENIED);
76 } else { 75 } else {
@@ -374,32 +373,4 @@ public class DeviceController extends BaseController { @@ -374,32 +373,4 @@ public class DeviceController extends BaseController {
374 throw handleException(e); 373 throw handleException(e);
375 } 374 }
376 } 375 }
377 -  
378 - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")  
379 - @RequestMapping(value = "/device/offline", method = RequestMethod.GET)  
380 - @ResponseBody  
381 - public List<Device> getOfflineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,  
382 - @RequestParam("threshold") long threshold) throws ThingsboardException {  
383 - try {  
384 - TenantId tenantId = getCurrentUser().getTenantId();  
385 - ListenableFuture<List<Device>> offlineDevices = offlineService.findOfflineDevices(tenantId.getId(), contactType, threshold);  
386 - return checkNotNull(offlineDevices.get());  
387 - } catch (Exception e) {  
388 - throw handleException(e);  
389 - }  
390 - }  
391 -  
392 - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")  
393 - @RequestMapping(value = "/device/online", method = RequestMethod.GET)  
394 - @ResponseBody  
395 - public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,  
396 - @RequestParam("threshold") long threshold) throws ThingsboardException {  
397 - try {  
398 - TenantId tenantId = getCurrentUser().getTenantId();  
399 - ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);  
400 - return checkNotNull(offlineDevices.get());  
401 - } catch (Exception e) {  
402 - throw handleException(e);  
403 - }  
404 - }  
405 } 376 }
@@ -118,6 +118,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @@ -118,6 +118,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
118 case FILTER: 118 case FILTER:
119 case TRANSFORMATION: 119 case TRANSFORMATION:
120 case ACTION: 120 case ACTION:
  121 + case EXTERNAL:
121 RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); 122 RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
122 scannedComponent.setName(ruleNodeAnnotation.name()); 123 scannedComponent.setName(ruleNodeAnnotation.name());
123 scannedComponent.setScope(ruleNodeAnnotation.scope()); 124 scannedComponent.setScope(ruleNodeAnnotation.scope());
@@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
194 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); 195 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
195 nodeDefinition.setUiResources(nodeAnnotation.uiResources()); 196 nodeDefinition.setUiResources(nodeAnnotation.uiResources());
196 nodeDefinition.setConfigDirective(nodeAnnotation.configDirective()); 197 nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
  198 + nodeDefinition.setIcon(nodeAnnotation.icon());
  199 + nodeDefinition.setIconUrl(nodeAnnotation.iconUrl());
197 return nodeDefinition; 200 return nodeDefinition;
198 } 201 }
199 202
@@ -116,7 +116,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn @@ -116,7 +116,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
116 messageType = msgData.get(MSG_TYPE).asText(); 116 messageType = msgData.get(MSG_TYPE).asText();
117 } 117 }
118 String newData = data != null ? data : msg.getData(); 118 String newData = data != null ? data : msg.getData();
119 - TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData(); 119 + TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
120 String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); 120 String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
121 return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition()); 121 return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
122 } catch (Throwable th) { 122 } catch (Throwable th) {
@@ -262,7 +262,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -262,7 +262,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
262 state.setInactivityTimeout(inactivityTimeout); 262 state.setInactivityTimeout(inactivityTimeout);
263 boolean oldActive = state.isActive(); 263 boolean oldActive = state.isActive();
264 state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); 264 state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
265 - if (!oldActive && state.isActive()) { 265 + if (!oldActive && state.isActive() || oldActive && !state.isActive()) {
266 saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); 266 saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
267 } 267 }
268 } 268 }
@@ -333,10 +333,6 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -333,10 +333,6 @@ public class DefaultDeviceStateService implements DeviceStateService {
333 }); 333 });
334 } 334 }
335 335
336 - private long getLastPersistTime(List<AttributeKvEntry> attributes) {  
337 - return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);  
338 - }  
339 -  
340 private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) { 336 private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
341 for (AttributeKvEntry attribute : attributes) { 337 for (AttributeKvEntry attribute : attributes) {
342 if (attribute.getKey().equals(attributeName)) { 338 if (attribute.getKey().equals(attributeName)) {
@@ -349,7 +345,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -349,7 +345,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
349 private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { 345 private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
350 DeviceState state = stateData.getState(); 346 DeviceState state = stateData.getState();
351 try { 347 try {
352 - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON 348 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
353 , json.writeValueAsString(state) 349 , json.writeValueAsString(state)
354 , null, null, 0L); 350 , null, null, 0L);
355 actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)); 351 actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
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.server.system;  
17 -  
18 -import com.fasterxml.jackson.core.type.TypeReference;  
19 -import com.google.common.collect.ImmutableMap;  
20 -import org.junit.Before;  
21 -import org.junit.Test;  
22 -import org.thingsboard.server.common.data.Device;  
23 -import org.thingsboard.server.common.data.security.DeviceCredentials;  
24 -import org.thingsboard.server.controller.AbstractControllerTest;  
25 -  
26 -import java.util.List;  
27 -import java.util.UUID;  
28 -import java.util.concurrent.TimeUnit;  
29 -  
30 -import static org.junit.Assert.assertEquals;  
31 -  
32 -public class BaseDeviceOfflineTest extends AbstractControllerTest {  
33 -  
34 - private Device deviceA;  
35 - private Device deviceB;  
36 - private DeviceCredentials credA;  
37 - private DeviceCredentials credB;  
38 -  
39 - @Before  
40 - public void before() throws Exception {  
41 - loginTenantAdmin();  
42 - deviceA = createDevice("DevA", "VMS");  
43 - credA = getCredentials(deviceA.getUuidId());  
44 - deviceB = createDevice("DevB", "SOLAR");  
45 - credB = getCredentials(deviceB.getUuidId());  
46 - }  
47 -  
48 - @Test  
49 - public void offlineDevicesCanBeFoundByLastConnectField() throws Exception {  
50 - makeDeviceContact(credA);  
51 - Thread.sleep(1000);  
52 - makeDeviceContact(credB);  
53 - Thread.sleep(100);  
54 - List<Device> devices = doGetTyped("/api/device/offline?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {  
55 - });  
56 -  
57 - assertEquals(devices.toString(),1, devices.size());  
58 - assertEquals("DevA", devices.get(0).getName());  
59 - }  
60 -  
61 - @Test  
62 - public void offlineDevicesCanBeFoundByLastUpdateField() throws Exception {  
63 - makeDeviceUpdate(credA);  
64 - Thread.sleep(1000);  
65 - makeDeviceUpdate(credB);  
66 - makeDeviceContact(credA);  
67 - Thread.sleep(100);  
68 - List<Device> devices = doGetTyped("/api/device/offline?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {  
69 - });  
70 -  
71 - assertEquals(devices.toString(),1, devices.size());  
72 - assertEquals("DevA", devices.get(0).getName());  
73 - }  
74 -  
75 - @Test  
76 - public void onlineDevicesCanBeFoundByLastConnectField() throws Exception {  
77 - makeDeviceContact(credB);  
78 - Thread.sleep(1000);  
79 - makeDeviceContact(credA);  
80 - Thread.sleep(100);  
81 - List<Device> devices = doGetTyped("/api/device/online?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {  
82 - });  
83 -  
84 - assertEquals(devices.toString(),1, devices.size());  
85 - assertEquals("DevA", devices.get(0).getName());  
86 - }  
87 -  
88 - @Test  
89 - public void onlineDevicesCanBeFoundByLastUpdateField() throws Exception {  
90 - makeDeviceUpdate(credB);  
91 - Thread.sleep(1000);  
92 - makeDeviceUpdate(credA);  
93 - makeDeviceContact(credB);  
94 - Thread.sleep(100);  
95 - List<Device> devices = doGetTyped("/api/device/online?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {  
96 - });  
97 -  
98 - assertEquals(devices.toString(),1, devices.size());  
99 - assertEquals("DevA", devices.get(0).getName());  
100 - }  
101 -  
102 - private Device createDevice(String name, String type) throws Exception {  
103 - Device device = new Device();  
104 - device.setName(name);  
105 - device.setType(type);  
106 - long currentTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10);  
107 - device.setLastConnectTs(currentTime);  
108 - device.setLastUpdateTs(currentTime);  
109 - return doPost("/api/device", device, Device.class);  
110 - }  
111 -  
112 - private DeviceCredentials getCredentials(UUID deviceId) throws Exception {  
113 - return doGet("/api/device/" + deviceId.toString() + "/credentials", DeviceCredentials.class);  
114 - }  
115 -  
116 - private void makeDeviceUpdate(DeviceCredentials credentials) throws Exception {  
117 - doPost("/api/v1/" + credentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});  
118 - }  
119 -  
120 - private void makeDeviceContact(DeviceCredentials credentials) throws Exception {  
121 - doGet("/api/v1/" + credentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");  
122 - }  
123 -}  
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.system; 16 package org.thingsboard.server.system;
17 17
18 -import com.google.common.collect.ImmutableMap;  
19 import org.junit.Before; 18 import org.junit.Before;
20 import org.junit.Test; 19 import org.junit.Test;
21 import org.springframework.test.web.servlet.ResultActions; 20 import org.springframework.test.web.servlet.ResultActions;
@@ -29,9 +28,6 @@ import java.util.Map; @@ -29,9 +28,6 @@ import java.util.Map;
29 import java.util.Random; 28 import java.util.Random;
30 import java.util.concurrent.atomic.AtomicInteger; 29 import java.util.concurrent.atomic.AtomicInteger;
31 30
32 -import static org.junit.Assert.assertEquals;  
33 -import static org.junit.Assert.assertNotNull;  
34 -import static org.junit.Assert.assertTrue;  
35 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 31 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
36 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; 32 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
37 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 33 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -52,9 +48,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { @@ -52,9 +48,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
52 device = new Device(); 48 device = new Device();
53 device.setName("My device"); 49 device.setName("My device");
54 device.setType("default"); 50 device.setType("default");
55 - long currentTime = System.currentTimeMillis();  
56 - device.setLastConnectTs(currentTime);  
57 - device.setLastUpdateTs(currentTime);  
58 device = doPost("/api/device", device, Device.class); 51 device = doPost("/api/device", device, Device.class);
59 52
60 deviceCredentials = 53 deviceCredentials =
@@ -74,34 +67,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { @@ -74,34 +67,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
74 doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk()); 67 doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
75 } 68 }
76 69
77 - @Test  
78 - public void deviceLastContactAndUpdateFieldsAreUpdated() throws Exception {  
79 - Device actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);  
80 - Long initConnectTs = actualDevice.getLastConnectTs();  
81 - Long initUpdateTs = actualDevice.getLastUpdateTs();  
82 - assertNotNull(initConnectTs);  
83 - assertNotNull(initUpdateTs);  
84 - Thread.sleep(50);  
85 -  
86 - doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});  
87 - actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);  
88 - Long postConnectTs = actualDevice.getLastConnectTs();  
89 - Long postUpdateTs = actualDevice.getLastUpdateTs();  
90 - System.out.println(postConnectTs + " - " + postUpdateTs + " -> " + (postConnectTs - initConnectTs) + " : " + (postUpdateTs - initUpdateTs));  
91 - assertTrue(postConnectTs > initConnectTs);  
92 - assertEquals(postConnectTs, postUpdateTs);  
93 - Thread.sleep(50);  
94 -  
95 - doGet("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");  
96 - Thread.sleep(50);  
97 - actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);  
98 - Long getConnectTs = actualDevice.getLastConnectTs();  
99 - Long getUpdateTs = actualDevice.getLastUpdateTs();  
100 - assertTrue(getConnectTs > postConnectTs);  
101 - assertEquals(getUpdateTs, postUpdateTs);  
102 -  
103 - }  
104 -  
105 protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { 70 protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
106 MockHttpServletRequestBuilder getRequest; 71 MockHttpServletRequestBuilder getRequest;
107 getRequest = get(urlTemplate, urlVariables); 72 getRequest = get(urlTemplate, urlVariables);
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.server.system.nosql;  
17 -  
18 -import org.thingsboard.server.dao.service.DaoNoSqlTest;  
19 -import org.thingsboard.server.system.BaseDeviceOfflineTest;  
20 -  
21 -@DaoNoSqlTest  
22 -public class DeviceOfflineNoSqlTest extends BaseDeviceOfflineTest {  
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.server.system.sql;  
17 -  
18 -import org.thingsboard.server.dao.service.DaoSqlTest;  
19 -import org.thingsboard.server.system.BaseDeviceOfflineTest;  
20 -  
21 -@DaoSqlTest  
22 -public class DeviceOfflineSqlTest extends BaseDeviceOfflineTest {  
23 -}  
@@ -31,8 +31,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -31,8 +31,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
31 private CustomerId customerId; 31 private CustomerId customerId;
32 private String name; 32 private String name;
33 private String type; 33 private String type;
34 - private Long lastConnectTs;  
35 - private Long lastUpdateTs;  
36 34
37 public Device() { 35 public Device() {
38 super(); 36 super();
@@ -83,22 +81,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -83,22 +81,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
83 this.type = type; 81 this.type = type;
84 } 82 }
85 83
86 - public Long getLastConnectTs() {  
87 - return lastConnectTs;  
88 - }  
89 -  
90 - public void setLastConnectTs(Long lastConnectTs) {  
91 - this.lastConnectTs = lastConnectTs;  
92 - }  
93 -  
94 - public Long getLastUpdateTs() {  
95 - return lastUpdateTs;  
96 - }  
97 -  
98 - public void setLastUpdateTs(Long lastUpdateTs) {  
99 - this.lastUpdateTs = lastUpdateTs;  
100 - }  
101 -  
102 @Override 84 @Override
103 public String getSearchText() { 85 public String getSearchText() {
104 return getName(); 86 return getName();
@@ -119,10 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -119,10 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
119 builder.append(getAdditionalInfo()); 101 builder.append(getAdditionalInfo());
120 builder.append(", createdTime="); 102 builder.append(", createdTime=");
121 builder.append(createdTime); 103 builder.append(createdTime);
122 - builder.append(", lastUpdateTs=");  
123 - builder.append(lastUpdateTs);  
124 - builder.append(", lastConnectTs=");  
125 - builder.append(lastConnectTs);  
126 builder.append(", id="); 104 builder.append(", id=");
127 builder.append(id); 105 builder.append(id);
128 builder.append("]"); 106 builder.append("]");
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.server.common.data.device;  
17 -  
18 -import lombok.AllArgsConstructor;  
19 -import lombok.Data;  
20 -import lombok.ToString;  
21 -  
22 -@Data  
23 -@AllArgsConstructor  
24 -@ToString  
25 -public class DeviceStatusQuery {  
26 -  
27 - private Status status;  
28 - private ContactType contactType;  
29 - private long threshold;  
30 -  
31 -  
32 - public enum Status {  
33 - ONLINE, OFFLINE  
34 - }  
35 -  
36 - public enum ContactType {  
37 - CONNECT, UPLOAD  
38 - }  
39 -  
40 -}  
@@ -17,9 +17,10 @@ package org.thingsboard.server.common.data.id; @@ -17,9 +17,10 @@ package org.thingsboard.server.common.data.id;
17 17
18 import com.fasterxml.jackson.annotation.JsonIgnore; 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 19
  20 +import java.io.Serializable;
20 import java.util.UUID; 21 import java.util.UUID;
21 22
22 -public abstract class IdBased<I extends UUIDBased> { 23 +public abstract class IdBased<I extends UUIDBased> implements Serializable {
23 24
24 protected I id; 25 protected I id;
25 26
@@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin; @@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
20 */ 20 */
21 public enum ComponentType { 21 public enum ComponentType {
22 22
23 - ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN 23 + ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, OLD_ACTION, PLUGIN
24 24
25 } 25 }
@@ -106,7 +106,7 @@ public final class TbMsg implements Serializable { @@ -106,7 +106,7 @@ public final class TbMsg implements Serializable {
106 } 106 }
107 107
108 public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) { 108 public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
109 - return new TbMsg(newId, type, originator, metaData, dataType, data, ruleChainId, ruleNodeId, clusterPartition); 109 + return new TbMsg(newId, type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, clusterPartition);
110 } 110 }
111 111
112 } 112 }
@@ -15,9 +15,9 @@ @@ -15,9 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.dao.device; 16 package org.thingsboard.server.dao.device;
17 17
18 -import com.datastax.driver.core.*;  
19 -import com.datastax.driver.core.querybuilder.Clause;  
20 -import com.datastax.driver.core.querybuilder.QueryBuilder; 18 +import com.datastax.driver.core.ResultSet;
  19 +import com.datastax.driver.core.ResultSetFuture;
  20 +import com.datastax.driver.core.Statement;
21 import com.datastax.driver.core.querybuilder.Select; 21 import com.datastax.driver.core.querybuilder.Select;
22 import com.datastax.driver.mapping.Result; 22 import com.datastax.driver.mapping.Result;
23 import com.google.common.base.Function; 23 import com.google.common.base.Function;
@@ -28,11 +28,9 @@ import org.springframework.stereotype.Component; @@ -28,11 +28,9 @@ import org.springframework.stereotype.Component;
28 import org.thingsboard.server.common.data.Device; 28 import org.thingsboard.server.common.data.Device;
29 import org.thingsboard.server.common.data.EntitySubtype; 29 import org.thingsboard.server.common.data.EntitySubtype;
30 import org.thingsboard.server.common.data.EntityType; 30 import org.thingsboard.server.common.data.EntityType;
31 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
32 import org.thingsboard.server.common.data.page.TextPageLink; 31 import org.thingsboard.server.common.data.page.TextPageLink;
33 import org.thingsboard.server.dao.DaoUtil; 32 import org.thingsboard.server.dao.DaoUtil;
34 import org.thingsboard.server.dao.model.EntitySubtypeEntity; 33 import org.thingsboard.server.dao.model.EntitySubtypeEntity;
35 -import org.thingsboard.server.dao.model.ModelConstants;  
36 import org.thingsboard.server.dao.model.nosql.DeviceEntity; 34 import org.thingsboard.server.dao.model.nosql.DeviceEntity;
37 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; 35 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
38 import org.thingsboard.server.dao.util.NoSqlDao; 36 import org.thingsboard.server.dao.util.NoSqlDao;
@@ -159,7 +157,7 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt @@ -159,7 +157,7 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
159 if (result != null) { 157 if (result != null) {
160 List<EntitySubtype> entitySubtypes = new ArrayList<>(); 158 List<EntitySubtype> entitySubtypes = new ArrayList<>();
161 result.all().forEach((entitySubtypeEntity) -> 159 result.all().forEach((entitySubtypeEntity) ->
162 - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) 160 + entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
163 ); 161 );
164 return entitySubtypes; 162 return entitySubtypes;
165 } else { 163 } else {
@@ -169,68 +167,4 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt @@ -169,68 +167,4 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
169 }); 167 });
170 } 168 }
171 169
172 - @Override  
173 - public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {  
174 - log.debug("Try to find [{}] devices by tenantId [{}]", statusQuery.getStatus(), tenantId);  
175 -  
176 - Select select = select().from(DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();  
177 - Select.Where query = select.where();  
178 - query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId));  
179 - Clause clause = statusClause(statusQuery);  
180 - query.and(clause);  
181 - return findListByStatementAsync(query);  
182 - }  
183 -  
184 - @Override  
185 - public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {  
186 - log.debug("Try to find [{}] devices by tenantId [{}] and type [{}]", statusQuery.getStatus(), tenantId, type);  
187 -  
188 - Select select = select().from(DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();  
189 - Select.Where query = select.where()  
190 - .and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId))  
191 - .and(eq(DEVICE_TYPE_PROPERTY, type));  
192 -  
193 - query.and(statusClause(statusQuery));  
194 - return findListByStatementAsync(query);  
195 - }  
196 -  
197 -  
198 - @Override  
199 - public void saveDeviceStatus(Device device) {  
200 - PreparedStatement statement = prepare("insert into " +  
201 - "device (id, tenant_id, customer_id, type, last_connect, last_update) values (?, ?, ?, ?, ?, ?)");  
202 - BoundStatement boundStatement = statement.bind(device.getUuidId(), device.getTenantId().getId(), device.getCustomerId().getId(),  
203 - device.getType(), device.getLastConnectTs(), device.getLastUpdateTs());  
204 - ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement);  
205 - Futures.withFallback(resultSetFuture, t -> {  
206 - log.error("Can't update device status for [{}]", device, t);  
207 - throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);  
208 - });  
209 - }  
210 -  
211 - private String getStatusProperty(DeviceStatusQuery statusQuery) {  
212 - switch (statusQuery.getContactType()) {  
213 - case UPLOAD:  
214 - return DEVICE_LAST_UPDATE_PROPERTY;  
215 - case CONNECT:  
216 - return DEVICE_LAST_CONNECT_PROPERTY;  
217 - }  
218 - return null;  
219 - }  
220 -  
221 - private Clause statusClause(DeviceStatusQuery statusQuery) {  
222 - long minTime = System.currentTimeMillis() - statusQuery.getThreshold();  
223 - String statusProperty = getStatusProperty(statusQuery);  
224 - if (statusProperty != null) {  
225 - switch (statusQuery.getStatus()) {  
226 - case ONLINE:  
227 - return gt(statusProperty, minTime);  
228 - case OFFLINE:  
229 - return lt(statusProperty, minTime);  
230 - }  
231 - }  
232 - log.error("Could not build status query from [{}]", statusQuery);  
233 - throw new IllegalStateException("Could not build status query for device []");  
234 - }  
235 -  
236 } 170 }
@@ -18,7 +18,6 @@ package org.thingsboard.server.dao.device; @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.device;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
20 import org.thingsboard.server.common.data.EntitySubtype; 20 import org.thingsboard.server.common.data.EntitySubtype;
21 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
22 import org.thingsboard.server.common.data.page.TextPageLink; 21 import org.thingsboard.server.common.data.page.TextPageLink;
23 import org.thingsboard.server.dao.Dao; 22 import org.thingsboard.server.dao.Dao;
24 23
@@ -28,6 +27,7 @@ import java.util.UUID; @@ -28,6 +27,7 @@ import java.util.UUID;
28 27
29 /** 28 /**
30 * The Interface DeviceDao. 29 * The Interface DeviceDao.
  30 + *
31 */ 31 */
32 public interface DeviceDao extends Dao<Device> { 32 public interface DeviceDao extends Dao<Device> {
33 33
@@ -52,7 +52,7 @@ public interface DeviceDao extends Dao<Device> { @@ -52,7 +52,7 @@ public interface DeviceDao extends Dao<Device> {
52 * Find devices by tenantId, type and page link. 52 * Find devices by tenantId, type and page link.
53 * 53 *
54 * @param tenantId the tenantId 54 * @param tenantId the tenantId
55 - * @param type the type 55 + * @param type the type
56 * @param pageLink the page link 56 * @param pageLink the page link
57 * @return the list of device objects 57 * @return the list of device objects
58 */ 58 */
@@ -61,7 +61,7 @@ public interface DeviceDao extends Dao<Device> { @@ -61,7 +61,7 @@ public interface DeviceDao extends Dao<Device> {
61 /** 61 /**
62 * Find devices by tenantId and devices Ids. 62 * Find devices by tenantId and devices Ids.
63 * 63 *
64 - * @param tenantId the tenantId 64 + * @param tenantId the tenantId
65 * @param deviceIds the device Ids 65 * @param deviceIds the device Ids
66 * @return the list of device objects 66 * @return the list of device objects
67 */ 67 */
@@ -70,9 +70,9 @@ public interface DeviceDao extends Dao<Device> { @@ -70,9 +70,9 @@ public interface DeviceDao extends Dao<Device> {
70 /** 70 /**
71 * Find devices by tenantId, customerId and page link. 71 * Find devices by tenantId, customerId and page link.
72 * 72 *
73 - * @param tenantId the tenantId 73 + * @param tenantId the tenantId
74 * @param customerId the customerId 74 * @param customerId the customerId
75 - * @param pageLink the page link 75 + * @param pageLink the page link
76 * @return the list of device objects 76 * @return the list of device objects
77 */ 77 */
78 List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); 78 List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
@@ -80,10 +80,10 @@ public interface DeviceDao extends Dao<Device> { @@ -80,10 +80,10 @@ public interface DeviceDao extends Dao<Device> {
80 /** 80 /**
81 * Find devices by tenantId, customerId, type and page link. 81 * Find devices by tenantId, customerId, type and page link.
82 * 82 *
83 - * @param tenantId the tenantId 83 + * @param tenantId the tenantId
84 * @param customerId the customerId 84 * @param customerId the customerId
85 - * @param type the type  
86 - * @param pageLink the page link 85 + * @param type the type
  86 + * @param pageLink the page link
87 * @return the list of device objects 87 * @return the list of device objects
88 */ 88 */
89 List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink); 89 List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
@@ -92,9 +92,9 @@ public interface DeviceDao extends Dao<Device> { @@ -92,9 +92,9 @@ public interface DeviceDao extends Dao<Device> {
92 /** 92 /**
93 * Find devices by tenantId, customerId and devices Ids. 93 * Find devices by tenantId, customerId and devices Ids.
94 * 94 *
95 - * @param tenantId the tenantId 95 + * @param tenantId the tenantId
96 * @param customerId the customerId 96 * @param customerId the customerId
97 - * @param deviceIds the device Ids 97 + * @param deviceIds the device Ids
98 * @return the list of device objects 98 * @return the list of device objects
99 */ 99 */
100 ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds); 100 ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds);
@@ -103,7 +103,7 @@ public interface DeviceDao extends Dao<Device> { @@ -103,7 +103,7 @@ public interface DeviceDao extends Dao<Device> {
103 * Find devices by tenantId and device name. 103 * Find devices by tenantId and device name.
104 * 104 *
105 * @param tenantId the tenantId 105 * @param tenantId the tenantId
106 - * @param name the device name 106 + * @param name the device name
107 * @return the optional device object 107 * @return the optional device object
108 */ 108 */
109 Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name); 109 Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name);
@@ -114,31 +114,4 @@ public interface DeviceDao extends Dao<Device> { @@ -114,31 +114,4 @@ public interface DeviceDao extends Dao<Device> {
114 * @return the list of tenant device type objects 114 * @return the list of tenant device type objects
115 */ 115 */
116 ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId); 116 ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
117 -  
118 - /**  
119 - * Find devices by tenantId, statusQuery and page link.  
120 - *  
121 - * @param tenantId the tenantId  
122 - * @param statusQuery the page link  
123 - * @return the list of device objects  
124 - */  
125 - ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery);  
126 -  
127 - /**  
128 - * Find devices by tenantId, type, statusQuery and page link.  
129 - *  
130 - * @param tenantId the tenantId  
131 - * @param type the type  
132 - * @param statusQuery the page link  
133 - * @return the list of device objects  
134 - */  
135 - ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery);  
136 -  
137 -  
138 - /**  
139 - * Update device last contact and update timestamp async  
140 - *  
141 - * @param device the device object  
142 - */  
143 - void saveDeviceStatus(Device device);  
144 } 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.server.dao.device;  
17 -  
18 -import com.google.common.util.concurrent.ListenableFuture;  
19 -import org.thingsboard.server.common.data.Device;  
20 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
21 -  
22 -import java.util.List;  
23 -import java.util.UUID;  
24 -  
25 -public interface DeviceOfflineService {  
26 -  
27 - void online(Device device, boolean isUpdate);  
28 -  
29 - void offline(Device device);  
30 -  
31 - ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);  
32 -  
33 - ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);  
34 -}  
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.server.dao.device;  
17 -  
18 -import com.google.common.util.concurrent.ListenableFuture;  
19 -import org.springframework.beans.factory.annotation.Autowired;  
20 -import org.springframework.stereotype.Service;  
21 -import org.thingsboard.server.common.data.Device;  
22 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
23 -  
24 -import java.util.List;  
25 -import java.util.UUID;  
26 -  
27 -import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.OFFLINE;  
28 -import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.ONLINE;  
29 -  
30 -@Service  
31 -public class DeviceOfflineServiceImpl implements DeviceOfflineService {  
32 -  
33 - @Autowired  
34 - private DeviceDao deviceDao;  
35 -  
36 - @Override  
37 - public void online(Device device, boolean isUpdate) {  
38 - long current = System.currentTimeMillis();  
39 - device.setLastConnectTs(current);  
40 - if(isUpdate) {  
41 - device.setLastUpdateTs(current);  
42 - }  
43 - deviceDao.saveDeviceStatus(device);  
44 - }  
45 -  
46 - @Override  
47 - public void offline(Device device) {  
48 - online(device, false);  
49 - }  
50 -  
51 - @Override  
52 - public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {  
53 - DeviceStatusQuery statusQuery = new DeviceStatusQuery(OFFLINE, contactType, offlineThreshold);  
54 - return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);  
55 - }  
56 -  
57 - @Override  
58 - public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {  
59 - DeviceStatusQuery statusQuery = new DeviceStatusQuery(ONLINE, contactType, offlineThreshold);  
60 - return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);  
61 - }  
62 -}  
@@ -133,8 +133,6 @@ public class ModelConstants { @@ -133,8 +133,6 @@ public class ModelConstants {
133 public static final String DEVICE_NAME_PROPERTY = "name"; 133 public static final String DEVICE_NAME_PROPERTY = "name";
134 public static final String DEVICE_TYPE_PROPERTY = "type"; 134 public static final String DEVICE_TYPE_PROPERTY = "type";
135 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; 135 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
136 - public static final String DEVICE_LAST_CONNECT_PROPERTY = "last_connect";  
137 - public static final String DEVICE_LAST_UPDATE_PROPERTY = "last_update";  
138 136
139 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; 137 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
140 public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; 138 public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
@@ -63,12 +63,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> { @@ -63,12 +63,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
63 @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) 63 @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
64 private JsonNode additionalInfo; 64 private JsonNode additionalInfo;
65 65
66 - @Column(name = DEVICE_LAST_CONNECT_PROPERTY)  
67 - private Long lastConnectTs;  
68 -  
69 - @Column(name = DEVICE_LAST_UPDATE_PROPERTY)  
70 - private Long lastUpdateTs;  
71 -  
72 public DeviceEntity() { 66 public DeviceEntity() {
73 super(); 67 super();
74 } 68 }
@@ -86,8 +80,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> { @@ -86,8 +80,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
86 this.name = device.getName(); 80 this.name = device.getName();
87 this.type = device.getType(); 81 this.type = device.getType();
88 this.additionalInfo = device.getAdditionalInfo(); 82 this.additionalInfo = device.getAdditionalInfo();
89 - this.lastConnectTs = device.getLastConnectTs();  
90 - this.lastUpdateTs = device.getLastUpdateTs();  
91 } 83 }
92 84
93 public UUID getId() { 85 public UUID getId() {
@@ -137,23 +129,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> { @@ -137,23 +129,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
137 public void setAdditionalInfo(JsonNode additionalInfo) { 129 public void setAdditionalInfo(JsonNode additionalInfo) {
138 this.additionalInfo = additionalInfo; 130 this.additionalInfo = additionalInfo;
139 } 131 }
140 -  
141 - public Long getLastConnectTs() {  
142 - return lastConnectTs;  
143 - }  
144 -  
145 - public void setLastConnectTs(Long lastConnectTs) {  
146 - this.lastConnectTs = lastConnectTs;  
147 - }  
148 -  
149 - public Long getLastUpdateTs() {  
150 - return lastUpdateTs;  
151 - }  
152 -  
153 - public void setLastUpdateTs(Long lastUpdateTs) {  
154 - this.lastUpdateTs = lastUpdateTs;  
155 - }  
156 - 132 +
157 @Override 133 @Override
158 public String getSearchTextSource() { 134 public String getSearchTextSource() {
159 return getName(); 135 return getName();
@@ -181,8 +157,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> { @@ -181,8 +157,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
181 device.setName(name); 157 device.setName(name);
182 device.setType(type); 158 device.setType(type);
183 device.setAdditionalInfo(additionalInfo); 159 device.setAdditionalInfo(additionalInfo);
184 - device.setLastConnectTs(lastConnectTs);  
185 - device.setLastUpdateTs(lastUpdateTs);  
186 return device; 160 return device;
187 } 161 }
188 162
@@ -34,9 +34,6 @@ import javax.persistence.Column; @@ -34,9 +34,6 @@ import javax.persistence.Column;
34 import javax.persistence.Entity; 34 import javax.persistence.Entity;
35 import javax.persistence.Table; 35 import javax.persistence.Table;
36 36
37 -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_CONNECT_PROPERTY;  
38 -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_UPDATE_PROPERTY;  
39 -  
40 @Data 37 @Data
41 @EqualsAndHashCode(callSuper = true) 38 @EqualsAndHashCode(callSuper = true)
42 @Entity 39 @Entity
@@ -63,12 +60,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT @@ -63,12 +60,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
63 @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) 60 @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
64 private JsonNode additionalInfo; 61 private JsonNode additionalInfo;
65 62
66 - @Column(name = DEVICE_LAST_CONNECT_PROPERTY)  
67 - private Long lastConnectTs;  
68 -  
69 - @Column(name = DEVICE_LAST_UPDATE_PROPERTY)  
70 - private Long lastUpdateTs;  
71 -  
72 public DeviceEntity() { 63 public DeviceEntity() {
73 super(); 64 super();
74 } 65 }
@@ -86,8 +77,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT @@ -86,8 +77,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
86 this.name = device.getName(); 77 this.name = device.getName();
87 this.type = device.getType(); 78 this.type = device.getType();
88 this.additionalInfo = device.getAdditionalInfo(); 79 this.additionalInfo = device.getAdditionalInfo();
89 - this.lastConnectTs = device.getLastConnectTs();  
90 - this.lastUpdateTs = device.getLastUpdateTs();  
91 } 80 }
92 81
93 @Override 82 @Override
@@ -113,8 +102,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT @@ -113,8 +102,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
113 device.setName(name); 102 device.setName(name);
114 device.setType(type); 103 device.setType(type);
115 device.setAdditionalInfo(additionalInfo); 104 device.setAdditionalInfo(additionalInfo);
116 - device.setLastConnectTs(lastConnectTs);  
117 - device.setLastUpdateTs(lastUpdateTs);  
118 return device; 105 return device;
119 } 106 }
120 } 107 }
@@ -79,28 +79,4 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> { @@ -79,28 +79,4 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
79 List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds); 79 List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
80 80
81 List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds); 81 List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
82 -  
83 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time")  
84 - List<DeviceEntity> findConnectOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);  
85 -  
86 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time")  
87 - List<DeviceEntity> findConnectOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);  
88 -  
89 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time")  
90 - List<DeviceEntity> findUpdateOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);  
91 -  
92 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time")  
93 - List<DeviceEntity> findUpdateOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);  
94 -  
95 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time AND d.type = :type")  
96 - List<DeviceEntity> findConnectOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);  
97 -  
98 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time AND d.type = :type")  
99 - List<DeviceEntity> findConnectOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);  
100 -  
101 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time AND d.type = :type")  
102 - List<DeviceEntity> findUpdateOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);  
103 -  
104 - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time AND d.type = :type")  
105 - List<DeviceEntity> findUpdateOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);  
106 } 82 }
@@ -15,9 +15,7 @@ @@ -15,9 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.sql.device; 16 package org.thingsboard.server.dao.sql.device;
17 17
18 -import com.google.common.util.concurrent.Futures;  
19 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
20 -import lombok.extern.slf4j.Slf4j;  
21 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.data.domain.PageRequest; 20 import org.springframework.data.domain.PageRequest;
23 import org.springframework.data.repository.CrudRepository; 21 import org.springframework.data.repository.CrudRepository;
@@ -26,7 +24,6 @@ import org.thingsboard.server.common.data.Device; @@ -26,7 +24,6 @@ import org.thingsboard.server.common.data.Device;
26 import org.thingsboard.server.common.data.EntitySubtype; 24 import org.thingsboard.server.common.data.EntitySubtype;
27 import org.thingsboard.server.common.data.EntityType; 25 import org.thingsboard.server.common.data.EntityType;
28 import org.thingsboard.server.common.data.UUIDConverter; 26 import org.thingsboard.server.common.data.UUIDConverter;
29 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
30 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.common.data.page.TextPageLink; 28 import org.thingsboard.server.common.data.page.TextPageLink;
32 import org.thingsboard.server.dao.DaoUtil; 29 import org.thingsboard.server.dao.DaoUtil;
@@ -46,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; @@ -46,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
46 */ 43 */
47 @Component 44 @Component
48 @SqlDao 45 @SqlDao
49 -@Slf4j  
50 public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { 46 public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
51 47
52 @Autowired 48 @Autowired
@@ -128,73 +124,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> @@ -128,73 +124,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
128 return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); 124 return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
129 } 125 }
130 126
131 - @Override  
132 - public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {  
133 - String strTenantId = fromTimeUUID(tenantId);  
134 - long minTime = System.currentTimeMillis() - statusQuery.getThreshold();  
135 - switch (statusQuery.getStatus()) {  
136 - case OFFLINE: {  
137 - switch (statusQuery.getContactType()) {  
138 - case UPLOAD:  
139 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantId(strTenantId, minTime)));  
140 - case CONNECT:  
141 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantId(strTenantId, minTime)));  
142 - }  
143 - break;  
144 - }  
145 - case ONLINE: {  
146 - switch (statusQuery.getContactType()) {  
147 - case UPLOAD:  
148 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantId(strTenantId, minTime)));  
149 - case CONNECT:  
150 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantId(strTenantId, minTime)));  
151 - }  
152 - break;  
153 - }  
154 - }  
155 -  
156 - log.error("Could not build status query from [{}]", statusQuery);  
157 - throw new IllegalStateException("Could not build status query for device []");  
158 - }  
159 -  
160 - @Override  
161 - public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {  
162 - String strTenantId = fromTimeUUID(tenantId);  
163 - long minTime = System.currentTimeMillis() - statusQuery.getThreshold();  
164 - switch (statusQuery.getStatus()) {  
165 - case OFFLINE: {  
166 - switch (statusQuery.getContactType()) {  
167 - case UPLOAD:  
168 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantIdAndType(strTenantId, minTime, type)));  
169 - case CONNECT:  
170 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantIdAndType(strTenantId, minTime, type)));  
171 - }  
172 - break;  
173 - }  
174 - case ONLINE: {  
175 - switch (statusQuery.getContactType()) {  
176 - case UPLOAD:  
177 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantIdAndType(strTenantId, minTime, type)));  
178 - case CONNECT:  
179 - return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantIdAndType(strTenantId, minTime, type)));  
180 - }  
181 - break;  
182 - }  
183 - }  
184 -  
185 - log.error("Could not build status query from [{}]", statusQuery);  
186 - throw new IllegalStateException("Could not build status query for device []");  
187 - }  
188 -  
189 - @Override  
190 - public void saveDeviceStatus(Device device) {  
191 - ListenableFuture<Device> future = service.submit(() -> save(device));  
192 - Futures.withFallback(future, t -> {  
193 - log.error("Can't update device status for [{}]", device, t);  
194 - throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);  
195 - });  
196 - }  
197 -  
198 private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) { 127 private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
199 List<EntitySubtype> list = Collections.emptyList(); 128 List<EntitySubtype> list = Collections.emptyList();
200 if (types != null && !types.isEmpty()) { 129 if (types != null && !types.isEmpty()) {
@@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors; @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
22 import lombok.Getter; 22 import lombok.Getter;
23 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
24 import org.springframework.beans.factory.annotation.Value; 24 import org.springframework.beans.factory.annotation.Value;
25 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
26 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
27 import org.thingsboard.server.common.msg.TbMsg; 26 import org.thingsboard.server.common.msg.TbMsg;
28 import org.thingsboard.server.dao.queue.MsgQueue; 27 import org.thingsboard.server.dao.queue.MsgQueue;
@@ -30,12 +29,7 @@ import org.thingsboard.server.dao.util.SqlDao; @@ -30,12 +29,7 @@ import org.thingsboard.server.dao.util.SqlDao;
30 29
31 import javax.annotation.PostConstruct; 30 import javax.annotation.PostConstruct;
32 import javax.annotation.PreDestroy; 31 import javax.annotation.PreDestroy;
33 -import java.util.ArrayList;  
34 -import java.util.Collections;  
35 -import java.util.HashMap;  
36 -import java.util.List;  
37 -import java.util.Map;  
38 -import java.util.UUID; 32 +import java.util.*;
39 import java.util.concurrent.ExecutionException; 33 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.Executors; 34 import java.util.concurrent.Executors;
41 import java.util.concurrent.atomic.AtomicLong; 35 import java.util.concurrent.atomic.AtomicLong;
@@ -72,13 +66,13 @@ public class InMemoryMsgQueue implements MsgQueue { @@ -72,13 +66,13 @@ public class InMemoryMsgQueue implements MsgQueue {
72 66
73 @Override 67 @Override
74 public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) { 68 public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) {
75 - if (pendingMsgCount.get() < maxSize) { 69 + if (pendingMsgCount.incrementAndGet() < maxSize) {
76 return queueExecutor.submit(() -> { 70 return queueExecutor.submit(() -> {
77 data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); 71 data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg);
78 - pendingMsgCount.incrementAndGet();  
79 return null; 72 return null;
80 }); 73 });
81 } else { 74 } else {
  75 + pendingMsgCount.decrementAndGet();
82 return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); 76 return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!"));
83 } 77 }
84 } 78 }
@@ -159,8 +159,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.device ( @@ -159,8 +159,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
159 type text, 159 type text,
160 search_text text, 160 search_text text,
161 additional_info text, 161 additional_info text,
162 - last_connect bigint,  
163 - last_update bigint,  
164 PRIMARY KEY (id, tenant_id, customer_id, type) 162 PRIMARY KEY (id, tenant_id, customer_id, type)
165 ); 163 );
166 164
@@ -118,9 +118,7 @@ CREATE TABLE IF NOT EXISTS device ( @@ -118,9 +118,7 @@ CREATE TABLE IF NOT EXISTS device (
118 type varchar(255), 118 type varchar(255),
119 name varchar(255), 119 name varchar(255),
120 search_text varchar(255), 120 search_text varchar(255),
121 - tenant_id varchar(31),  
122 - last_connect bigint,  
123 - last_update bigint 121 + tenant_id varchar(31)
124 ); 122 );
125 123
126 CREATE TABLE IF NOT EXISTS device_credentials ( 124 CREATE TABLE IF NOT EXISTS device_credentials (
@@ -31,5 +31,7 @@ public class NodeDefinition { @@ -31,5 +31,7 @@ public class NodeDefinition {
31 JsonNode defaultConfiguration; 31 JsonNode defaultConfiguration;
32 String[] uiResources; 32 String[] uiResources;
33 String configDirective; 33 String configDirective;
  34 + String icon;
  35 + String iconUrl;
34 36
35 } 37 }
@@ -49,6 +49,10 @@ public @interface RuleNode { @@ -49,6 +49,10 @@ public @interface RuleNode {
49 49
50 String configDirective() default ""; 50 String configDirective() default "";
51 51
  52 + String icon() default "";
  53 +
  54 + String iconUrl() default "";
  55 +
52 boolean customRelations() default false; 56 boolean customRelations() default false;
53 57
54 } 58 }
@@ -40,8 +40,6 @@ import java.util.Set; @@ -40,8 +40,6 @@ import java.util.Set;
40 */ 40 */
41 public interface TbContext { 41 public interface TbContext {
42 42
43 - void tellNext(TbMsg msg);  
44 -  
45 void tellNext(TbMsg msg, String relationType); 43 void tellNext(TbMsg msg, String relationType);
46 44
47 void tellNext(TbMsg msg, String relationType, Throwable th); 45 void tellNext(TbMsg msg, String relationType, Throwable th);
@@ -54,8 +52,6 @@ public interface TbContext { @@ -54,8 +52,6 @@ public interface TbContext {
54 52
55 void tellSibling(TbMsg msg, ServerAddress address); 53 void tellSibling(TbMsg msg, ServerAddress address);
56 54
57 - void spawn(TbMsg msg);  
58 -  
59 void ack(TbMsg msg); 55 void ack(TbMsg msg);
60 56
61 void tellError(TbMsg msg, Throwable th); 57 void tellError(TbMsg msg, Throwable th);
@@ -73,16 +73,6 @@ @@ -73,16 +73,6 @@
73 <artifactId>guava</artifactId> 73 <artifactId>guava</artifactId>
74 </dependency> 74 </dependency>
75 <dependency> 75 <dependency>
76 - <groupId>org.apache.velocity</groupId>  
77 - <artifactId>velocity</artifactId>  
78 - <scope>provided</scope>  
79 - </dependency>  
80 - <dependency>  
81 - <groupId>org.apache.velocity</groupId>  
82 - <artifactId>velocity-tools</artifactId>  
83 - <scope>provided</scope>  
84 - </dependency>  
85 - <dependency>  
86 <groupId>org.springframework</groupId> 76 <groupId>org.springframework</groupId>
87 <artifactId>spring-web</artifactId> 77 <artifactId>spring-web</artifactId>
88 <scope>provided</scope> 78 <scope>provided</scope>
@@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
47 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + 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>", 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"}, 49 uiResources = {"static/rulenode/rulenode-core-config.js"},
50 - configDirective = "tbActionNodeAlarmConfig") 50 + configDirective = "tbActionNodeAlarmConfig",
  51 + icon = "notifications_active"
  52 +)
51 53
52 public class TbAlarmNode implements TbNode { 54 public class TbAlarmNode implements TbNode {
53 55
@@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
22 import org.thingsboard.server.common.msg.TbMsg; 22 import org.thingsboard.server.common.msg.TbMsg;
23 23
24 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 24 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  25 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
25 26
26 @Slf4j 27 @Slf4j
27 @RuleNode( 28 @RuleNode(
@@ -29,11 +30,13 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -29,11 +30,13 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
29 name = "log", 30 name = "log",
30 configClazz = TbLogNodeConfiguration.class, 31 configClazz = TbLogNodeConfiguration.class,
31 nodeDescription = "Log incoming messages using JS script for transformation Message into String", 32 nodeDescription = "Log incoming messages using JS script for transformation Message into String",
32 - nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + 33 + nodeDetails = "Transform incoming Message with configured JS function to String and log final value into Thingsboard log file. " +
33 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + 34 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
34 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", 35 "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
35 uiResources = {"static/rulenode/rulenode-core-config.js"}, 36 uiResources = {"static/rulenode/rulenode-core-config.js"},
36 - configDirective = "tbActionNodeLogConfig") 37 + configDirective = "tbActionNodeLogConfig",
  38 + icon = "menu"
  39 +)
37 40
38 public class TbLogNode implements TbNode { 41 public class TbLogNode implements TbNode {
39 42
@@ -52,7 +55,7 @@ public class TbLogNode implements TbNode { @@ -52,7 +55,7 @@ public class TbLogNode implements TbNode {
52 withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), 55 withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)),
53 toString -> { 56 toString -> {
54 log.info(toString); 57 log.info(toString);
55 - ctx.tellNext(msg); 58 + ctx.tellNext(msg, SUCCESS);
56 }, 59 },
57 t -> ctx.tellError(msg, t)); 60 t -> ctx.tellError(msg, t));
58 } 61 }
@@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
38 38
39 @Slf4j 39 @Slf4j
40 @RuleNode( 40 @RuleNode(
41 - type = ComponentType.ACTION, 41 + type = ComponentType.EXTERNAL,
42 name = "aws sns", 42 name = "aws sns",
43 configClazz = TbSnsNodeConfiguration.class, 43 configClazz = TbSnsNodeConfiguration.class,
44 nodeDescription = "Publish messages to AWS SNS", 44 nodeDescription = "Publish messages to AWS SNS",
45 nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.", 45 nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.",
46 uiResources = {"static/rulenode/rulenode-core-config.js"}, 46 uiResources = {"static/rulenode/rulenode-core-config.js"},
47 - configDirective = "tbActionNodeSnsConfig" 47 + configDirective = "tbActionNodeSnsConfig",
  48 + iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
48 ) 49 )
49 public class TbSnsNode implements TbNode { 50 public class TbSnsNode implements TbNode {
50 51
@@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
41 41
42 @Slf4j 42 @Slf4j
43 @RuleNode( 43 @RuleNode(
44 - type = ComponentType.ACTION, 44 + type = ComponentType.EXTERNAL,
45 name = "aws sqs", 45 name = "aws sqs",
46 configClazz = TbSqsNodeConfiguration.class, 46 configClazz = TbSqsNodeConfiguration.class,
47 nodeDescription = "Publish messages to AWS SQS", 47 nodeDescription = "Publish messages to AWS SQS",
48 nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.", 48 nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.",
49 uiResources = {"static/rulenode/rulenode-core-config.js"}, 49 uiResources = {"static/rulenode/rulenode-core-config.js"},
50 - configDirective = "tbActionNodeSqsConfig" 50 + configDirective = "tbActionNodeSqsConfig",
  51 + iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
51 ) 52 )
52 public class TbSqsNode implements TbNode { 53 public class TbSqsNode implements TbNode {
53 54
@@ -30,6 +30,7 @@ import java.util.UUID; @@ -30,6 +30,7 @@ import java.util.UUID;
30 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.TimeUnit;
31 31
32 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 32 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  33 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
33 34
34 @Slf4j 35 @Slf4j
35 @RuleNode( 36 @RuleNode(
@@ -37,10 +38,11 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -37,10 +38,11 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
37 name = "generator", 38 name = "generator",
38 configClazz = TbMsgGeneratorNodeConfiguration.class, 39 configClazz = TbMsgGeneratorNodeConfiguration.class,
39 nodeDescription = "Periodically generates messages", 40 nodeDescription = "Periodically generates messages",
40 - nodeDetails = "Generates messages with configurable period. ", 41 + nodeDetails = "Generates messages with configurable period. Javascript function used fore message generation.",
41 inEnabled = false, 42 inEnabled = false,
42 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 43 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
43 - configDirective = "tbActionNodeGeneratorConfig" 44 + configDirective = "tbActionNodeGeneratorConfig",
  45 + icon = "repeat"
44 ) 46 )
45 47
46 public class TbMsgGeneratorNode implements TbNode { 48 public class TbMsgGeneratorNode implements TbNode {
@@ -71,7 +73,7 @@ public class TbMsgGeneratorNode implements TbNode { @@ -71,7 +73,7 @@ public class TbMsgGeneratorNode implements TbNode {
71 public void onMsg(TbContext ctx, TbMsg msg) { 73 public void onMsg(TbContext ctx, TbMsg msg) {
72 if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { 74 if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) {
73 withCallback(generate(ctx), 75 withCallback(generate(ctx),
74 - m -> {ctx.tellNext(m); sentTickMsg(ctx);}, 76 + m -> {ctx.tellNext(m, SUCCESS); sentTickMsg(ctx);},
75 t -> {ctx.tellError(msg, t); sentTickMsg(ctx);}); 77 t -> {ctx.tellError(msg, t); sentTickMsg(ctx);});
76 } 78 }
77 } 79 }
@@ -26,7 +26,8 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter @@ -26,7 +26,8 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter
26 @Override 26 @Override
27 public TbJsFilterNodeConfiguration defaultConfiguration() { 27 public TbJsFilterNodeConfiguration defaultConfiguration() {
28 TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); 28 TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
29 - configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;"); 29 + configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' " +
  30 + "&& metadata.temp == 10 && msg.bigObj.prop == 42 && msgType === 'POST_TELEMETRY';");
30 return configuration; 31 return configuration;
31 } 32 }
32 } 33 }
@@ -29,6 +29,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -29,6 +29,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
29 @RuleNode( 29 @RuleNode(
30 type = ComponentType.FILTER, 30 type = ComponentType.FILTER,
31 name = "switch", customRelations = true, 31 name = "switch", customRelations = true,
  32 + relationTypes = {},
32 configClazz = TbJsSwitchNodeConfiguration.class, 33 configClazz = TbJsSwitchNodeConfiguration.class,
33 nodeDescription = "Route incoming Message to one or multiple output chains", 34 nodeDescription = "Route incoming Message to one or multiple output chains",
34 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + 35 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +
@@ -30,9 +30,11 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch @@ -30,9 +30,11 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch
30 public TbJsSwitchNodeConfiguration defaultConfiguration() { 30 public TbJsSwitchNodeConfiguration defaultConfiguration() {
31 TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); 31 TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
32 configuration.setJsScript("function nextRelation(metadata, msg) {\n" + 32 configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
33 - " return ['one','nine'];" + 33 + " return ['one','nine'];\n" +
34 "};\n" + 34 "};\n" +
35 - "\n" + 35 + "if(msgType === 'POST_TELEMETRY') {\n" +
  36 + " return ['two'];\n" +
  37 + "}\n" +
36 "return nextRelation(metadata, msg);"); 38 "return nextRelation(metadata, msg);");
37 return configuration; 39 return configuration;
38 } 40 }
@@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg;
31 configClazz = TbMsgTypeFilterNodeConfiguration.class, 31 configClazz = TbMsgTypeFilterNodeConfiguration.class,
32 relationTypes = {"True", "False"}, 32 relationTypes = {"True", "False"},
33 nodeDescription = "Filter incoming messages by Message Type", 33 nodeDescription = "Filter incoming messages by Message Type",
34 - nodeDetails = "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.", 34 + nodeDetails = "If incoming MessageType is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
35 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 35 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
36 configDirective = "tbFilterNodeMessageTypeConfig") 36 configDirective = "tbFilterNodeMessageTypeConfig")
37 public class TbMsgTypeFilterNode implements TbNode { 37 public class TbMsgTypeFilterNode implements TbNode {
@@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter; @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.rule.engine.api.NodeConfiguration; 19 import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +import org.thingsboard.server.common.msg.session.SessionMsgType;
20 21
21 import java.util.Arrays; 22 import java.util.Arrays;
22 import java.util.Collections; 23 import java.util.Collections;
@@ -33,7 +34,10 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsg @@ -33,7 +34,10 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsg
33 @Override 34 @Override
34 public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { 35 public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
35 TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); 36 TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
36 - configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); 37 + configuration.setMessageTypes(Arrays.asList(
  38 + SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
  39 + SessionMsgType.POST_TELEMETRY_REQUEST.name(),
  40 + SessionMsgType.TO_SERVER_RPC_REQUEST.name()));
37 return configuration; 41 return configuration;
38 } 42 }
39 } 43 }
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
30 configClazz = EmptyNodeConfiguration.class, 30 configClazz = EmptyNodeConfiguration.class,
31 relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"}, 31 relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"},
32 nodeDescription = "Route incoming messages by Message Type", 32 nodeDescription = "Route incoming messages by Message Type",
33 - nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> via corresponding chain, otherwise <b>Other</b> chain is used.", 33 + nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
34 uiResources = {"static/rulenode/rulenode-core-config.js"}, 34 uiResources = {"static/rulenode/rulenode-core-config.js"},
35 configDirective = "tbNodeEmptyConfig") 35 configDirective = "tbNodeEmptyConfig")
36 public class TbMsgTypeSwitchNode implements TbNode { 36 public class TbMsgTypeSwitchNode implements TbNode {
@@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException; @@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException;
28 28
29 @Slf4j 29 @Slf4j
30 @RuleNode( 30 @RuleNode(
31 - type = ComponentType.ACTION, 31 + type = ComponentType.EXTERNAL,
32 name = "kafka", 32 name = "kafka",
33 configClazz = TbKafkaNodeConfiguration.class, 33 configClazz = TbKafkaNodeConfiguration.class,
34 nodeDescription = "Publish messages to Kafka server", 34 nodeDescription = "Publish messages to Kafka server",
35 nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.", 35 nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.",
36 uiResources = {"static/rulenode/rulenode-core-config.js"}, 36 uiResources = {"static/rulenode/rulenode-core-config.js"},
37 - configDirective = "tbActionNodeKafkaConfig" 37 + configDirective = "tbActionNodeKafkaConfig",
  38 + iconUrl = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzOCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDQxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIwMS44MTYgMjMwLjIxNmMtMTYuMTg2IDAtMzAuNjk3IDcuMTcxLTQwLjYzNCAxOC40NjFsLTI1LjQ2My0xOC4wMjZjMi43MDMtNy40NDIgNC4yNTUtMTUuNDMzIDQuMjU1LTIzLjc5NyAwLTguMjE5LTEuNDk4LTE2LjA3Ni00LjExMi0yMy40MDhsMjUuNDA2LTE3LjgzNWM5LjkzNiAxMS4yMzMgMjQuNDA5IDE4LjM2NSA0MC41NDggMTguMzY1IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI5Ljg3OS0yNC4zMDktNTQuMTg0LTU0LjE4NC01NC4xODQtMjkuODc1IDAtNTQuMTg0IDI0LjMwNS01NC4xODQgNTQuMTg0IDAgNS4zNDguODA4IDEwLjUwNSAyLjI1OCAxNS4zODlsLTI1LjQyMyAxNy44NDRjLTEwLjYyLTEzLjE3NS0yNS45MTEtMjIuMzc0LTQzLjMzMy0yNS4xODJ2LTMwLjY0YzI0LjU0NC01LjE1NSA0My4wMzctMjYuOTYyIDQzLjAzNy01My4wMTlDMTI0LjE3MSAyNC4zMDUgOTkuODYyIDAgNjkuOTg3IDAgNDAuMTEyIDAgMTUuODAzIDI0LjMwNSAxNS44MDMgNTQuMTg0YzAgMjUuNzA4IDE4LjAxNCA0Ny4yNDYgNDIuMDY3IDUyLjc2OXYzMS4wMzhDMjUuMDQ0IDE0My43NTMgMCAxNzIuNDAxIDAgMjA2Ljg1NGMwIDM0LjYyMSAyNS4yOTIgNjMuMzc0IDU4LjM1NSA2OC45NHYzMi43NzRjLTI0LjI5OSA1LjM0MS00Mi41NTIgMjcuMDExLTQyLjU1MiA1Mi44OTQgMCAyOS44NzkgMjQuMzA5IDU0LjE4NCA1NC4xODQgNTQuMTg0IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI1Ljg4My0xOC4yNTMtNDcuNTUzLTQyLjU1Mi01Mi44OTR2LTMyLjc3NWE2OS45NjUgNjkuOTY1IDAgMCAwIDQyLjYtMjQuNzc2bDI1LjYzMyAxOC4xNDNjLTEuNDIzIDQuODQtMi4yMiA5Ljk0Ni0yLjIyIDE1LjI0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0em0wLTEyNi42OTVjMTQuNDg3IDAgMjYuMjcgMTEuNzg4IDI2LjI3IDI2LjI3MXMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3LTI2LjI3LTExLjc4Ny0yNi4yNy0yNi4yN2MwLTE0LjQ4MyAxMS43ODMtMjYuMjcxIDI2LjI3LTI2LjI3MXptLTE1OC4xLTQ5LjMzN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3em01Mi41NDEgMzA3LjI3OGMwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3YzAtMTQuNDgzIDExLjc4NC0yNi4yNyAyNi4yNzEtMjYuMjdzMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3em0tMjYuMjcyLTExNy45N2MtMjAuMjA1IDAtMzYuNjQyLTE2LjQzNC0zNi42NDItMzYuNjM4IDAtMjAuMjA1IDE2LjQzNy0zNi42NDIgMzYuNjQyLTM2LjY0MiAyMC4yMDQgMCAzNi42NDEgMTYuNDM3IDM2LjY0MSAzNi42NDIgMCAyMC4yMDQtMTYuNDM3IDM2LjYzOC0zNi42NDEgMzYuNjM4em0xMzEuODMxIDY3LjE3OWMtMTQuNDg3IDAtMjYuMjctMTEuNzg4LTI2LjI3LTI2LjI3MXMxMS43ODMtMjYuMjcgMjYuMjctMjYuMjcgMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3YzAgMTQuNDgzLTExLjc4MyAyNi4yNzEtMjYuMjcgMjYuMjcxeiIvPjwvc3ZnPg=="
38 ) 39 )
39 public class TbKafkaNode implements TbNode { 40 public class TbKafkaNode implements TbNode {
40 41
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.mail;  
17 -  
18 -import com.fasterxml.jackson.databind.ObjectMapper;  
19 -import org.apache.velocity.Template;  
20 -import org.apache.velocity.VelocityContext;  
21 -import org.apache.velocity.runtime.RuntimeServices;  
22 -import org.apache.velocity.runtime.RuntimeSingleton;  
23 -import org.apache.velocity.runtime.parser.ParseException;  
24 -import org.apache.velocity.runtime.parser.node.SimpleNode;  
25 -import org.thingsboard.server.common.msg.TbMsg;  
26 -  
27 -import java.io.IOException;  
28 -import java.io.StringReader;  
29 -import java.io.StringWriter;  
30 -import java.util.Map;  
31 -  
32 -import static org.thingsboard.server.common.msg.TbMsgDataType.JSON;  
33 -  
34 -public class RuleVelocityUtils {  
35 -  
36 - public static VelocityContext createContext(TbMsg msg) throws IOException {  
37 - VelocityContext context = new VelocityContext();  
38 - context.put("originator", msg.getOriginator());  
39 - context.put("type", msg.getType());  
40 - context.put("metadata", msg.getMetaData().values());  
41 - if (msg.getDataType() == JSON) {  
42 - Map map = new ObjectMapper().readValue(msg.getData(), Map.class);  
43 - context.put("msg", map);  
44 - } else {  
45 - context.put("msg", msg.getData());  
46 - }  
47 - return context;  
48 - }  
49 -  
50 - public static String merge(Template template, VelocityContext context) {  
51 - StringWriter writer = new StringWriter();  
52 - template.merge(context, writer);  
53 - return writer.toString();  
54 - }  
55 -  
56 - public static Template create(String source, String templateName) throws ParseException {  
57 - RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();  
58 - StringReader reader = new StringReader(source);  
59 - SimpleNode node = runtimeServices.parse(reader, templateName);  
60 - Template template = new Template();  
61 - template.setRuntimeServices(runtimeServices);  
62 - template.setData(node);  
63 - template.initDocument();  
64 - return template;  
65 - }  
66 -  
67 -  
68 -}  
@@ -18,18 +18,16 @@ package org.thingsboard.rule.engine.mail; @@ -18,18 +18,16 @@ package org.thingsboard.rule.engine.mail;
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 -import org.apache.velocity.Template;  
22 -import org.apache.velocity.VelocityContext;  
23 -import org.apache.velocity.runtime.parser.ParseException;  
24 import org.springframework.util.StringUtils; 21 import org.springframework.util.StringUtils;
25 import org.thingsboard.rule.engine.TbNodeUtils; 22 import org.thingsboard.rule.engine.TbNodeUtils;
26 import org.thingsboard.rule.engine.api.*; 23 import org.thingsboard.rule.engine.api.*;
27 import org.thingsboard.server.common.data.plugin.ComponentType; 24 import org.thingsboard.server.common.data.plugin.ComponentType;
28 import org.thingsboard.server.common.msg.TbMsg; 25 import org.thingsboard.server.common.msg.TbMsg;
  26 +import org.thingsboard.server.common.msg.TbMsgMetaData;
29 27
30 import java.io.IOException; 28 import java.io.IOException;
31 -import java.util.Optional;  
32 29
  30 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
33 import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; 31 import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
34 32
35 @Slf4j 33 @Slf4j
@@ -41,34 +39,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @@ -41,34 +39,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
41 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + 39 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
42 "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", 40 "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
43 uiResources = {"static/rulenode/rulenode-core-config.js"}, 41 uiResources = {"static/rulenode/rulenode-core-config.js"},
44 - configDirective = "tbTransformationNodeToEmailConfig") 42 + configDirective = "tbTransformationNodeToEmailConfig",
  43 + icon = "email"
  44 +)
45 public class TbMsgToEmailNode implements TbNode { 45 public class TbMsgToEmailNode implements TbNode {
46 46
47 private static final ObjectMapper MAPPER = new ObjectMapper(); 47 private static final ObjectMapper MAPPER = new ObjectMapper();
48 48
49 private TbMsgToEmailNodeConfiguration config; 49 private TbMsgToEmailNodeConfiguration config;
50 50
51 - private Optional<Template> fromTemplate;  
52 - private Optional<Template> toTemplate;  
53 - private Optional<Template> ccTemplate;  
54 - private Optional<Template> bccTemplate;  
55 - private Optional<Template> subjectTemplate;  
56 - private Optional<Template> bodyTemplate;  
57 -  
58 @Override 51 @Override
59 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 52 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
60 this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); 53 this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class);
61 - try {  
62 - fromTemplate = toTemplate(config.getFromTemplate(), "From Template");  
63 - toTemplate = toTemplate(config.getToTemplate(), "To Template");  
64 - ccTemplate = toTemplate(config.getCcTemplate(), "Cc Template");  
65 - bccTemplate = toTemplate(config.getBccTemplate(), "Bcc Template");  
66 - subjectTemplate = toTemplate(config.getSubjectTemplate(), "Subject Template");  
67 - bodyTemplate = toTemplate(config.getBodyTemplate(), "Body Template");  
68 - } catch (ParseException e) {  
69 - log.error("Failed to create templates based on provided configuration!", e);  
70 - throw new TbNodeException(e);  
71 - }  
72 } 54 }
73 55
74 @Override 56 @Override
@@ -76,7 +58,7 @@ public class TbMsgToEmailNode implements TbNode { @@ -76,7 +58,7 @@ public class TbMsgToEmailNode implements TbNode {
76 try { 58 try {
77 EmailPojo email = convert(msg); 59 EmailPojo email = convert(msg);
78 TbMsg emailMsg = buildEmailMsg(ctx, msg, email); 60 TbMsg emailMsg = buildEmailMsg(ctx, msg, email);
79 - ctx.tellNext(emailMsg); 61 + ctx.tellNext(emailMsg, SUCCESS);
80 } catch (Exception ex) { 62 } catch (Exception ex) {
81 log.warn("Can not convert message to email " + ex.getMessage()); 63 log.warn("Can not convert message to email " + ex.getMessage());
82 ctx.tellError(msg, ex); 64 ctx.tellError(msg, ex);
@@ -90,21 +72,20 @@ public class TbMsgToEmailNode implements TbNode { @@ -90,21 +72,20 @@ public class TbMsgToEmailNode implements TbNode {
90 72
91 private EmailPojo convert(TbMsg msg) throws IOException { 73 private EmailPojo convert(TbMsg msg) throws IOException {
92 EmailPojo.EmailPojoBuilder builder = EmailPojo.builder(); 74 EmailPojo.EmailPojoBuilder builder = EmailPojo.builder();
93 - VelocityContext context = RuleVelocityUtils.createContext(msg);  
94 - fromTemplate.ifPresent(t -> builder.from(RuleVelocityUtils.merge(t, context)));  
95 - toTemplate.ifPresent(t -> builder.to(RuleVelocityUtils.merge(t, context)));  
96 - ccTemplate.ifPresent(t -> builder.cc(RuleVelocityUtils.merge(t, context)));  
97 - bccTemplate.ifPresent(t -> builder.bcc(RuleVelocityUtils.merge(t, context)));  
98 - subjectTemplate.ifPresent(t -> builder.subject(RuleVelocityUtils.merge(t, context)));  
99 - bodyTemplate.ifPresent(t -> builder.body(RuleVelocityUtils.merge(t, context))); 75 + builder.from(fromTemplate(this.config.getFromTemplate(), msg.getMetaData()));
  76 + builder.to(fromTemplate(this.config.getToTemplate(), msg.getMetaData()));
  77 + builder.cc(fromTemplate(this.config.getCcTemplate(), msg.getMetaData()));
  78 + builder.bcc(fromTemplate(this.config.getBccTemplate(), msg.getMetaData()));
  79 + builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg.getMetaData()));
  80 + builder.body(fromTemplate(this.config.getBodyTemplate(), msg.getMetaData()));
100 return builder.build(); 81 return builder.build();
101 } 82 }
102 83
103 - private Optional<Template> toTemplate(String source, String name) throws ParseException {  
104 - if (!StringUtils.isEmpty(source)) {  
105 - return Optional.of(RuleVelocityUtils.create(source, name)); 84 + private String fromTemplate(String template, TbMsgMetaData metaData) {
  85 + if (!StringUtils.isEmpty(template)) {
  86 + return TbNodeUtils.processPattern(template, metaData);
106 } else { 87 } else {
107 - return Optional.empty(); 88 + return null;
108 } 89 }
109 } 90 }
110 91
@@ -32,10 +32,9 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { @@ -32,10 +32,9 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration {
32 public TbMsgToEmailNodeConfiguration defaultConfiguration() { 32 public TbMsgToEmailNodeConfiguration defaultConfiguration() {
33 TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); 33 TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration();
34 configuration.fromTemplate = "info@testmail.org"; 34 configuration.fromTemplate = "info@testmail.org";
35 - configuration.toTemplate = "$metadata.userEmail";  
36 - configuration.subjectTemplate = "Device $deviceType temperature high";  
37 - configuration.bodyTemplate = "Device $metadata.deviceName has high temperature $msg.temp";  
38 - 35 + configuration.toTemplate = "${userEmail}";
  36 + configuration.subjectTemplate = "Device ${deviceType} temperature high";
  37 + configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}";
39 return configuration; 38 return configuration;
40 } 39 }
41 } 40 }
@@ -15,37 +15,57 @@ @@ -15,37 +15,57 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.mail; 16 package org.thingsboard.rule.engine.mail;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
18 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.ListenableFuture;
19 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
20 import org.apache.commons.lang3.StringUtils; 22 import org.apache.commons.lang3.StringUtils;
  23 +import org.springframework.mail.javamail.JavaMailSenderImpl;
  24 +import org.springframework.mail.javamail.MimeMessageHelper;
21 import org.thingsboard.rule.engine.TbNodeUtils; 25 import org.thingsboard.rule.engine.TbNodeUtils;
22 import org.thingsboard.rule.engine.api.*; 26 import org.thingsboard.rule.engine.api.*;
23 import org.thingsboard.server.common.data.plugin.ComponentType; 27 import org.thingsboard.server.common.data.plugin.ComponentType;
24 import org.thingsboard.server.common.msg.TbMsg; 28 import org.thingsboard.server.common.msg.TbMsg;
25 29
  30 +import javax.mail.internet.MimeMessage;
26 import java.io.IOException; 31 import java.io.IOException;
  32 +import java.util.Properties;
27 33
28 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 34 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  35 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
29 36
30 @Slf4j 37 @Slf4j
31 @RuleNode( 38 @RuleNode(
32 - type = ComponentType.ACTION, 39 + type = ComponentType.EXTERNAL,
33 name = "send email", 40 name = "send email",
34 configClazz = TbSendEmailNodeConfiguration.class, 41 configClazz = TbSendEmailNodeConfiguration.class,
35 nodeDescription = "Log incoming messages using JS script for transformation Message into String", 42 nodeDescription = "Log incoming messages using JS script for transformation Message into String",
36 nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + 43 nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " +
37 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + 44 "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
38 - "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>") 45 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
  46 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  47 + configDirective = "tbActionNodeSendEmailConfig",
  48 + icon = "send"
  49 +)
39 public class TbSendEmailNode implements TbNode { 50 public class TbSendEmailNode implements TbNode {
40 51
  52 + private static final String MAIL_PROP = "mail.";
41 static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; 53 static final String SEND_EMAIL_TYPE = "SEND_EMAIL";
42 private static final ObjectMapper MAPPER = new ObjectMapper(); 54 private static final ObjectMapper MAPPER = new ObjectMapper();
43 55
44 private TbSendEmailNodeConfiguration config; 56 private TbSendEmailNodeConfiguration config;
  57 + private JavaMailSenderImpl mailSender;
45 58
46 @Override 59 @Override
47 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 60 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
48 - this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class); 61 + try {
  62 + this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class);
  63 + if (!this.config.isUseSystemSmtpSettings()) {
  64 + mailSender = createMailSender();
  65 + }
  66 + } catch (Exception e) {
  67 + throw new TbNodeException(e);
  68 + }
49 } 69 }
50 70
51 @Override 71 @Override
@@ -54,17 +74,37 @@ public class TbSendEmailNode implements TbNode { @@ -54,17 +74,37 @@ public class TbSendEmailNode implements TbNode {
54 validateType(msg.getType()); 74 validateType(msg.getType());
55 EmailPojo email = getEmail(msg); 75 EmailPojo email = getEmail(msg);
56 withCallback(ctx.getMailExecutor().executeAsync(() -> { 76 withCallback(ctx.getMailExecutor().executeAsync(() -> {
57 - ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(),  
58 - email.getBcc(), email.getSubject(), email.getBody()); 77 + sendEmail(ctx, email);
59 return null; 78 return null;
60 }), 79 }),
61 - ok -> ctx.tellNext(msg), 80 + ok -> ctx.tellNext(msg, SUCCESS),
62 fail -> ctx.tellError(msg, fail)); 81 fail -> ctx.tellError(msg, fail));
63 } catch (Exception ex) { 82 } catch (Exception ex) {
64 ctx.tellError(msg, ex); 83 ctx.tellError(msg, ex);
65 } 84 }
66 } 85 }
67 86
  87 + private void sendEmail(TbContext ctx, EmailPojo email) throws Exception {
  88 + if (this.config.isUseSystemSmtpSettings()) {
  89 + ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(),
  90 + email.getBcc(), email.getSubject(), email.getBody());
  91 + } else {
  92 + MimeMessage mailMsg = mailSender.createMimeMessage();
  93 + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8");
  94 + helper.setFrom(email.getFrom());
  95 + helper.setTo(email.getTo().split("\\s*,\\s*"));
  96 + if (!StringUtils.isBlank(email.getCc())) {
  97 + helper.setCc(email.getCc().split("\\s*,\\s*"));
  98 + }
  99 + if (!StringUtils.isBlank(email.getBcc())) {
  100 + helper.setBcc(email.getBcc().split("\\s*,\\s*"));
  101 + }
  102 + helper.setSubject(email.getSubject());
  103 + helper.setText(email.getBody());
  104 + mailSender.send(helper.getMimeMessage());
  105 + }
  106 + }
  107 +
68 private EmailPojo getEmail(TbMsg msg) throws IOException { 108 private EmailPojo getEmail(TbMsg msg) throws IOException {
69 EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class); 109 EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class);
70 if (StringUtils.isBlank(email.getTo())) { 110 if (StringUtils.isBlank(email.getTo())) {
@@ -83,4 +123,26 @@ public class TbSendEmailNode implements TbNode { @@ -83,4 +123,26 @@ public class TbSendEmailNode implements TbNode {
83 @Override 123 @Override
84 public void destroy() { 124 public void destroy() {
85 } 125 }
  126 +
  127 + private JavaMailSenderImpl createMailSender() {
  128 + JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
  129 + mailSender.setHost(this.config.getSmtpHost());
  130 + mailSender.setPort(this.config.getSmtpPort());
  131 + mailSender.setUsername(this.config.getUsername());
  132 + mailSender.setPassword(this.config.getPassword());
  133 + mailSender.setJavaMailProperties(createJavaMailProperties());
  134 + return mailSender;
  135 + }
  136 +
  137 + private Properties createJavaMailProperties() {
  138 + Properties javaMailProperties = new Properties();
  139 + String protocol = this.config.getSmtpProtocol();
  140 + javaMailProperties.put("mail.transport.protocol", protocol);
  141 + javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost());
  142 + javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort()+"");
  143 + javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout()+"");
  144 + javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername())));
  145 + javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString());
  146 + return javaMailProperties;
  147 + }
86 } 148 }
@@ -21,12 +21,24 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; @@ -21,12 +21,24 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
21 @Data 21 @Data
22 public class TbSendEmailNodeConfiguration implements NodeConfiguration { 22 public class TbSendEmailNodeConfiguration implements NodeConfiguration {
23 23
24 - private String tmp; 24 + private boolean useSystemSmtpSettings;
  25 + private String smtpHost;
  26 + private int smtpPort;
  27 + private String username;
  28 + private String password;
  29 + private String smtpProtocol;
  30 + private int timeout;
  31 + private boolean enableTls;
25 32
26 @Override 33 @Override
27 public TbSendEmailNodeConfiguration defaultConfiguration() { 34 public TbSendEmailNodeConfiguration defaultConfiguration() {
28 TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration(); 35 TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration();
29 - configuration.tmp = ""; 36 + configuration.setUseSystemSmtpSettings(true);
  37 + configuration.setSmtpHost("localhost");
  38 + configuration.setSmtpProtocol("smtp");
  39 + configuration.setSmtpPort(25);
  40 + configuration.setTimeout(10000);
  41 + configuration.setEnableTls(false);
30 return configuration; 42 return configuration;
31 } 43 }
32 } 44 }
@@ -18,8 +18,12 @@ package org.thingsboard.rule.engine.metadata; @@ -18,8 +18,12 @@ package org.thingsboard.rule.engine.metadata;
18 import com.google.common.base.Function; 18 import com.google.common.base.Function;
19 import com.google.common.util.concurrent.Futures; 19 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture; 20 import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
21 import org.thingsboard.rule.engine.TbNodeUtils; 22 import org.thingsboard.rule.engine.TbNodeUtils;
22 -import org.thingsboard.rule.engine.api.*; 23 +import org.thingsboard.rule.engine.api.TbContext;
  24 +import org.thingsboard.rule.engine.api.TbNode;
  25 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  26 +import org.thingsboard.rule.engine.api.TbNodeException;
23 import org.thingsboard.server.common.data.id.EntityId; 27 import org.thingsboard.server.common.data.id.EntityId;
24 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
25 import org.thingsboard.server.common.data.kv.KvEntry; 29 import org.thingsboard.server.common.data.kv.KvEntry;
@@ -30,8 +34,11 @@ import java.util.List; @@ -30,8 +34,11 @@ import java.util.List;
30 import java.util.stream.Collectors; 34 import java.util.stream.Collectors;
31 35
32 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 36 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  37 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
  38 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
33 import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; 39 import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
34 40
  41 +@Slf4j
35 public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode { 42 public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode {
36 43
37 private TbGetEntityAttrNodeConfiguration config; 44 private TbGetEntityAttrNodeConfiguration config;
@@ -46,17 +53,24 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode @@ -46,17 +53,24 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode
46 try { 53 try {
47 withCallback( 54 withCallback(
48 findEntityAsync(ctx, msg.getOriginator()), 55 findEntityAsync(ctx, msg.getOriginator()),
49 - entityId -> withCallback(  
50 - config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId),  
51 - attributes -> putAttributesAndTell(ctx, msg, attributes),  
52 - t -> ctx.tellError(msg, t)  
53 - ), 56 + entityId -> safeGetAttributes(ctx, msg, entityId),
54 t -> ctx.tellError(msg, t)); 57 t -> ctx.tellError(msg, t));
55 } catch (Throwable th) { 58 } catch (Throwable th) {
56 ctx.tellError(msg, th); 59 ctx.tellError(msg, th);
57 } 60 }
58 } 61 }
59 62
  63 + private void safeGetAttributes(TbContext ctx, TbMsg msg, T entityId) {
  64 + if(entityId == null || entityId.isNullUid()) {
  65 + ctx.tellNext(msg, FAILURE);
  66 + return;
  67 + }
  68 +
  69 + withCallback(config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId),
  70 + attributes -> putAttributesAndTell(ctx, msg, attributes),
  71 + t -> ctx.tellError(msg, t));
  72 + }
  73 +
60 private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) { 74 private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) {
61 ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); 75 ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet());
62 return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l -> 76 return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l ->
@@ -75,7 +89,7 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode @@ -75,7 +89,7 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode
75 String attrName = config.getAttrMapping().get(r.getKey()); 89 String attrName = config.getAttrMapping().get(r.getKey());
76 msg.getMetaData().putValue(attrName, r.getValueAsString()); 90 msg.getMetaData().putValue(attrName, r.getValueAsString());
77 }); 91 });
78 - ctx.tellNext(msg); 92 + ctx.tellNext(msg, SUCCESS);
79 } 93 }
80 94
81 @Override 95 @Override
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg;
30 import java.util.List; 30 import java.util.List;
31 31
32 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 32 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  33 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
33 import static org.thingsboard.server.common.data.DataConstants.*; 34 import static org.thingsboard.server.common.data.DataConstants.*;
34 35
35 /** 36 /**
@@ -41,9 +42,9 @@ import static org.thingsboard.server.common.data.DataConstants.*; @@ -41,9 +42,9 @@ import static org.thingsboard.server.common.data.DataConstants.*;
41 configClazz = TbGetAttributesNodeConfiguration.class, 42 configClazz = TbGetAttributesNodeConfiguration.class,
42 nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", 43 nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata",
43 nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + 44 nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
44 - "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +  
45 - "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> " +  
46 - "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.", 45 + "with specific prefix: <i>cs/shared/ss</i>. Latest telemetry value added into metadata without prefix. " +
  46 + "To access those attributes in other nodes this template can be used " +
  47 + "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ",
47 uiResources = {"static/rulenode/rulenode-core-config.js"}, 48 uiResources = {"static/rulenode/rulenode-core-config.js"},
48 configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") 49 configDirective = "tbEnrichmentNodeOriginatorAttributesConfig")
49 public class TbGetAttributesNode implements TbNode { 50 public class TbGetAttributesNode implements TbNode {
@@ -57,22 +58,17 @@ public class TbGetAttributesNode implements TbNode { @@ -57,22 +58,17 @@ public class TbGetAttributesNode implements TbNode {
57 58
58 @Override 59 @Override
59 public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { 60 public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
60 - if (CollectionUtils.isNotEmpty(config.getLatestTsKeyNames())) {  
61 - withCallback(getLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()),  
62 - i -> ctx.tellNext(msg),  
63 - t -> ctx.tellError(msg, t));  
64 - } else {  
65 - ListenableFuture<List<Void>> future = Futures.allAsList(  
66 - putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"),  
67 - putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"),  
68 - putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_"));  
69 -  
70 - withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t));  
71 - } 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));
72 } 68 }
73 69
74 private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) { 70 private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) {
75 - if (keys == null) { 71 + if (CollectionUtils.isEmpty(keys)) {
76 return Futures.immediateFuture(null); 72 return Futures.immediateFuture(null);
77 } 73 }
78 ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys); 74 ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys);
@@ -82,8 +78,8 @@ public class TbGetAttributesNode implements TbNode { @@ -82,8 +78,8 @@ public class TbGetAttributesNode implements TbNode {
82 }); 78 });
83 } 79 }
84 80
85 - private ListenableFuture<Void> getLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) {  
86 - if (keys == null) { 81 + private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) {
  82 + if (CollectionUtils.isEmpty(keys)) {
87 return Futures.immediateFuture(null); 83 return Futures.immediateFuture(null);
88 } 84 }
89 ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys); 85 ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys);
@@ -29,8 +29,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -29,8 +29,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
29 configClazz = TbGetEntityAttrNodeConfiguration.class, 29 configClazz = TbGetEntityAttrNodeConfiguration.class,
30 nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", 30 nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
31 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 31 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
  32 + "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
32 "To access those attributes in other nodes this template can be used " + 33 "To access those attributes in other nodes this template can be used " +
33 - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", 34 + "<code>metadata.temperature</code>.",
34 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 35 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
35 configDirective = "tbEnrichmentNodeCustomerAttributesConfig") 36 configDirective = "tbEnrichmentNodeCustomerAttributesConfig")
36 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { 37 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
@@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
31 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + 31 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
32 "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + 32 "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
33 "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 33 "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
  34 + "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
34 "To access those attributes in other nodes this template can be used " + 35 "To access those attributes in other nodes this template can be used " +
35 - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", 36 + "<code>metadata.temperature</code>.",
36 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 37 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
37 configDirective = "tbEnrichmentNodeRelatedAttributesConfig") 38 configDirective = "tbEnrichmentNodeRelatedAttributesConfig")
38 39
@@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
31 configClazz = TbGetEntityAttrNodeConfiguration.class, 31 configClazz = TbGetEntityAttrNodeConfiguration.class,
32 nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", 32 nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
33 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 33 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
  34 + "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
34 "To access those attributes in other nodes this template can be used " + 35 "To access those attributes in other nodes this template can be used " +
35 - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", 36 + "<code>metadata.temperature</code>.",
36 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 37 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
37 configDirective = "tbEnrichmentNodeTenantAttributesConfig") 38 configDirective = "tbEnrichmentNodeTenantAttributesConfig")
38 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { 39 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
@@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException; @@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException;
43 43
44 @Slf4j 44 @Slf4j
45 @RuleNode( 45 @RuleNode(
46 - type = ComponentType.ACTION, 46 + type = ComponentType.EXTERNAL,
47 name = "mqtt", 47 name = "mqtt",
48 configClazz = TbMqttNodeConfiguration.class, 48 configClazz = TbMqttNodeConfiguration.class,
49 nodeDescription = "Publish messages to MQTT broker", 49 nodeDescription = "Publish messages to MQTT broker",
50 nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.", 50 nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.",
51 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 51 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
52 - configDirective = "tbActionNodeMqttConfig" 52 + configDirective = "tbActionNodeMqttConfig",
  53 + icon = "call_split"
53 ) 54 )
54 public class TbMqttNode implements TbNode { 55 public class TbMqttNode implements TbNode {
55 56
@@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
33 33
34 @Slf4j 34 @Slf4j
35 @RuleNode( 35 @RuleNode(
36 - type = ComponentType.ACTION, 36 + type = ComponentType.EXTERNAL,
37 name = "rabbitmq", 37 name = "rabbitmq",
38 configClazz = TbRabbitMqNodeConfiguration.class, 38 configClazz = TbRabbitMqNodeConfiguration.class,
39 nodeDescription = "Publish messages to RabbitMQ", 39 nodeDescription = "Publish messages to RabbitMQ",
40 nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.", 40 nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.",
41 uiResources = {"static/rulenode/rulenode-core-config.js"}, 41 uiResources = {"static/rulenode/rulenode-core-config.js"},
42 - configDirective = "tbActionNodeRabbitMqConfig" 42 + configDirective = "tbActionNodeRabbitMqConfig",
  43 + iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgeT0iMHB4IiB4PSIwcHgiIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIHN0cm9rZS13aWR0aD0iLjg0OTU2IiBkPSJtODYwLjQ3IDQxNi4zMmgtMjYyLjAxYy0xMi45MTMgMC0yMy42MTgtMTAuNzA0LTIzLjYxOC0yMy42MTh2LTI3Mi43MWMwLTIwLjMwNS0xNi4yMjctMzYuMjc2LTM2LjI3Ni0zNi4yNzZoLTkzLjc5MmMtMjAuMzA1IDAtMzYuMjc2IDE2LjIyNy0zNi4yNzYgMzYuMjc2djI3MC44NGMtMC4yNTQ4NyAxNC4xMDMtMTEuNDY5IDI1LjU3Mi0yNS43NDIgMjUuNTcybC04NS42MzYgMC42Nzk2NWMtMTQuMTAzIDAtMjUuNTcyLTExLjQ2OS0yNS41NzItMjUuNTcybDAuNjc5NjUtMjcxLjUyYzAtMjAuMzA1LTE2LjIyNy0zNi4yNzYtMzYuMjc2LTM2LjI3NmgtOTMuNTM3Yy0yMC4zMDUgMC0zNi4yNzYgMTYuMjI3LTM2LjI3NiAzNi4yNzZ2NzYzLjg0YzAgMTguMDk2IDE0Ljc4MiAzMi40NTMgMzIuNDUzIDMyLjQ1M2g3MjIuODFjMTguMDk2IDAgMzIuNDUzLTE0Ljc4MiAzMi40NTMtMzIuNDUzdi00MzUuMzFjLTEuMTg5NC0xOC4xODEtMTUuMjkyLTMyLjE5OC0zMy4zODgtMzIuMTk4em0tMTIyLjY4IDI4Ny4wN2MwIDIzLjYxOC0xOC44NiA0Mi40NzgtNDIuNDc4IDQyLjQ3OGgtNzMuOTk3Yy0yMy42MTggMC00Mi40NzgtMTguODYtNDIuNDc4LTQyLjQ3OHYtNzQuMjUyYzAtMjMuNjE4IDE4Ljg2LTQyLjQ3OCA0Mi40NzgtNDIuNDc4aDczLjk5N2MyMy42MTggMCA0Mi40NzggMTguODYgNDIuNDc4IDQyLjQ3OHoiLz48L3N2Zz4="
43 ) 44 )
44 public class TbRabbitMqNode implements TbNode { 45 public class TbRabbitMqNode implements TbNode {
45 46
@@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity; @@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity;
26 import org.springframework.http.client.Netty4ClientHttpRequestFactory; 26 import org.springframework.http.client.Netty4ClientHttpRequestFactory;
27 import org.springframework.util.concurrent.ListenableFuture; 27 import org.springframework.util.concurrent.ListenableFuture;
28 import org.springframework.util.concurrent.ListenableFutureCallback; 28 import org.springframework.util.concurrent.ListenableFutureCallback;
29 -import org.springframework.web.client.*; 29 +import org.springframework.web.client.AsyncRestTemplate;
  30 +import org.springframework.web.client.HttpClientErrorException;
30 import org.thingsboard.rule.engine.TbNodeUtils; 31 import org.thingsboard.rule.engine.TbNodeUtils;
31 import org.thingsboard.rule.engine.api.*; 32 import org.thingsboard.rule.engine.api.*;
32 import org.thingsboard.server.common.data.plugin.ComponentType; 33 import org.thingsboard.server.common.data.plugin.ComponentType;
@@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg;
34 import org.thingsboard.server.common.msg.TbMsgMetaData; 35 import org.thingsboard.server.common.msg.TbMsgMetaData;
35 36
36 import javax.net.ssl.SSLException; 37 import javax.net.ssl.SSLException;
37 -import java.util.Map;  
38 import java.util.concurrent.ExecutionException; 38 import java.util.concurrent.ExecutionException;
39 import java.util.concurrent.TimeUnit; 39 import java.util.concurrent.TimeUnit;
40 40
41 @Slf4j 41 @Slf4j
42 @RuleNode( 42 @RuleNode(
43 - type = ComponentType.ACTION, 43 + type = ComponentType.EXTERNAL,
44 name = "rest api call", 44 name = "rest api call",
45 configClazz = TbRestApiCallNodeConfiguration.class, 45 configClazz = TbRestApiCallNodeConfiguration.class,
46 nodeDescription = "Invoke REST API calls to external REST server", 46 nodeDescription = "Invoke REST API calls to external REST server",
47 nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.", 47 nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.",
48 uiResources = {"static/rulenode/rulenode-core-config.js"}, 48 uiResources = {"static/rulenode/rulenode-core-config.js"},
49 - configDirective = "tbActionNodeRestApiCallConfig" 49 + configDirective = "tbActionNodeRestApiCallConfig",
  50 + iconUrl = "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB2ZXJzaW9uPSIxLjEiIHk9IjBweCIgeD0iMHB4Ij48ZyB0cmFuc2Zvcm09Im1hdHJpeCguOTQ5NzUgMCAwIC45NDk3NSAxNy4xMiAyNi40OTIpIj48cGF0aCBkPSJtMTY5LjExIDEwOC41NGMtOS45MDY2IDAuMDczNC0xOS4wMTQgNi41NzI0LTIyLjAxNCAxNi40NjlsLTY5Ljk5MyAyMzEuMDhjLTMuNjkwNCAxMi4xODEgMy4yODkyIDI1LjIyIDE1LjQ2OSAyOC45MSAyLjIyNTkgMC42NzQ4MSA0LjQ5NjkgMSA2LjcyODUgMSA5Ljk3MjEgMCAxOS4xNjUtNi41MTUzIDIyLjE4Mi0xNi40NjdhNi41MjI0IDYuNTIyNCAwIDAgMCAwLjAwMiAtMC4wMDJsNjkuOTktMjMxLjA3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMCAtMC4wMDJjMy42ODU1LTEyLjE4MS0zLjI4Ny0yNS4yMjUtMTUuNDcxLTI4LjkxMi0yLjI4MjUtMC42OTE0NS00LjYxMTYtMS4wMTY5LTYuODk4NC0xem04NC45ODggMGMtOS45MDQ4IDAuMDczNC0xOS4wMTggNi41Njc1LTIyLjAxOCAxNi40NjlsLTY5Ljk4NiAyMzEuMDhjLTMuNjg5OCAxMi4xNzkgMy4yODUzIDI1LjIxNyAxNS40NjUgMjguOTA4IDIuMjI5NyAwLjY3NjQ3IDQuNTAwOCAxLjAwMiA2LjczMjQgMS4wMDIgOS45NzIxIDAgMTkuMTY1LTYuNTE1MyAyMi4xODItMTYuNDY3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMC4wMDIgLTAuMDAybDY5Ljk4OC0yMzEuMDdjMy42OTA4LTEyLjE4MS0zLjI4NTItMjUuMjIzLTE1LjQ2Ny0yOC45MTItMi4yODE0LTAuNjkyMzEtNC42MTA4LTEuMDE4OS02Ljg5ODQtMS4wMDJ6bS0yMTcuMjkgNDIuMjNjLTEyLjcyOS0wLjAwMDg3LTIzLjE4OCAxMC40NTYtMjMuMTg4IDIzLjE4NiAwLjAwMSAxMi43MjggMTAuNDU5IDIzLjE4NiAyMy4xODggMjMuMTg2IDEyLjcyNy0wLjAwMSAyMy4xODMtMTAuNDU5IDIzLjE4NC0yMy4xODYgMC4wMDA4NzYtMTIuNzI4LTEwLjQ1Ni0yMy4xODUtMjMuMTg0LTIzLjE4NnptMCAxNDYuNjRjLTEyLjcyNy0wLjAwMDg3LTIzLjE4NiAxMC40NTUtMjMuMTg4IDIzLjE4NC0wLjAwMDg3MyAxMi43MjkgMTAuNDU4IDIzLjE4OCAyMy4xODggMjMuMTg4IDEyLjcyOC0wLjAwMSAyMy4xODQtMTAuNDYgMjMuMTg0LTIzLjE4OC0wLjAwMS0xMi43MjYtMTAuNDU3LTIzLjE4My0yMy4xODQtMjMuMTg0em0yNzAuNzkgNDIuMjExYy0xMi43MjcgMC0yMy4xODQgMTAuNDU3LTIzLjE4NCAyMy4xODRzMTAuNDU1IDIzLjE4OCAyMy4xODQgMjMuMTg4aDE1NC45OGMxMi43MjkgMCAyMy4xODYtMTAuNDYgMjMuMTg2LTIzLjE4OCAwLjAwMS0xMi43MjgtMTAuNDU4LTIzLjE4NC0yMy4xODYtMjMuMTg0eiIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMzc2IDAgMCAxLjAzNzYgLTcuNTY3NiAtMTQuOTI1KSIgc3Ryb2tlLXdpZHRoPSIxLjI2OTMiLz48L2c+PC9zdmc+"
50 ) 51 )
51 public class TbRestApiCallNode implements TbNode { 52 public class TbRestApiCallNode implements TbNode {
52 53
@@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg;
36 nodeDescription = "Sends reply to the RPC call from device", 36 nodeDescription = "Sends reply to the RPC call from device",
37 nodeDetails = "Expects messages with any message type. Will forward message body to the device.", 37 nodeDetails = "Expects messages with any message type. Will forward message body to the device.",
38 uiResources = {"static/rulenode/rulenode-core-config.js"}, 38 uiResources = {"static/rulenode/rulenode-core-config.js"},
39 - configDirective = "tbActionNodeRpcReplyConfig" 39 + configDirective = "tbActionNodeRpcReplyConfig",
  40 + icon = "call_merge"
40 ) 41 )
41 public class TbSendRPCReplyNode implements TbNode { 42 public class TbSendRPCReplyNode implements TbNode {
42 43
@@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit; @@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit;
43 nodeDescription = "Sends one-way RPC call to device", 43 nodeDescription = "Sends one-way RPC call to device",
44 nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.", 44 nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.",
45 uiResources = {"static/rulenode/rulenode-core-config.js"}, 45 uiResources = {"static/rulenode/rulenode-core-config.js"},
46 - configDirective = "tbActionNodeRpcRequestConfig" 46 + configDirective = "tbActionNodeRpcRequestConfig",
  47 + icon = "call_made"
47 ) 48 )
48 public class TbSendRPCRequestNode implements TbNode { 49 public class TbSendRPCRequestNode implements TbNode {
49 50
@@ -48,7 +48,8 @@ import java.util.Set; @@ -48,7 +48,8 @@ import java.util.Set;
48 nodeDescription = "Saves attributes data", 48 nodeDescription = "Saves attributes data",
49 nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type", 49 nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
50 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 50 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
51 - configDirective = "tbActionNodeAttributesConfig" 51 + configDirective = "tbActionNodeAttributesConfig",
  52 + icon = "file_upload"
52 ) 53 )
53 public class TbMsgAttributesNode implements TbNode { 54 public class TbMsgAttributesNode implements TbNode {
54 55
@@ -45,7 +45,8 @@ import java.util.Map; @@ -45,7 +45,8 @@ import java.util.Map;
45 nodeDescription = "Saves timeseries data", 45 nodeDescription = "Saves timeseries data",
46 nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type", 46 nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type",
47 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 47 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
48 - configDirective = "tbActionNodeTimeseriesConfig" 48 + configDirective = "tbActionNodeTimeseriesConfig",
  49 + icon = "file_upload"
49 ) 50 )
50 public class TbMsgTimeseriesNode implements TbNode { 51 public class TbMsgTimeseriesNode implements TbNode {
51 52
@@ -22,6 +22,8 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -22,6 +22,8 @@ import org.thingsboard.server.common.msg.TbMsg;
22 22
23 import javax.annotation.Nullable; 23 import javax.annotation.Nullable;
24 24
  25 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  26 +
25 /** 27 /**
26 * Created by ashvayka on 02.04.18. 28 * Created by ashvayka on 02.04.18.
27 */ 29 */
@@ -32,7 +34,7 @@ class TelemetryNodeCallback implements FutureCallback<Void> { @@ -32,7 +34,7 @@ class TelemetryNodeCallback implements FutureCallback<Void> {
32 34
33 @Override 35 @Override
34 public void onSuccess(@Nullable Void result) { 36 public void onSuccess(@Nullable Void result) {
35 - ctx.tellNext(msg); 37 + ctx.tellNext(msg, SUCCESS);
36 } 38 }
37 39
38 @Override 40 @Override
@@ -18,10 +18,15 @@ package org.thingsboard.rule.engine.transform; @@ -18,10 +18,15 @@ package org.thingsboard.rule.engine.transform;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 import org.thingsboard.rule.engine.TbNodeUtils; 20 import org.thingsboard.rule.engine.TbNodeUtils;
21 -import org.thingsboard.rule.engine.api.*; 21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNode;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
22 import org.thingsboard.server.common.msg.TbMsg; 25 import org.thingsboard.server.common.msg.TbMsg;
23 26
24 import static org.thingsboard.rule.engine.DonAsynchron.withCallback; 27 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  28 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
  29 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
25 30
26 /** 31 /**
27 * Created by ashvayka on 19.01.18. 32 * Created by ashvayka on 19.01.18.
@@ -39,20 +44,18 @@ public abstract class TbAbstractTransformNode implements TbNode { @@ -39,20 +44,18 @@ public abstract class TbAbstractTransformNode implements TbNode {
39 @Override 44 @Override
40 public void onMsg(TbContext ctx, TbMsg msg) { 45 public void onMsg(TbContext ctx, TbMsg msg) {
41 withCallback(transform(ctx, msg), 46 withCallback(transform(ctx, msg),
42 - m -> routeMsg(ctx, m), 47 + m -> {
  48 + if (m != null) {
  49 + ctx.tellNext(m, SUCCESS);
  50 + } else {
  51 + ctx.tellNext(msg, FAILURE);
  52 + }
  53 + },
43 t -> ctx.tellError(msg, t)); 54 t -> ctx.tellError(msg, t));
44 } 55 }
45 56
46 protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg); 57 protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg);
47 58
48 - private void routeMsg(TbContext ctx, TbMsg msg) {  
49 - if (config.isStartNewChain()) {  
50 - ctx.spawn(msg);  
51 - } else {  
52 - ctx.tellNext(msg);  
53 - }  
54 - }  
55 -  
56 public void setConfig(TbTransformNodeConfiguration config) { 59 public void setConfig(TbTransformNodeConfiguration config) {
57 this.config = config; 60 this.config = config;
58 } 61 }
@@ -21,7 +21,10 @@ import com.google.common.util.concurrent.Futures; @@ -21,7 +21,10 @@ import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture; 21 import com.google.common.util.concurrent.ListenableFuture;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
23 import org.thingsboard.rule.engine.TbNodeUtils; 23 import org.thingsboard.rule.engine.TbNodeUtils;
24 -import org.thingsboard.rule.engine.api.*; 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;
25 import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; 28 import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
26 import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; 29 import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader;
27 import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; 30 import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader;
@@ -34,13 +37,15 @@ import java.util.HashSet; @@ -34,13 +37,15 @@ import java.util.HashSet;
34 @Slf4j 37 @Slf4j
35 @RuleNode( 38 @RuleNode(
36 type = ComponentType.TRANSFORMATION, 39 type = ComponentType.TRANSFORMATION,
37 - name="change originator", 40 + name = "change originator",
38 configClazz = TbChangeOriginatorNodeConfiguration.class, 41 configClazz = TbChangeOriginatorNodeConfiguration.class,
39 nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", 42 nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity",
40 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + 43 nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
41 "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", 44 "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
42 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 45 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
43 - configDirective = "tbTransformationNodeChangeOriginatorConfig") 46 + configDirective = "tbTransformationNodeChangeOriginatorConfig",
  47 + icon = "find_replace"
  48 +)
44 public class TbChangeOriginatorNode extends TbAbstractTransformNode { 49 public class TbChangeOriginatorNode extends TbAbstractTransformNode {
45 50
46 protected static final String CUSTOMER_SOURCE = "CUSTOMER"; 51 protected static final String CUSTOMER_SOURCE = "CUSTOMER";
@@ -59,7 +64,12 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { @@ -59,7 +64,12 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
59 @Override 64 @Override
60 protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) { 65 protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
61 ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator()); 66 ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator());
62 - return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData())); 67 + return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> {
  68 + if (n == null || n.isNullUid()) {
  69 + return null;
  70 + }
  71 + return ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData());
  72 + });
63 } 73 }
64 74
65 private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) { 75 private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) {
@@ -43,7 +43,6 @@ public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfigur @@ -43,7 +43,6 @@ public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfigur
43 relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); 43 relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
44 configuration.setRelationsQuery(relationsQuery); 44 configuration.setRelationsQuery(relationsQuery);
45 45
46 - configuration.setStartNewChain(false);  
47 return configuration; 46 return configuration;
48 } 47 }
49 } 48 }
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.msg.TbMsg;
26 name = "script", 26 name = "script",
27 configClazz = TbTransformMsgNodeConfiguration.class, 27 configClazz = TbTransformMsgNodeConfiguration.class,
28 nodeDescription = "Change Message payload, Metadata or Message type using JavaScript", 28 nodeDescription = "Change Message payload, Metadata or Message type using JavaScript",
29 - nodeDetails = "JavaScript function receive 3 input parameters.<br/> " + 29 + nodeDetails = "JavaScript function receive 3 input parameters <br/> " +
30 "<code>metadata</code> - is a Message metadata.<br/>" + 30 "<code>metadata</code> - is a Message metadata.<br/>" +
31 "<code>msg</code> - is a Message payload.<br/>" + 31 "<code>msg</code> - is a Message payload.<br/>" +
32 "<code>msgType</code> - is a Message type.<br/>" + 32 "<code>msgType</code> - is a Message type.<br/>" +
@@ -26,7 +26,6 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio @@ -26,7 +26,6 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
26 @Override 26 @Override
27 public TbTransformMsgNodeConfiguration defaultConfiguration() { 27 public TbTransformMsgNodeConfiguration defaultConfiguration() {
28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); 28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
29 - configuration.setStartNewChain(false);  
30 configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};"); 29 configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};");
31 return configuration; 30 return configuration;
32 } 31 }
@@ -20,5 +20,4 @@ import lombok.Data; @@ -20,5 +20,4 @@ import lombok.Data;
20 @Data 20 @Data
21 public class TbTransformNodeConfiguration { 21 public class TbTransformNodeConfiguration {
22 22
23 - private boolean startNewChain = false;  
24 } 23 }
@@ -45,6 +45,6 @@ public class EntitiesCustomerIdAsyncLoader { @@ -45,6 +45,6 @@ public class EntitiesCustomerIdAsyncLoader {
45 private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) { 45 private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) {
46 return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> { 46 return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> {
47 return in != null ? Futures.immediateFuture(in.getCustomerId()) 47 return in != null ? Futures.immediateFuture(in.getCustomerId())
48 - : Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); 48 + : Futures.immediateFuture(null);});
49 } 49 }
50 } 50 }
@@ -40,11 +40,11 @@ public class EntitiesRelatedEntityIdAsyncLoader { @@ -40,11 +40,11 @@ public class EntitiesRelatedEntityIdAsyncLoader {
40 if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { 40 if (relationsQuery.getDirection() == EntitySearchDirection.FROM) {
41 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) 41 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
42 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) 42 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo())
43 - : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); 43 + : Futures.immediateFuture(null));
44 } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { 44 } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) {
45 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) 45 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
46 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) 46 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom())
47 - : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); 47 + : Futures.immediateFuture(null));
48 } 48 }
49 return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); 49 return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction"));
50 } 50 }
@@ -53,6 +53,6 @@ public class EntitiesTenantIdAsyncLoader { @@ -53,6 +53,6 @@ public class EntitiesTenantIdAsyncLoader {
53 private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) { 53 private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) {
54 return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> { 54 return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> {
55 return in != null ? Futures.immediateFuture(in.getTenantId()) 55 return in != null ? Futures.immediateFuture(in.getTenantId())
56 - : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); 56 + : Futures.immediateFuture(null);});
57 } 57 }
58 } 58 }
1 -!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.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),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(60)},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> </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> </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=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> "},18,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> ";  
2 -},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> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </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> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </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> </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> </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> </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> </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> </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> </md-input-container> </section> "},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testConditionJs=function(e,n){var i=angular.copy(n?a.configuration.createConditionJs:a.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};r.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?a.configuration.createConditionJs=e:a.configuration.clearConditionJs=e,s.$setDirty()})},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(43),i=r(a),o=n(31),l=r(o),s=n(32),u=r(s),d=n(30),c=r(d),m=n(35),g=r(m),p=n(39),f=r(p),b=n(40),v=r(b),y=n(38),q=r(y),T=n(34),h=r(T),$=n(41),k=r($),w=n(42),C=r(w),x=n(37),_=r(x),M=n(36),S=r(M);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).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$mdExpansionPanel=t,r.ruleNodeTypes=n,r.credentialsTypeChanged=function(){var e=r.configuration.credentials.type;r.configuration.credentials={},r.configuration.credentials.type=e,r.updateValidity()},r.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){r.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(r.configuration.credentials.caCertFileName=e.name,r.configuration.credentials.caCert=a),"privateKey"==t&&(r.configuration.credentials.privateKeyFileName=e.name,r.configuration.credentials.privateKey=a),"Cert"==t&&(r.configuration.credentials.certFileName=e.name,r.configuration.credentials.cert=a)),r.updateValidity()}})},n.readAsText(e.file)},r.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(r.configuration.credentials.caCertFileName=null,r.configuration.credentials.caCert=null),"privateKey"==e&&(r.configuration.credentials.privateKeyFileName=null,r.configuration.credentials.privateKey=null),"Cert"==e&&(r.configuration.credentials.certFileName=null,r.configuration.credentials.cert=null),r.updateValidity()},r.updateValidity=function(){var e=!0,t=r.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){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)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t){"use strict";function n(e){var t=function(t,n,r,a){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$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 r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(47),i=r(a),o=n(48),l=r(o),s=n(45),u=r(s),d=n(49),c=r(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 r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(21),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(52),i=r(a),o=n(51),l=r(o),s=n(53),u=r(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 r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.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)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(3);var i=n(22),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={passed:12,name:"Vit",bigObj:{prop:42}},o={temp:10};n.testNodeScript(e,a,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(23),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(24),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){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)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$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}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(25),o=r(i);n(4)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(26),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.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(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(27),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(56),i=r(a),o=n(58),l=r(o),s=n(59),u=r(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 r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(28),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(29),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(63),i=r(a),o=n(50),l=r(o),s=n(46),u=r(s),d=n(57),c=r(d),m=n(33),g=r(m),p=n(44),f=r(p),b=n(55),v=r(b),y=n(54),q=r(y),T=n(62),h=r(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","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","to-template":"To Template","to-template-required":"To Template is required","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","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","request-method":"Request method",headers:"Headers",header:"Header","header-required":"Header is required",  
3 -value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","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"},"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 r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(61),o=r(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"}},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(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}]));
4 //# sourceMappingURL=rulenode-core-config.js.map 4 //# sourceMappingURL=rulenode-core-config.js.map
@@ -59,6 +59,9 @@ public class TbMsgToEmailNodeTest { @@ -59,6 +59,9 @@ public class TbMsgToEmailNodeTest {
59 initWithScript(); 59 initWithScript();
60 metaData.putValue("username", "oreo"); 60 metaData.putValue("username", "oreo");
61 metaData.putValue("userEmail", "user@email.io"); 61 metaData.putValue("userEmail", "user@email.io");
  62 + metaData.putValue("name", "temp");
  63 + metaData.putValue("passed", "5");
  64 + metaData.putValue("count", "100");
62 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); 65 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
63 66
64 emailNode.onMsg(ctx, msg); 67 emailNode.onMsg(ctx, msg);
@@ -91,9 +94,9 @@ public class TbMsgToEmailNodeTest { @@ -91,9 +94,9 @@ public class TbMsgToEmailNodeTest {
91 try { 94 try {
92 TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); 95 TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration();
93 config.setFromTemplate("test@mail.org"); 96 config.setFromTemplate("test@mail.org");
94 - config.setToTemplate("$metadata.userEmail");  
95 - config.setSubjectTemplate("Hi $metadata.username there");  
96 - config.setBodyTemplate("$msg.name is to high. Current $msg.passed and $msg.complex.count"); 97 + config.setToTemplate("${userEmail}");
  98 + config.setSubjectTemplate("Hi ${username} there");
  99 + config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}");
97 ObjectMapper mapper = new ObjectMapper(); 100 ObjectMapper mapper = new ObjectMapper();
98 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); 101 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
99 102
@@ -31,12 +31,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; @@ -31,12 +31,7 @@ import org.thingsboard.rule.engine.api.TbNodeException;
31 import org.thingsboard.server.common.data.Device; 31 import org.thingsboard.server.common.data.Device;
32 import org.thingsboard.server.common.data.User; 32 import org.thingsboard.server.common.data.User;
33 import org.thingsboard.server.common.data.asset.Asset; 33 import org.thingsboard.server.common.data.asset.Asset;
34 -import org.thingsboard.server.common.data.id.AssetId;  
35 -import org.thingsboard.server.common.data.id.CustomerId;  
36 -import org.thingsboard.server.common.data.id.DeviceId;  
37 -import org.thingsboard.server.common.data.id.RuleChainId;  
38 -import org.thingsboard.server.common.data.id.RuleNodeId;  
39 -import org.thingsboard.server.common.data.id.UserId; 34 +import org.thingsboard.server.common.data.id.*;
40 import org.thingsboard.server.common.data.kv.*; 35 import org.thingsboard.server.common.data.kv.*;
41 import org.thingsboard.server.common.msg.TbMsg; 36 import org.thingsboard.server.common.msg.TbMsg;
42 import org.thingsboard.server.common.msg.TbMsgMetaData; 37 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -56,6 +51,8 @@ import static org.junit.Assert.assertTrue; @@ -56,6 +51,8 @@ import static org.junit.Assert.assertTrue;
56 import static org.mockito.Matchers.same; 51 import static org.mockito.Matchers.same;
57 import static org.mockito.Mockito.verify; 52 import static org.mockito.Mockito.verify;
58 import static org.mockito.Mockito.when; 53 import static org.mockito.Mockito.when;
  54 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
  55 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
59 import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; 56 import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
60 57
61 @RunWith(MockitoJUnitRunner.class) 58 @RunWith(MockitoJUnitRunner.class)
@@ -148,7 +145,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -148,7 +145,7 @@ public class TbGetCustomerAttributeNodeTest {
148 } 145 }
149 146
150 @Test 147 @Test
151 - public void errorThrownIfCustomerCannotBeFound() { 148 + public void failedChainUsedIfCustomerCannotBeFound() {
152 UserId userId = new UserId(UUIDs.timeBased()); 149 UserId userId = new UserId(UUIDs.timeBased());
153 CustomerId customerId = new CustomerId(UUIDs.timeBased()); 150 CustomerId customerId = new CustomerId(UUIDs.timeBased());
154 User user = new User(); 151 User user = new User();
@@ -159,13 +156,9 @@ public class TbGetCustomerAttributeNodeTest { @@ -159,13 +156,9 @@ public class TbGetCustomerAttributeNodeTest {
159 when(ctx.getUserService()).thenReturn(userService); 156 when(ctx.getUserService()).thenReturn(userService);
160 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); 157 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null));
161 158
162 - node.onMsg(ctx, msg);  
163 - final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);  
164 - verify(ctx).tellError(same(msg), captor.capture());  
165 159
166 - Throwable value = captor.getValue();  
167 - assertEquals(IllegalStateException.class, value.getClass());  
168 - assertEquals("Customer not found", value.getMessage()); 160 + node.onMsg(ctx, msg);
  161 + verify(ctx).tellNext(msg, FAILURE);
169 assertTrue(msg.getMetaData().getData().isEmpty()); 162 assertTrue(msg.getMetaData().getData().isEmpty());
170 } 163 }
171 164
@@ -252,7 +245,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -252,7 +245,7 @@ public class TbGetCustomerAttributeNodeTest {
252 .thenReturn(Futures.immediateFuture(timeseries)); 245 .thenReturn(Futures.immediateFuture(timeseries));
253 246
254 node.onMsg(ctx, msg); 247 node.onMsg(ctx, msg);
255 - verify(ctx).tellNext(msg); 248 + verify(ctx).tellNext(msg, SUCCESS);
256 assertEquals(msg.getMetaData().getValue("tempo"), "highest"); 249 assertEquals(msg.getMetaData().getValue("tempo"), "highest");
257 } 250 }
258 251
@@ -264,7 +257,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -264,7 +257,7 @@ public class TbGetCustomerAttributeNodeTest {
264 .thenReturn(Futures.immediateFuture(attributes)); 257 .thenReturn(Futures.immediateFuture(attributes));
265 258
266 node.onMsg(ctx, msg); 259 node.onMsg(ctx, msg);
267 - verify(ctx).tellNext(msg); 260 + verify(ctx).tellNext(msg, SUCCESS);
268 assertEquals(msg.getMetaData().getValue("tempo"), "high"); 261 assertEquals(msg.getMetaData().getValue("tempo"), "high");
269 } 262 }
270 } 263 }
@@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals; @@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals;
40 import static org.mockito.Matchers.same; 40 import static org.mockito.Matchers.same;
41 import static org.mockito.Mockito.verify; 41 import static org.mockito.Mockito.verify;
42 import static org.mockito.Mockito.when; 42 import static org.mockito.Mockito.when;
  43 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
43 44
44 @RunWith(MockitoJUnitRunner.class) 45 @RunWith(MockitoJUnitRunner.class)
45 public class TbChangeOriginatorNodeTest { 46 public class TbChangeOriginatorNodeTest {
@@ -54,7 +55,7 @@ public class TbChangeOriginatorNodeTest { @@ -54,7 +55,7 @@ public class TbChangeOriginatorNodeTest {
54 55
55 @Test 56 @Test
56 public void originatorCanBeChangedToCustomerId() throws TbNodeException { 57 public void originatorCanBeChangedToCustomerId() throws TbNodeException {
57 - init(false); 58 + init();
58 AssetId assetId = new AssetId(UUIDs.timeBased()); 59 AssetId assetId = new AssetId(UUIDs.timeBased());
59 CustomerId customerId = new CustomerId(UUIDs.timeBased()); 60 CustomerId customerId = new CustomerId(UUIDs.timeBased());
60 Asset asset = new Asset(); 61 Asset asset = new Asset();
@@ -82,7 +83,7 @@ public class TbChangeOriginatorNodeTest { @@ -82,7 +83,7 @@ public class TbChangeOriginatorNodeTest {
82 83
83 @Test 84 @Test
84 public void newChainCanBeStarted() throws TbNodeException { 85 public void newChainCanBeStarted() throws TbNodeException {
85 - init(true); 86 + init();
86 AssetId assetId = new AssetId(UUIDs.timeBased()); 87 AssetId assetId = new AssetId(UUIDs.timeBased());
87 CustomerId customerId = new CustomerId(UUIDs.timeBased()); 88 CustomerId customerId = new CustomerId(UUIDs.timeBased());
88 Asset asset = new Asset(); 89 Asset asset = new Asset();
@@ -109,7 +110,7 @@ public class TbChangeOriginatorNodeTest { @@ -109,7 +110,7 @@ public class TbChangeOriginatorNodeTest {
109 110
110 @Test 111 @Test
111 public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException { 112 public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException {
112 - init(true); 113 + init();
113 AssetId assetId = new AssetId(UUIDs.timeBased()); 114 AssetId assetId = new AssetId(UUIDs.timeBased());
114 CustomerId customerId = new CustomerId(UUIDs.timeBased()); 115 CustomerId customerId = new CustomerId(UUIDs.timeBased());
115 Asset asset = new Asset(); 116 Asset asset = new Asset();
@@ -121,19 +122,15 @@ public class TbChangeOriginatorNodeTest { @@ -121,19 +122,15 @@ public class TbChangeOriginatorNodeTest {
121 TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); 122 TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
122 123
123 when(ctx.getAssetService()).thenReturn(assetService); 124 when(ctx.getAssetService()).thenReturn(assetService);
124 - when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong"))); 125 + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(null));
125 126
126 node.onMsg(ctx, msg); 127 node.onMsg(ctx, msg);
127 - ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);  
128 - verify(ctx).tellError(same(msg), captor.capture());  
129 - Throwable value = captor.getValue();  
130 - assertEquals("wrong", value.getMessage()); 128 + verify(ctx).tellNext(same(msg), same(FAILURE));
131 } 129 }
132 130
133 - public void init(boolean startNewChain) throws TbNodeException { 131 + public void init() throws TbNodeException {
134 TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration(); 132 TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration();
135 config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); 133 config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE);
136 - config.setStartNewChain(startNewChain);  
137 ObjectMapper mapper = new ObjectMapper(); 134 ObjectMapper mapper = new ObjectMapper();
138 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); 135 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
139 136
@@ -38,6 +38,7 @@ import java.util.concurrent.Callable; @@ -38,6 +38,7 @@ import java.util.concurrent.Callable;
38 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertEquals;
39 import static org.mockito.Matchers.same; 39 import static org.mockito.Matchers.same;
40 import static org.mockito.Mockito.*; 40 import static org.mockito.Mockito.*;
  41 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
41 42
42 @RunWith(MockitoJUnitRunner.class) 43 @RunWith(MockitoJUnitRunner.class)
43 public class TbTransformMsgNodeTest { 44 public class TbTransformMsgNodeTest {
@@ -53,7 +54,7 @@ public class TbTransformMsgNodeTest { @@ -53,7 +54,7 @@ public class TbTransformMsgNodeTest {
53 54
54 @Test 55 @Test
55 public void metadataCanBeUpdated() throws TbNodeException, ScriptException { 56 public void metadataCanBeUpdated() throws TbNodeException, ScriptException {
56 - initWithScript(false); 57 + initWithScript();
57 TbMsgMetaData metaData = new TbMsgMetaData(); 58 TbMsgMetaData metaData = new TbMsgMetaData();
58 metaData.putValue("temp", "7"); 59 metaData.putValue("temp", "7");
59 String rawJson = "{\"passed\": 5}"; 60 String rawJson = "{\"passed\": 5}";
@@ -68,37 +69,14 @@ public class TbTransformMsgNodeTest { @@ -68,37 +69,14 @@ public class TbTransformMsgNodeTest {
68 node.onMsg(ctx, msg); 69 node.onMsg(ctx, msg);
69 verify(ctx).getJsExecutor(); 70 verify(ctx).getJsExecutor();
70 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); 71 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
71 - verify(ctx).tellNext(captor.capture());  
72 - TbMsg actualMsg = captor.getValue();  
73 - assertEquals(transformedMsg, actualMsg);  
74 - }  
75 -  
76 -  
77 - @Test  
78 - public void newChainCanBeStarted() throws TbNodeException, ScriptException {  
79 - initWithScript(true);  
80 - TbMsgMetaData metaData = new TbMsgMetaData();  
81 - metaData.putValue("temp", "7");  
82 - String rawJson = "{\"passed\": 5";  
83 -  
84 - RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());  
85 - RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());  
86 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);  
87 - TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L);  
88 - mockJsExecutor();  
89 - when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg);  
90 -  
91 - node.onMsg(ctx, msg);  
92 - verify(ctx).getJsExecutor();  
93 - ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);  
94 - verify(ctx).spawn(captor.capture()); 72 + verify(ctx).tellNext(captor.capture(), SUCCESS);
95 TbMsg actualMsg = captor.getValue(); 73 TbMsg actualMsg = captor.getValue();
96 assertEquals(transformedMsg, actualMsg); 74 assertEquals(transformedMsg, actualMsg);
97 } 75 }
98 76
99 @Test 77 @Test
100 public void exceptionHandledCorrectly() throws TbNodeException, ScriptException { 78 public void exceptionHandledCorrectly() throws TbNodeException, ScriptException {
101 - initWithScript(false); 79 + initWithScript();
102 TbMsgMetaData metaData = new TbMsgMetaData(); 80 TbMsgMetaData metaData = new TbMsgMetaData();
103 metaData.putValue("temp", "7"); 81 metaData.putValue("temp", "7");
104 String rawJson = "{\"passed\": 5"; 82 String rawJson = "{\"passed\": 5";
@@ -113,10 +91,9 @@ public class TbTransformMsgNodeTest { @@ -113,10 +91,9 @@ public class TbTransformMsgNodeTest {
113 verifyError(msg, "error", IllegalStateException.class); 91 verifyError(msg, "error", IllegalStateException.class);
114 } 92 }
115 93
116 - private void initWithScript(boolean startChain) throws TbNodeException { 94 + private void initWithScript() throws TbNodeException {
117 TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration(); 95 TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration();
118 config.setJsScript("scr"); 96 config.setJsScript("scr");
119 - config.setStartNewChain(startChain);  
120 ObjectMapper mapper = new ObjectMapper(); 97 ObjectMapper mapper = new ObjectMapper();
121 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); 98 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
122 99
@@ -35,7 +35,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; @@ -35,7 +35,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
35 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 35 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
36 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 36 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
37 import org.thingsboard.server.common.transport.quota.QuotaService; 37 import org.thingsboard.server.common.transport.quota.QuotaService;
38 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
39 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; 38 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
40 import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; 39 import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy;
41 import org.thingsboard.server.transport.coap.session.CoapSessionCtx; 40 import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
@@ -54,17 +53,15 @@ public class CoapTransportResource extends CoapResource { @@ -54,17 +53,15 @@ public class CoapTransportResource extends CoapResource {
54 private final SessionMsgProcessor processor; 53 private final SessionMsgProcessor processor;
55 private final DeviceAuthService authService; 54 private final DeviceAuthService authService;
56 private final QuotaService quotaService; 55 private final QuotaService quotaService;
57 - private final DeviceOfflineService offlineService;  
58 private final Field observerField; 56 private final Field observerField;
59 private final long timeout; 57 private final long timeout;
60 58
61 public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, 59 public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name,
62 - long timeout, QuotaService quotaService, DeviceOfflineService offlineService) { 60 + long timeout, QuotaService quotaService) {
63 super(name); 61 super(name);
64 this.processor = processor; 62 this.processor = processor;
65 this.authService = authService; 63 this.authService = authService;
66 this.quotaService = quotaService; 64 this.quotaService = quotaService;
67 - this.offlineService = offlineService;  
68 this.adaptor = adaptor; 65 this.adaptor = adaptor;
69 this.timeout = timeout; 66 this.timeout = timeout;
70 // This is important to turn off existing observable logic in 67 // This is important to turn off existing observable logic in
@@ -171,7 +168,6 @@ public class CoapTransportResource extends CoapResource { @@ -171,7 +168,6 @@ public class CoapTransportResource extends CoapResource {
171 case TO_SERVER_RPC_REQUEST: 168 case TO_SERVER_RPC_REQUEST:
172 ctx.setSessionType(SessionType.SYNC); 169 ctx.setSessionType(SessionType.SYNC);
173 msg = adaptor.convertToActorMsg(ctx, type, request); 170 msg = adaptor.convertToActorMsg(ctx, type, request);
174 - offlineService.online(ctx.getDevice(), true);  
175 break; 171 break;
176 case SUBSCRIBE_ATTRIBUTES_REQUEST: 172 case SUBSCRIBE_ATTRIBUTES_REQUEST:
177 case SUBSCRIBE_RPC_COMMANDS_REQUEST: 173 case SUBSCRIBE_RPC_COMMANDS_REQUEST:
@@ -179,13 +175,11 @@ public class CoapTransportResource extends CoapResource { @@ -179,13 +175,11 @@ public class CoapTransportResource extends CoapResource {
179 advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); 175 advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx));
180 ctx.setSessionType(SessionType.ASYNC); 176 ctx.setSessionType(SessionType.ASYNC);
181 msg = adaptor.convertToActorMsg(ctx, type, request); 177 msg = adaptor.convertToActorMsg(ctx, type, request);
182 - offlineService.online(ctx.getDevice(), false);  
183 break; 178 break;
184 case UNSUBSCRIBE_ATTRIBUTES_REQUEST: 179 case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
185 case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: 180 case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
186 ctx.setSessionType(SessionType.ASYNC); 181 ctx.setSessionType(SessionType.ASYNC);
187 msg = adaptor.convertToActorMsg(ctx, type, request); 182 msg = adaptor.convertToActorMsg(ctx, type, request);
188 - offlineService.online(ctx.getDevice(), false);  
189 break; 183 break;
190 default: 184 default:
191 log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); 185 log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type);
@@ -27,7 +27,6 @@ import org.springframework.stereotype.Service; @@ -27,7 +27,6 @@ import org.springframework.stereotype.Service;
27 import org.thingsboard.server.common.transport.SessionMsgProcessor; 27 import org.thingsboard.server.common.transport.SessionMsgProcessor;
28 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 28 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
29 import org.thingsboard.server.common.transport.quota.QuotaService; 29 import org.thingsboard.server.common.transport.quota.QuotaService;
30 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
31 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; 30 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
32 31
33 import javax.annotation.PostConstruct; 32 import javax.annotation.PostConstruct;
@@ -58,9 +57,6 @@ public class CoapTransportService { @@ -58,9 +57,6 @@ public class CoapTransportService {
58 @Autowired(required = false) 57 @Autowired(required = false)
59 private QuotaService quotaService; 58 private QuotaService quotaService;
60 59
61 - @Autowired(required = false)  
62 - private DeviceOfflineService offlineService;  
63 -  
64 60
65 @Value("${coap.bind_address}") 61 @Value("${coap.bind_address}")
66 private String host; 62 private String host;
@@ -90,7 +86,7 @@ public class CoapTransportService { @@ -90,7 +86,7 @@ public class CoapTransportService {
90 86
91 private void createResources() { 87 private void createResources() {
92 CoapResource api = new CoapResource(API); 88 CoapResource api = new CoapResource(API);
93 - api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService, offlineService)); 89 + api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService));
94 server.add(api); 90 server.add(api);
95 } 91 }
96 92
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.transport.coap; 16 package org.thingsboard.server.transport.coap;
17 17
18 -import com.google.common.util.concurrent.ListenableFuture;  
19 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
20 import org.eclipse.californium.core.CoapClient; 19 import org.eclipse.californium.core.CoapClient;
21 import org.eclipse.californium.core.CoapResponse; 20 import org.eclipse.californium.core.CoapResponse;
@@ -32,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext; @@ -32,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext;
32 import org.springframework.test.annotation.DirtiesContext.ClassMode; 31 import org.springframework.test.annotation.DirtiesContext.ClassMode;
33 import org.springframework.test.context.junit4.SpringRunner; 32 import org.springframework.test.context.junit4.SpringRunner;
34 import org.thingsboard.server.common.data.Device; 33 import org.thingsboard.server.common.data.Device;
35 -import org.thingsboard.server.common.data.device.DeviceStatusQuery;  
36 import org.thingsboard.server.common.data.id.CustomerId; 34 import org.thingsboard.server.common.data.id.CustomerId;
37 import org.thingsboard.server.common.data.id.DeviceId; 35 import org.thingsboard.server.common.data.id.DeviceId;
38 import org.thingsboard.server.common.data.id.TenantId; 36 import org.thingsboard.server.common.data.id.TenantId;
@@ -53,7 +51,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; @@ -53,7 +51,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
53 import org.thingsboard.server.common.transport.auth.DeviceAuthResult; 51 import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
54 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 52 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
55 import org.thingsboard.server.common.transport.quota.QuotaService; 53 import org.thingsboard.server.common.transport.quota.QuotaService;
56 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
57 54
58 import java.util.ArrayList; 55 import java.util.ArrayList;
59 import java.util.List; 56 import java.util.List;
@@ -140,31 +137,6 @@ public class CoapServerTest { @@ -140,31 +137,6 @@ public class CoapServerTest {
140 public static QuotaService quotaService() { 137 public static QuotaService quotaService() {
141 return key -> false; 138 return key -> false;
142 } 139 }
143 -  
144 - @Bean  
145 - public static DeviceOfflineService offlineService() {  
146 - return new DeviceOfflineService() {  
147 - @Override  
148 - public void online(Device device, boolean isUpdate) {  
149 -  
150 - }  
151 -  
152 - @Override  
153 - public void offline(Device device) {  
154 -  
155 - }  
156 -  
157 - @Override  
158 - public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {  
159 - return null;  
160 - }  
161 -  
162 - @Override  
163 - public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {  
164 - return null;  
165 - }  
166 - };  
167 - }  
168 } 140 }
169 141
170 @Autowired 142 @Autowired
@@ -26,7 +26,6 @@ import org.springframework.http.ResponseEntity; @@ -26,7 +26,6 @@ import org.springframework.http.ResponseEntity;
26 import org.springframework.util.StringUtils; 26 import org.springframework.util.StringUtils;
27 import org.springframework.web.bind.annotation.*; 27 import org.springframework.web.bind.annotation.*;
28 import org.springframework.web.context.request.async.DeferredResult; 28 import org.springframework.web.context.request.async.DeferredResult;
29 -import org.thingsboard.server.common.data.Device;  
30 import org.thingsboard.server.common.data.security.DeviceTokenCredentials; 29 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
31 import org.thingsboard.server.common.msg.core.*; 30 import org.thingsboard.server.common.msg.core.*;
32 import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; 31 import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
@@ -37,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; @@ -37,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
37 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 36 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
38 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
39 import org.thingsboard.server.common.transport.quota.QuotaService; 38 import org.thingsboard.server.common.transport.quota.QuotaService;
40 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
41 import org.thingsboard.server.transport.http.session.HttpSessionCtx; 39 import org.thingsboard.server.transport.http.session.HttpSessionCtx;
42 40
43 import javax.servlet.http.HttpServletRequest; 41 import javax.servlet.http.HttpServletRequest;
@@ -65,9 +63,6 @@ public class DeviceApiController { @@ -65,9 +63,6 @@ public class DeviceApiController {
65 @Autowired(required = false) 63 @Autowired(required = false)
66 private QuotaService quotaService; 64 private QuotaService quotaService;
67 65
68 - @Autowired(required = false)  
69 - private DeviceOfflineService offlineService;  
70 -  
71 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") 66 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
72 public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, 67 public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
73 @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, 68 @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
@@ -87,7 +82,7 @@ public class DeviceApiController { @@ -87,7 +82,7 @@ public class DeviceApiController {
87 Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null; 82 Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null;
88 request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet); 83 request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet);
89 } 84 }
90 - process(ctx, request, false); 85 + process(ctx, request);
91 } else { 86 } else {
92 responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); 87 responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
93 } 88 }
@@ -105,7 +100,7 @@ public class DeviceApiController { @@ -105,7 +100,7 @@ public class DeviceApiController {
105 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 100 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
106 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 101 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
107 try { 102 try {
108 - process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)), true); 103 + process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)));
109 } catch (IllegalStateException | JsonSyntaxException ex) { 104 } catch (IllegalStateException | JsonSyntaxException ex) {
110 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 105 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
111 } 106 }
@@ -125,7 +120,7 @@ public class DeviceApiController { @@ -125,7 +120,7 @@ public class DeviceApiController {
125 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 120 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
126 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 121 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
127 try { 122 try {
128 - process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)), true); 123 + process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)));
129 } catch (IllegalStateException | JsonSyntaxException ex) { 124 } catch (IllegalStateException | JsonSyntaxException ex) {
130 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 125 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
131 } 126 }
@@ -155,7 +150,7 @@ public class DeviceApiController { @@ -155,7 +150,7 @@ public class DeviceApiController {
155 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 150 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
156 try { 151 try {
157 JsonObject response = new JsonParser().parse(json).getAsJsonObject(); 152 JsonObject response = new JsonParser().parse(json).getAsJsonObject();
158 - process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()), true); 153 + process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()));
159 } catch (IllegalStateException | JsonSyntaxException ex) { 154 } catch (IllegalStateException | JsonSyntaxException ex) {
160 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 155 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
161 } 156 }
@@ -178,7 +173,7 @@ public class DeviceApiController { @@ -178,7 +173,7 @@ public class DeviceApiController {
178 JsonObject request = new JsonParser().parse(json).getAsJsonObject(); 173 JsonObject request = new JsonParser().parse(json).getAsJsonObject();
179 process(ctx, new ToServerRpcRequestMsg(0, 174 process(ctx, new ToServerRpcRequestMsg(0,
180 request.get("method").getAsString(), 175 request.get("method").getAsString(),
181 - request.get("params").toString()), true); 176 + request.get("params").toString()));
182 } catch (IllegalStateException | JsonSyntaxException ex) { 177 } catch (IllegalStateException | JsonSyntaxException ex) {
183 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 178 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
184 } 179 }
@@ -204,7 +199,7 @@ public class DeviceApiController { @@ -204,7 +199,7 @@ public class DeviceApiController {
204 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout); 199 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout);
205 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 200 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
206 try { 201 try {
207 - process(ctx, msg, false); 202 + process(ctx, msg);
208 } catch (IllegalStateException | JsonSyntaxException ex) { 203 } catch (IllegalStateException | JsonSyntaxException ex) {
209 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 204 responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
210 } 205 }
@@ -222,10 +217,9 @@ public class DeviceApiController { @@ -222,10 +217,9 @@ public class DeviceApiController {
222 return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout); 217 return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout);
223 } 218 }
224 219
225 - private void process(HttpSessionCtx ctx, FromDeviceMsg request, boolean isUpdate) { 220 + private void process(HttpSessionCtx ctx, FromDeviceMsg request) {
226 AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request); 221 AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request);
227 processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg)); 222 processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg));
228 - offlineService.online(ctx.getDevice(), isUpdate);  
229 } 223 }
230 224
231 private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) { 225 private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {
@@ -37,7 +37,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; @@ -37,7 +37,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
37 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
38 import org.thingsboard.server.common.transport.quota.QuotaService; 38 import org.thingsboard.server.common.transport.quota.QuotaService;
39 import org.thingsboard.server.dao.EncryptionUtil; 39 import org.thingsboard.server.dao.EncryptionUtil;
40 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
41 import org.thingsboard.server.dao.device.DeviceService; 40 import org.thingsboard.server.dao.device.DeviceService;
42 import org.thingsboard.server.dao.relation.RelationService; 41 import org.thingsboard.server.dao.relation.RelationService;
43 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 42 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@@ -73,14 +72,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -73,14 +72,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
73 private final DeviceAuthService authService; 72 private final DeviceAuthService authService;
74 private final RelationService relationService; 73 private final RelationService relationService;
75 private final QuotaService quotaService; 74 private final QuotaService quotaService;
76 - private final DeviceOfflineService offlineService;  
77 private final SslHandler sslHandler; 75 private final SslHandler sslHandler;
78 private volatile boolean connected; 76 private volatile boolean connected;
79 private volatile InetSocketAddress address; 77 private volatile InetSocketAddress address;
80 private volatile GatewaySessionCtx gatewaySessionCtx; 78 private volatile GatewaySessionCtx gatewaySessionCtx;
81 79
82 public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, 80 public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
83 - MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService, DeviceOfflineService offlineService) { 81 + MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) {
84 this.processor = processor; 82 this.processor = processor;
85 this.deviceService = deviceService; 83 this.deviceService = deviceService;
86 this.relationService = relationService; 84 this.relationService = relationService;
@@ -90,7 +88,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -90,7 +88,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
90 this.sessionId = deviceSessionCtx.getSessionId().toUidStr(); 88 this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
91 this.sslHandler = sslHandler; 89 this.sslHandler = sslHandler;
92 this.quotaService = quotaService; 90 this.quotaService = quotaService;
93 - this.offlineService = offlineService;  
94 } 91 }
95 92
96 @Override 93 @Override
@@ -132,13 +129,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -132,13 +129,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
132 case PINGREQ: 129 case PINGREQ:
133 if (checkConnected(ctx)) { 130 if (checkConnected(ctx)) {
134 ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); 131 ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
135 - offlineService.online(deviceSessionCtx.getDevice(), false);  
136 } 132 }
137 break; 133 break;
138 case DISCONNECT: 134 case DISCONNECT:
139 if (checkConnected(ctx)) { 135 if (checkConnected(ctx)) {
140 processDisconnect(ctx); 136 processDisconnect(ctx);
141 - offlineService.offline(deviceSessionCtx.getDevice());  
142 } 137 }
143 break; 138 break;
144 default: 139 default:
@@ -190,28 +185,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -190,28 +185,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
190 try { 185 try {
191 if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) { 186 if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) {
192 msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg); 187 msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg);
193 - offlineService.online(deviceSessionCtx.getDevice(), true);  
194 } else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) { 188 } else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) {
195 msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg); 189 msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg);
196 - offlineService.online(deviceSessionCtx.getDevice(), true);  
197 } else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { 190 } else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
198 msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg); 191 msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg);
199 if (msgId >= 0) { 192 if (msgId >= 0) {
200 ctx.writeAndFlush(createMqttPubAckMsg(msgId)); 193 ctx.writeAndFlush(createMqttPubAckMsg(msgId));
201 } 194 }
202 - offlineService.online(deviceSessionCtx.getDevice(), false);  
203 } else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) { 195 } else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) {
204 msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg); 196 msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg);
205 if (msgId >= 0) { 197 if (msgId >= 0) {
206 ctx.writeAndFlush(createMqttPubAckMsg(msgId)); 198 ctx.writeAndFlush(createMqttPubAckMsg(msgId));
207 } 199 }
208 - offlineService.online(deviceSessionCtx.getDevice(), true);  
209 } else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) { 200 } else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) {
210 msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg); 201 msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg);
211 if (msgId >= 0) { 202 if (msgId >= 0) {
212 ctx.writeAndFlush(createMqttPubAckMsg(msgId)); 203 ctx.writeAndFlush(createMqttPubAckMsg(msgId));
213 } 204 }
214 - offlineService.online(deviceSessionCtx.getDevice(), true);  
215 } 205 }
216 } catch (AdaptorException e) { 206 } catch (AdaptorException e) {
217 log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); 207 log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
@@ -260,7 +250,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -260,7 +250,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
260 } 250 }
261 } 251 }
262 ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); 252 ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
263 - offlineService.online(deviceSessionCtx.getDevice(), false);  
264 } 253 }
265 254
266 private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { 255 private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
@@ -284,7 +273,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -284,7 +273,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
284 } 273 }
285 } 274 }
286 ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); 275 ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
287 - offlineService.online(deviceSessionCtx.getDevice(), false);  
288 } 276 }
289 277
290 private MqttMessage createUnSubAckMessage(int msgId) { 278 private MqttMessage createUnSubAckMessage(int msgId) {
@@ -316,7 +304,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -316,7 +304,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
316 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); 304 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
317 connected = true; 305 connected = true;
318 checkGatewaySession(); 306 checkGatewaySession();
319 - offlineService.online(deviceSessionCtx.getDevice(), false);  
320 } 307 }
321 } 308 }
322 309
@@ -328,7 +315,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -328,7 +315,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
328 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); 315 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
329 connected = true; 316 connected = true;
330 checkGatewaySession(); 317 checkGatewaySession();
331 - offlineService.online(deviceSessionCtx.getDevice(), false);  
332 } else { 318 } else {
333 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); 319 ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED));
334 ctx.close(); 320 ctx.close();
@@ -379,9 +365,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -379,9 +365,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
379 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 365 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
380 log.error("[{}] Unexpected Exception", sessionId, cause); 366 log.error("[{}] Unexpected Exception", sessionId, cause);
381 ctx.close(); 367 ctx.close();
382 - if(deviceSessionCtx.getDevice() != null) {  
383 - offlineService.offline(deviceSessionCtx.getDevice());  
384 - }  
385 } 368 }
386 369
387 private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) { 370 private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
@@ -420,8 +403,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -420,8 +403,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
420 if (infoNode != null) { 403 if (infoNode != null) {
421 JsonNode gatewayNode = infoNode.get("gateway"); 404 JsonNode gatewayNode = infoNode.get("gateway");
422 if (gatewayNode != null && gatewayNode.asBoolean()) { 405 if (gatewayNode != null && gatewayNode.asBoolean()) {
423 - gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService,  
424 - relationService, deviceSessionCtx, offlineService); 406 + gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService, relationService, deviceSessionCtx);
425 } 407 }
426 } 408 }
427 } 409 }
@@ -429,8 +411,5 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -429,8 +411,5 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
429 @Override 411 @Override
430 public void operationComplete(Future<? super Void> future) throws Exception { 412 public void operationComplete(Future<? super Void> future) throws Exception {
431 processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId())); 413 processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId()));
432 - if(deviceSessionCtx.getDevice() != null) {  
433 - offlineService.offline(deviceSessionCtx.getDevice());  
434 - }  
435 } 414 }
436 } 415 }
@@ -24,7 +24,6 @@ import io.netty.handler.ssl.SslHandler; @@ -24,7 +24,6 @@ import io.netty.handler.ssl.SslHandler;
24 import org.thingsboard.server.common.transport.SessionMsgProcessor; 24 import org.thingsboard.server.common.transport.SessionMsgProcessor;
25 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 25 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
26 import org.thingsboard.server.common.transport.quota.QuotaService; 26 import org.thingsboard.server.common.transport.quota.QuotaService;
27 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
28 import org.thingsboard.server.dao.device.DeviceService; 27 import org.thingsboard.server.dao.device.DeviceService;
29 import org.thingsboard.server.dao.relation.RelationService; 28 import org.thingsboard.server.dao.relation.RelationService;
30 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 29 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@@ -43,11 +42,10 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -43,11 +42,10 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
43 private final MqttTransportAdaptor adaptor; 42 private final MqttTransportAdaptor adaptor;
44 private final MqttSslHandlerProvider sslHandlerProvider; 43 private final MqttSslHandlerProvider sslHandlerProvider;
45 private final QuotaService quotaService; 44 private final QuotaService quotaService;
46 - private final DeviceOfflineService offlineService;  
47 45
48 public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, 46 public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
49 MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider, 47 MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
50 - QuotaService quotaService, DeviceOfflineService offlineService) { 48 + QuotaService quotaService) {
51 this.processor = processor; 49 this.processor = processor;
52 this.deviceService = deviceService; 50 this.deviceService = deviceService;
53 this.authService = authService; 51 this.authService = authService;
@@ -55,7 +53,6 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -55,7 +53,6 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
55 this.adaptor = adaptor; 53 this.adaptor = adaptor;
56 this.sslHandlerProvider = sslHandlerProvider; 54 this.sslHandlerProvider = sslHandlerProvider;
57 this.quotaService = quotaService; 55 this.quotaService = quotaService;
58 - this.offlineService = offlineService;  
59 } 56 }
60 57
61 @Override 58 @Override
@@ -70,7 +67,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -70,7 +67,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
70 pipeline.addLast("encoder", MqttEncoder.INSTANCE); 67 pipeline.addLast("encoder", MqttEncoder.INSTANCE);
71 68
72 MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService, 69 MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
73 - adaptor, sslHandler, quotaService, offlineService); 70 + adaptor, sslHandler, quotaService);
74 71
75 pipeline.addLast(handler); 72 pipeline.addLast(handler);
76 ch.closeFuture().addListener(handler); 73 ch.closeFuture().addListener(handler);
@@ -30,7 +30,6 @@ import org.springframework.stereotype.Service; @@ -30,7 +30,6 @@ import org.springframework.stereotype.Service;
30 import org.thingsboard.server.common.transport.SessionMsgProcessor; 30 import org.thingsboard.server.common.transport.SessionMsgProcessor;
31 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 31 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
32 import org.thingsboard.server.common.transport.quota.QuotaService; 32 import org.thingsboard.server.common.transport.quota.QuotaService;
33 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
34 import org.thingsboard.server.dao.device.DeviceService; 33 import org.thingsboard.server.dao.device.DeviceService;
35 import org.thingsboard.server.dao.relation.RelationService; 34 import org.thingsboard.server.dao.relation.RelationService;
36 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 35 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@@ -70,9 +69,6 @@ public class MqttTransportService { @@ -70,9 +69,6 @@ public class MqttTransportService {
70 @Autowired(required = false) 69 @Autowired(required = false)
71 private QuotaService quotaService; 70 private QuotaService quotaService;
72 71
73 - @Autowired(required = false)  
74 - private DeviceOfflineService offlineService;  
75 -  
76 @Value("${mqtt.bind_address}") 72 @Value("${mqtt.bind_address}")
77 private String host; 73 private String host;
78 @Value("${mqtt.bind_port}") 74 @Value("${mqtt.bind_port}")
@@ -110,7 +106,7 @@ public class MqttTransportService { @@ -110,7 +106,7 @@ public class MqttTransportService {
110 b.group(bossGroup, workerGroup) 106 b.group(bossGroup, workerGroup)
111 .channel(NioServerSocketChannel.class) 107 .channel(NioServerSocketChannel.class)
112 .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService, 108 .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
113 - adaptor, sslHandlerProvider, quotaService, offlineService)); 109 + adaptor, sslHandlerProvider, quotaService));
114 110
115 serverChannel = b.bind(host, port).sync().channel(); 111 serverChannel = b.bind(host, port).sync().channel();
116 log.info("Mqtt transport started!"); 112 log.info("Mqtt transport started!");
@@ -36,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; @@ -36,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
36 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 36 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
37 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 37 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
38 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 38 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
39 -import org.thingsboard.server.dao.device.DeviceOfflineService;  
40 import org.thingsboard.server.dao.device.DeviceService; 39 import org.thingsboard.server.dao.device.DeviceService;
41 import org.thingsboard.server.dao.relation.RelationService; 40 import org.thingsboard.server.dao.relation.RelationService;
42 import org.thingsboard.server.transport.mqtt.MqttTransportHandler; 41 import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
@@ -62,17 +61,14 @@ public class GatewaySessionCtx { @@ -62,17 +61,14 @@ public class GatewaySessionCtx {
62 private final DeviceService deviceService; 61 private final DeviceService deviceService;
63 private final DeviceAuthService authService; 62 private final DeviceAuthService authService;
64 private final RelationService relationService; 63 private final RelationService relationService;
65 - private final DeviceOfflineService offlineService;  
66 private final Map<String, GatewayDeviceSessionCtx> devices; 64 private final Map<String, GatewayDeviceSessionCtx> devices;
67 private ChannelHandlerContext channel; 65 private ChannelHandlerContext channel;
68 66
69 - public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService,  
70 - RelationService relationService, DeviceSessionCtx gatewaySessionCtx, DeviceOfflineService offlineService) { 67 + public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, DeviceSessionCtx gatewaySessionCtx) {
71 this.processor = processor; 68 this.processor = processor;
72 this.deviceService = deviceService; 69 this.deviceService = deviceService;
73 this.authService = authService; 70 this.authService = authService;
74 this.relationService = relationService; 71 this.relationService = relationService;
75 - this.offlineService = offlineService;  
76 this.gateway = gatewaySessionCtx.getDevice(); 72 this.gateway = gatewaySessionCtx.getDevice();
77 this.gatewaySessionId = gatewaySessionCtx.getSessionId(); 73 this.gatewaySessionId = gatewaySessionCtx.getSessionId();
78 this.devices = new HashMap<>(); 74 this.devices = new HashMap<>();
@@ -102,7 +98,6 @@ public class GatewaySessionCtx { @@ -102,7 +98,6 @@ public class GatewaySessionCtx {
102 log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName); 98 log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
103 processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg()))); 99 processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg())));
104 processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg()))); 100 processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg())));
105 - offlineService.online(device, false);  
106 } 101 }
107 } 102 }
108 103
@@ -112,7 +107,6 @@ public class GatewaySessionCtx { @@ -112,7 +107,6 @@ public class GatewaySessionCtx {
112 if (deviceSessionCtx != null) { 107 if (deviceSessionCtx != null) {
113 processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId())); 108 processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId()));
114 deviceSessionCtx.setClosed(true); 109 deviceSessionCtx.setClosed(true);
115 - offlineService.offline(deviceSessionCtx.getDevice());  
116 log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName); 110 log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName);
117 } else { 111 } else {
118 log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName); 112 log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName);
@@ -123,7 +117,6 @@ public class GatewaySessionCtx { @@ -123,7 +117,6 @@ public class GatewaySessionCtx {
123 public void onGatewayDisconnect() { 117 public void onGatewayDisconnect() {
124 devices.forEach((k, v) -> { 118 devices.forEach((k, v) -> {
125 processor.process(SessionCloseMsg.onDisconnect(v.getSessionId())); 119 processor.process(SessionCloseMsg.onDisconnect(v.getSessionId()));
126 - offlineService.offline(v.getDevice());  
127 }); 120 });
128 } 121 }
129 122
@@ -145,7 +138,6 @@ public class GatewaySessionCtx { @@ -145,7 +138,6 @@ public class GatewaySessionCtx {
145 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); 138 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
146 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), 139 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
147 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); 140 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
148 - offlineService.online(deviceSessionCtx.getDevice(), true);  
149 } 141 }
150 } else { 142 } else {
151 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); 143 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@@ -162,7 +154,6 @@ public class GatewaySessionCtx { @@ -162,7 +154,6 @@ public class GatewaySessionCtx {
162 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); 154 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
163 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), 155 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
164 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); 156 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
165 - offlineService.online(deviceSessionCtx.getDevice(), true);  
166 } else { 157 } else {
167 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); 158 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
168 } 159 }
@@ -185,7 +176,6 @@ public class GatewaySessionCtx { @@ -185,7 +176,6 @@ public class GatewaySessionCtx {
185 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); 176 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
186 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), 177 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
187 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); 178 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
188 - offlineService.online(deviceSessionCtx.getDevice(), true);  
189 } 179 }
190 } else { 180 } else {
191 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); 181 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@@ -220,7 +210,6 @@ public class GatewaySessionCtx { @@ -220,7 +210,6 @@ public class GatewaySessionCtx {
220 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), 210 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
221 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); 211 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
222 ack(msg); 212 ack(msg);
223 - offlineService.online(deviceSessionCtx.getDevice(), false);  
224 } else { 213 } else {
225 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); 214 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
226 } 215 }
@@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', []) @@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', [])
485 clientSide: false 485 clientSide: false
486 } 486 }
487 }, 487 },
488 - ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], 488 + ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
489 ruleChainNodeComponent: { 489 ruleChainNodeComponent: {
490 type: 'RULE_CHAIN', 490 type: 'RULE_CHAIN',
491 name: 'rule chain', 491 name: 'rule chain',
@@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', []) @@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', [])
536 nodeClass: "tb-action-type", 536 nodeClass: "tb-action-type",
537 icon: "flash_on" 537 icon: "flash_on"
538 }, 538 },
  539 + EXTERNAL: {
  540 + value: "EXTERNAL",
  541 + name: "rulenode.type-external",
  542 + details: "rulenode.type-external-details",
  543 + nodeClass: "tb-external-type",
  544 + icon: "cloud_upload"
  545 + },
539 RULE_CHAIN: { 546 RULE_CHAIN: {
540 value: "RULE_CHAIN", 547 value: "RULE_CHAIN",
541 name: "rulenode.type-rule-chain", 548 name: "rulenode.type-rule-chain",
@@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', []) @@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', [])
1228 "type-transformation-details": "Change Message payload and Metadata", 1228 "type-transformation-details": "Change Message payload and Metadata",
1229 "type-action": "Action", 1229 "type-action": "Action",
1230 "type-action-details": "Perform special action", 1230 "type-action-details": "Perform special action",
  1231 + "type-external": "External",
  1232 + "type-external-details": "Interacts with external system",
1231 "type-rule-chain": "Rule Chain", 1233 "type-rule-chain": "Rule Chain",
1232 "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", 1234 "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
1233 "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", 1235 "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
@@ -246,6 +246,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time @@ -246,6 +246,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
246 var contextInfo = { 246 var contextInfo = {
247 headerClass: node.nodeClass, 247 headerClass: node.nodeClass,
248 icon: node.icon, 248 icon: node.icon,
  249 + iconUrl: node.iconUrl,
249 title: node.name, 250 title: node.name,
250 subtitle: node.component.name 251 subtitle: node.component.name
251 }; 252 };
@@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time @@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
805 var ruleNodeComponent = ruleNodeComponents[i]; 806 var ruleNodeComponent = ruleNodeComponents[i];
806 componentType = ruleNodeComponent.type; 807 componentType = ruleNodeComponent.type;
807 var model = vm.ruleNodeTypesModel[componentType].model; 808 var model = vm.ruleNodeTypesModel[componentType].model;
  809 + var icon = vm.types.ruleNodeType[componentType].icon;
  810 + var iconUrl = null;
  811 + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) {
  812 + icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon;
  813 + }
  814 + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) {
  815 + iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl;
  816 + }
808 var node = { 817 var node = {
809 id: 'node-lib-' + componentType + '-' + model.nodes.length, 818 id: 'node-lib-' + componentType + '-' + model.nodes.length,
810 component: ruleNodeComponent, 819 component: ruleNodeComponent,
811 name: '', 820 name: '',
812 nodeClass: vm.types.ruleNodeType[componentType].nodeClass, 821 nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
813 - icon: vm.types.ruleNodeType[componentType].icon, 822 + icon: icon,
  823 + iconUrl: iconUrl,
814 x: 30, 824 x: 30,
815 y: 10+50*model.nodes.length, 825 y: 10+50*model.nodes.length,
816 connectors: [] 826 connectors: []
@@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time @@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
904 var ruleNode = vm.ruleChainMetaData.nodes[i]; 914 var ruleNode = vm.ruleChainMetaData.nodes[i];
905 var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); 915 var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
906 if (component) { 916 if (component) {
  917 + var icon = vm.types.ruleNodeType[component.type].icon;
  918 + var iconUrl = null;
  919 + if (component.configurationDescriptor.nodeDefinition.icon) {
  920 + icon = component.configurationDescriptor.nodeDefinition.icon;
  921 + }
  922 + if (component.configurationDescriptor.nodeDefinition.iconUrl) {
  923 + iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
  924 + }
907 var node = { 925 var node = {
908 id: 'rule-chain-node-' + vm.nextNodeID++, 926 id: 'rule-chain-node-' + vm.nextNodeID++,
909 ruleNodeId: ruleNode.id, 927 ruleNodeId: ruleNode.id,
@@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time @@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
915 component: component, 933 component: component,
916 name: ruleNode.name, 934 name: ruleNode.name,
917 nodeClass: vm.types.ruleNodeType[component.type].nodeClass, 935 nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
918 - icon: vm.types.ruleNodeType[component.type].icon, 936 + icon: icon,
  937 + iconUrl: iconUrl,
919 connectors: [] 938 connectors: []
920 }; 939 };
921 if (component.configurationDescriptor.nodeDefinition.inEnabled) { 940 if (component.configurationDescriptor.nodeDefinition.inEnabled) {
@@ -76,7 +76,7 @@ @@ -76,7 +76,7 @@
76 -moz-user-select: none; 76 -moz-user-select: none;
77 -ms-user-select: none; 77 -ms-user-select: none;
78 user-select: none; 78 user-select: none;
79 - min-width: 180px; 79 + min-width: 150px;
80 } 80 }
81 .fc-canvas { 81 .fc-canvas {
82 background: #f9f9f9; 82 background: #f9f9f9;
@@ -161,6 +161,9 @@ @@ -161,6 +161,9 @@
161 &.tb-action-type { 161 &.tb-action-type {
162 background-color: #f1928f; 162 background-color: #f1928f;
163 } 163 }
  164 + &.tb-external-type {
  165 + background-color: #fbc766;
  166 + }
164 &.tb-rule-chain-type { 167 &.tb-rule-chain-type {
165 background-color: #d6c4f1; 168 background-color: #d6c4f1;
166 } 169 }
@@ -75,6 +75,8 @@ @@ -75,6 +75,8 @@
75 <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel"> 75 <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
76 <md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" 76 <md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
77 ng-mouseleave="vm.destroyTooltips()"> 77 ng-mouseleave="vm.destroyTooltips()">
  78 + <md-icon aria-label="node-type-icon"
  79 + class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
78 <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> 80 <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
79 <md-expansion-panel-icon></md-expansion-panel-icon> 81 <md-expansion-panel-icon></md-expansion-panel-icon>
80 </md-expansion-panel-collapsed> 82 </md-expansion-panel-collapsed>
@@ -82,6 +84,8 @@ @@ -82,6 +84,8 @@
82 <md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" 84 <md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
83 ng-mouseleave="vm.destroyTooltips()" 85 ng-mouseleave="vm.destroyTooltips()"
84 ng-click="vm.$mdExpansionPanel(typeId).collapse()"> 86 ng-click="vm.$mdExpansionPanel(typeId).collapse()">
  87 + <md-icon aria-label="node-type-icon"
  88 + class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
85 <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> 89 <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
86 <md-expansion-panel-icon></md-expansion-panel-icon> 90 <md-expansion-panel-icon></md-expansion-panel-icon>
87 </md-expansion-panel-header> 91 </md-expansion-panel-header>
@@ -114,8 +118,10 @@ @@ -114,8 +118,10 @@
114 </div> 118 </div>
115 <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()"> 119 <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
116 <div class="tb-context-menu-header {{vm.contextInfo.headerClass}}"> 120 <div class="tb-context-menu-header {{vm.contextInfo.headerClass}}">
117 - <md-icon aria-label="node-type-icon" 121 + <md-icon ng-if="!vm.contextInfo.iconUrl" aria-label="node-type-icon"
118 class="material-icons">{{vm.contextInfo.icon}}</md-icon> 122 class="material-icons">{{vm.contextInfo.icon}}</md-icon>
  123 + <md-icon ng-if="vm.contextInfo.iconUrl" aria-label="node-type-icon"
  124 + md-svg-icon="{{vm.contextInfo.iconUrl}}"></md-icon>
119 <div flex> 125 <div flex>
120 <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div> 126 <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div>
121 <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div> 127 <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div>
@@ -24,8 +24,10 @@ @@ -24,8 +24,10 @@
24 ng-mouseleave="callbacks.mouseLeave($event, node)"> 24 ng-mouseleave="callbacks.mouseLeave($event, node)">
25 <div class="{{flowchartConstants.nodeOverlayClass}}"></div> 25 <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
26 <div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }"> 26 <div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }">
27 - <md-icon aria-label="node-type-icon" flex="15" 27 + <md-icon ng-if="!node.iconUrl" aria-label="node-type-icon" flex="15"
28 class="material-icons">{{node.icon}}</md-icon> 28 class="material-icons">{{node.icon}}</md-icon>
  29 + <md-icon ng-if="node.iconUrl" aria-label="node-type-icon" flex="15"
  30 + md-svg-icon="{{node.iconUrl}}"></md-icon>
29 <div layout="column" flex="85" layout-align="center"> 31 <div layout="column" flex="85" layout-align="center">
30 <span class="tb-node-type">{{ node.component.name }}</span> 32 <span class="tb-node-type">{{ node.component.name }}</span>
31 <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span> 33 <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span>
@@ -267,6 +267,29 @@ div { @@ -267,6 +267,29 @@ div {
267 } 267 }
268 } 268 }
269 269
  270 +.tb-hint {
  271 + font-size: 12px;
  272 + line-height: 14px;
  273 + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2);
  274 + color: grey;
  275 + padding-bottom: 15px;
  276 + &.ng-hide, &.ng-enter, &.ng-leave.ng-leave-active {
  277 + bottom: 26px;
  278 + opacity: 0;
  279 + }
  280 + &.ng-leave, &.ng-enter.ng-enter-active {
  281 + bottom: 7px;
  282 + opacity: 1;
  283 + }
  284 +}
  285 +
  286 +md-input-container {
  287 + .tk-hint {
  288 + padding-top: 40px;
  289 + }
  290 +}
  291 +
  292 +
270 .md-caption { 293 .md-caption {
271 &.tb-required:after { 294 &.tb-required:after {
272 content: ' *'; 295 content: ' *';