Commit 1d6e1ea2b5da649c0623f84ef717009360b63baf
Merge branch 'develop/cluster-refactoring' of github.com:thingsboard/thingsboard…
… into develop/cluster-refactoring
Showing
94 changed files
with
390 additions
and
1062 deletions
... | ... | @@ -35,7 +35,4 @@ CREATE TABLE IF NOT EXISTS rule_node ( |
35 | 35 | name varchar(255), |
36 | 36 | debug_mode boolean, |
37 | 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; | |
\ No newline at end of file | ||
38 | +); | |
\ No newline at end of file | ... | ... |
... | ... | @@ -340,7 +340,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso |
340 | 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 | 344 | PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), |
345 | 345 | SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1); |
346 | 346 | pushToRuleEngineWithTimeout(context, tbMsg, msgData); |
... | ... | @@ -365,7 +365,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso |
365 | 365 | kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); |
366 | 366 | } |
367 | 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 | 369 | pushToRuleEngineWithTimeout(context, tbMsg, msgData); |
370 | 370 | } |
371 | 371 | } | ... | ... |
... | ... | @@ -62,11 +62,6 @@ class DefaultTbContext implements TbContext { |
62 | 62 | } |
63 | 63 | |
64 | 64 | @Override |
65 | - public void tellNext(TbMsg msg) { | |
66 | - tellNext(msg, (String) null); | |
67 | - } | |
68 | - | |
69 | - @Override | |
70 | 65 | public void tellNext(TbMsg msg, String relationType) { |
71 | 66 | tellNext(msg, relationType, null); |
72 | 67 | } |
... | ... | @@ -100,11 +95,6 @@ class DefaultTbContext implements TbContext { |
100 | 95 | } |
101 | 96 | |
102 | 97 | @Override |
103 | - public void spawn(TbMsg msg) { | |
104 | - throw new RuntimeException("Not Implemented!"); | |
105 | - } | |
106 | - | |
107 | - @Override | |
108 | 98 | public void ack(TbMsg msg) { |
109 | 99 | |
110 | 100 | } |
... | ... | @@ -124,12 +114,12 @@ class DefaultTbContext implements TbContext { |
124 | 114 | |
125 | 115 | @Override |
126 | 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 | 120 | @Override |
131 | 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 | 125 | @Override | ... | ... |
... | ... | @@ -269,6 +269,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
269 | 269 | |
270 | 270 | private TbMsg enrichWithRuleChainId(TbMsg tbMsg) { |
271 | 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 | 45 | import org.thingsboard.server.dao.customer.CustomerService; |
46 | 46 | import org.thingsboard.server.dao.dashboard.DashboardService; |
47 | 47 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
48 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
49 | 48 | import org.thingsboard.server.dao.device.DeviceService; |
50 | 49 | import org.thingsboard.server.dao.exception.DataValidationException; |
51 | 50 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
... | ... | @@ -136,9 +135,6 @@ public abstract class BaseController { |
136 | 135 | protected AuditLogService auditLogService; |
137 | 136 | |
138 | 137 | @Autowired |
139 | - protected DeviceOfflineService offlineService; | |
140 | - | |
141 | - @Autowired | |
142 | 138 | protected DeviceStateService deviceStateService; |
143 | 139 | |
144 | 140 | @ExceptionHandler(ThingsboardException.class) | ... | ... |
... | ... | @@ -25,9 +25,6 @@ import org.thingsboard.server.common.data.EntitySubtype; |
25 | 25 | import org.thingsboard.server.common.data.EntityType; |
26 | 26 | import org.thingsboard.server.common.data.audit.ActionType; |
27 | 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 | 28 | import org.thingsboard.server.common.data.id.CustomerId; |
32 | 29 | import org.thingsboard.server.common.data.id.DeviceId; |
33 | 30 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -37,6 +34,8 @@ import org.thingsboard.server.common.data.security.Authority; |
37 | 34 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
38 | 35 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
39 | 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 | 39 | import org.thingsboard.server.service.security.model.SecurityUser; |
41 | 40 | |
42 | 41 | import java.util.ArrayList; |
... | ... | @@ -70,7 +69,7 @@ public class DeviceController extends BaseController { |
70 | 69 | device.setTenantId(getCurrentUser().getTenantId()); |
71 | 70 | if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) { |
72 | 71 | if (device.getId() == null || device.getId().isNullUid() || |
73 | - device.getCustomerId() == null || device.getCustomerId().isNullUid()) { | |
72 | + device.getCustomerId() == null || device.getCustomerId().isNullUid()) { | |
74 | 73 | throw new ThingsboardException("You don't have permission to perform this operation!", |
75 | 74 | ThingsboardErrorCode.PERMISSION_DENIED); |
76 | 75 | } else { |
... | ... | @@ -374,32 +373,4 @@ public class DeviceController extends BaseController { |
374 | 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 | 118 | case FILTER: |
119 | 119 | case TRANSFORMATION: |
120 | 120 | case ACTION: |
121 | + case EXTERNAL: | |
121 | 122 | RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); |
122 | 123 | scannedComponent.setName(ruleNodeAnnotation.name()); |
123 | 124 | scannedComponent.setScope(ruleNodeAnnotation.scope()); |
... | ... | @@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe |
194 | 195 | nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); |
195 | 196 | nodeDefinition.setUiResources(nodeAnnotation.uiResources()); |
196 | 197 | nodeDefinition.setConfigDirective(nodeAnnotation.configDirective()); |
198 | + nodeDefinition.setIcon(nodeAnnotation.icon()); | |
199 | + nodeDefinition.setIconUrl(nodeAnnotation.iconUrl()); | |
197 | 200 | return nodeDefinition; |
198 | 201 | } |
199 | 202 | ... | ... |
... | ... | @@ -116,7 +116,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn |
116 | 116 | messageType = msgData.get(MSG_TYPE).asText(); |
117 | 117 | } |
118 | 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 | 120 | String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); |
121 | 121 | return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition()); |
122 | 122 | } catch (Throwable th) { | ... | ... |
... | ... | @@ -262,7 +262,7 @@ public class DefaultDeviceStateService implements DeviceStateService { |
262 | 262 | state.setInactivityTimeout(inactivityTimeout); |
263 | 263 | boolean oldActive = state.isActive(); |
264 | 264 | state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); |
265 | - if (!oldActive && state.isActive()) { | |
265 | + if (!oldActive && state.isActive() || oldActive && !state.isActive()) { | |
266 | 266 | saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); |
267 | 267 | } |
268 | 268 | } |
... | ... | @@ -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 | 336 | private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) { |
341 | 337 | for (AttributeKvEntry attribute : attributes) { |
342 | 338 | if (attribute.getKey().equals(attributeName)) { |
... | ... | @@ -349,7 +345,7 @@ public class DefaultDeviceStateService implements DeviceStateService { |
349 | 345 | private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { |
350 | 346 | DeviceState state = stateData.getState(); |
351 | 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 | 349 | , json.writeValueAsString(state) |
354 | 350 | , null, null, 0L); |
355 | 351 | actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)); | ... | ... |
application/src/test/java/org/thingsboard/server/system/BaseDeviceOfflineTest.java
deleted
100644 → 0
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 | 15 | */ |
16 | 16 | package org.thingsboard.server.system; |
17 | 17 | |
18 | -import com.google.common.collect.ImmutableMap; | |
19 | 18 | import org.junit.Before; |
20 | 19 | import org.junit.Test; |
21 | 20 | import org.springframework.test.web.servlet.ResultActions; |
... | ... | @@ -29,9 +28,6 @@ import java.util.Map; |
29 | 28 | import java.util.Random; |
30 | 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 | 31 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
36 | 32 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; |
37 | 33 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
... | ... | @@ -52,9 +48,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { |
52 | 48 | device = new Device(); |
53 | 49 | device.setName("My device"); |
54 | 50 | device.setType("default"); |
55 | - long currentTime = System.currentTimeMillis(); | |
56 | - device.setLastConnectTs(currentTime); | |
57 | - device.setLastUpdateTs(currentTime); | |
58 | 51 | device = doPost("/api/device", device, Device.class); |
59 | 52 | |
60 | 53 | deviceCredentials = |
... | ... | @@ -74,34 +67,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { |
74 | 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 | 70 | protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { |
106 | 71 | MockHttpServletRequestBuilder getRequest; |
107 | 72 | getRequest = get(urlTemplate, urlVariables); | ... | ... |
application/src/test/java/org/thingsboard/server/system/nosql/DeviceOfflineNoSqlTest.java
deleted
100644 → 0
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 | -} |
application/src/test/java/org/thingsboard/server/system/sql/DeviceOfflineSqlTest.java
deleted
100644 → 0
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 | 31 | private CustomerId customerId; |
32 | 32 | private String name; |
33 | 33 | private String type; |
34 | - private Long lastConnectTs; | |
35 | - private Long lastUpdateTs; | |
36 | 34 | |
37 | 35 | public Device() { |
38 | 36 | super(); |
... | ... | @@ -83,22 +81,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen |
83 | 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 | 84 | @Override |
103 | 85 | public String getSearchText() { |
104 | 86 | return getName(); |
... | ... | @@ -119,10 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen |
119 | 101 | builder.append(getAdditionalInfo()); |
120 | 102 | builder.append(", createdTime="); |
121 | 103 | builder.append(createdTime); |
122 | - builder.append(", lastUpdateTs="); | |
123 | - builder.append(lastUpdateTs); | |
124 | - builder.append(", lastConnectTs="); | |
125 | - builder.append(lastConnectTs); | |
126 | 104 | builder.append(", id="); |
127 | 105 | builder.append(id); |
128 | 106 | builder.append("]"); | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceStatusQuery.java
deleted
100644 → 0
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 | 17 | |
18 | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | 19 | |
20 | +import java.io.Serializable; | |
20 | 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 | 25 | protected I id; |
25 | 26 | ... | ... |
... | ... | @@ -106,7 +106,7 @@ public final class TbMsg implements Serializable { |
106 | 106 | } |
107 | 107 | |
108 | 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 | 15 | */ |
16 | 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 | 21 | import com.datastax.driver.core.querybuilder.Select; |
22 | 22 | import com.datastax.driver.mapping.Result; |
23 | 23 | import com.google.common.base.Function; |
... | ... | @@ -28,11 +28,9 @@ import org.springframework.stereotype.Component; |
28 | 28 | import org.thingsboard.server.common.data.Device; |
29 | 29 | import org.thingsboard.server.common.data.EntitySubtype; |
30 | 30 | import org.thingsboard.server.common.data.EntityType; |
31 | -import org.thingsboard.server.common.data.device.DeviceStatusQuery; | |
32 | 31 | import org.thingsboard.server.common.data.page.TextPageLink; |
33 | 32 | import org.thingsboard.server.dao.DaoUtil; |
34 | 33 | import org.thingsboard.server.dao.model.EntitySubtypeEntity; |
35 | -import org.thingsboard.server.dao.model.ModelConstants; | |
36 | 34 | import org.thingsboard.server.dao.model.nosql.DeviceEntity; |
37 | 35 | import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; |
38 | 36 | import org.thingsboard.server.dao.util.NoSqlDao; |
... | ... | @@ -159,7 +157,7 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt |
159 | 157 | if (result != null) { |
160 | 158 | List<EntitySubtype> entitySubtypes = new ArrayList<>(); |
161 | 159 | result.all().forEach((entitySubtypeEntity) -> |
162 | - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) | |
160 | + entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) | |
163 | 161 | ); |
164 | 162 | return entitySubtypes; |
165 | 163 | } else { |
... | ... | @@ -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 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.server.common.data.Device; |
20 | 20 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | -import org.thingsboard.server.common.data.device.DeviceStatusQuery; | |
22 | 21 | import org.thingsboard.server.common.data.page.TextPageLink; |
23 | 22 | import org.thingsboard.server.dao.Dao; |
24 | 23 | |
... | ... | @@ -28,6 +27,7 @@ import java.util.UUID; |
28 | 27 | |
29 | 28 | /** |
30 | 29 | * The Interface DeviceDao. |
30 | + * | |
31 | 31 | */ |
32 | 32 | public interface DeviceDao extends Dao<Device> { |
33 | 33 | |
... | ... | @@ -52,7 +52,7 @@ public interface DeviceDao extends Dao<Device> { |
52 | 52 | * Find devices by tenantId, type and page link. |
53 | 53 | * |
54 | 54 | * @param tenantId the tenantId |
55 | - * @param type the type | |
55 | + * @param type the type | |
56 | 56 | * @param pageLink the page link |
57 | 57 | * @return the list of device objects |
58 | 58 | */ |
... | ... | @@ -61,7 +61,7 @@ public interface DeviceDao extends Dao<Device> { |
61 | 61 | /** |
62 | 62 | * Find devices by tenantId and devices Ids. |
63 | 63 | * |
64 | - * @param tenantId the tenantId | |
64 | + * @param tenantId the tenantId | |
65 | 65 | * @param deviceIds the device Ids |
66 | 66 | * @return the list of device objects |
67 | 67 | */ |
... | ... | @@ -70,9 +70,9 @@ public interface DeviceDao extends Dao<Device> { |
70 | 70 | /** |
71 | 71 | * Find devices by tenantId, customerId and page link. |
72 | 72 | * |
73 | - * @param tenantId the tenantId | |
73 | + * @param tenantId the tenantId | |
74 | 74 | * @param customerId the customerId |
75 | - * @param pageLink the page link | |
75 | + * @param pageLink the page link | |
76 | 76 | * @return the list of device objects |
77 | 77 | */ |
78 | 78 | List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); |
... | ... | @@ -80,10 +80,10 @@ public interface DeviceDao extends Dao<Device> { |
80 | 80 | /** |
81 | 81 | * Find devices by tenantId, customerId, type and page link. |
82 | 82 | * |
83 | - * @param tenantId the tenantId | |
83 | + * @param tenantId the tenantId | |
84 | 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 | 87 | * @return the list of device objects |
88 | 88 | */ |
89 | 89 | List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink); |
... | ... | @@ -92,9 +92,9 @@ public interface DeviceDao extends Dao<Device> { |
92 | 92 | /** |
93 | 93 | * Find devices by tenantId, customerId and devices Ids. |
94 | 94 | * |
95 | - * @param tenantId the tenantId | |
95 | + * @param tenantId the tenantId | |
96 | 96 | * @param customerId the customerId |
97 | - * @param deviceIds the device Ids | |
97 | + * @param deviceIds the device Ids | |
98 | 98 | * @return the list of device objects |
99 | 99 | */ |
100 | 100 | ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds); |
... | ... | @@ -103,7 +103,7 @@ public interface DeviceDao extends Dao<Device> { |
103 | 103 | * Find devices by tenantId and device name. |
104 | 104 | * |
105 | 105 | * @param tenantId the tenantId |
106 | - * @param name the device name | |
106 | + * @param name the device name | |
107 | 107 | * @return the optional device object |
108 | 108 | */ |
109 | 109 | Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name); |
... | ... | @@ -114,31 +114,4 @@ public interface DeviceDao extends Dao<Device> { |
114 | 114 | * @return the list of tenant device type objects |
115 | 115 | */ |
116 | 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 | -} |
dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineServiceImpl.java
deleted
100644 → 0
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 | 133 | public static final String DEVICE_NAME_PROPERTY = "name"; |
134 | 134 | public static final String DEVICE_TYPE_PROPERTY = "type"; |
135 | 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 | 137 | public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; |
140 | 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 | 63 | @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) |
64 | 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 | 66 | public DeviceEntity() { |
73 | 67 | super(); |
74 | 68 | } |
... | ... | @@ -86,8 +80,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> { |
86 | 80 | this.name = device.getName(); |
87 | 81 | this.type = device.getType(); |
88 | 82 | this.additionalInfo = device.getAdditionalInfo(); |
89 | - this.lastConnectTs = device.getLastConnectTs(); | |
90 | - this.lastUpdateTs = device.getLastUpdateTs(); | |
91 | 83 | } |
92 | 84 | |
93 | 85 | public UUID getId() { |
... | ... | @@ -137,23 +129,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> { |
137 | 129 | public void setAdditionalInfo(JsonNode additionalInfo) { |
138 | 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 | 133 | @Override |
158 | 134 | public String getSearchTextSource() { |
159 | 135 | return getName(); |
... | ... | @@ -181,8 +157,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> { |
181 | 157 | device.setName(name); |
182 | 158 | device.setType(type); |
183 | 159 | device.setAdditionalInfo(additionalInfo); |
184 | - device.setLastConnectTs(lastConnectTs); | |
185 | - device.setLastUpdateTs(lastUpdateTs); | |
186 | 160 | return device; |
187 | 161 | } |
188 | 162 | ... | ... |
... | ... | @@ -34,9 +34,6 @@ import javax.persistence.Column; |
34 | 34 | import javax.persistence.Entity; |
35 | 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 | 37 | @Data |
41 | 38 | @EqualsAndHashCode(callSuper = true) |
42 | 39 | @Entity |
... | ... | @@ -63,12 +60,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT |
63 | 60 | @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) |
64 | 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 | 63 | public DeviceEntity() { |
73 | 64 | super(); |
74 | 65 | } |
... | ... | @@ -86,8 +77,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT |
86 | 77 | this.name = device.getName(); |
87 | 78 | this.type = device.getType(); |
88 | 79 | this.additionalInfo = device.getAdditionalInfo(); |
89 | - this.lastConnectTs = device.getLastConnectTs(); | |
90 | - this.lastUpdateTs = device.getLastUpdateTs(); | |
91 | 80 | } |
92 | 81 | |
93 | 82 | @Override |
... | ... | @@ -113,8 +102,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT |
113 | 102 | device.setName(name); |
114 | 103 | device.setType(type); |
115 | 104 | device.setAdditionalInfo(additionalInfo); |
116 | - device.setLastConnectTs(lastConnectTs); | |
117 | - device.setLastUpdateTs(lastUpdateTs); | |
118 | 105 | return device; |
119 | 106 | } |
120 | 107 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -79,28 +79,4 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> { |
79 | 79 | List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds); |
80 | 80 | |
81 | 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 | 15 | */ |
16 | 16 | package org.thingsboard.server.dao.sql.device; |
17 | 17 | |
18 | -import com.google.common.util.concurrent.Futures; | |
19 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
20 | -import lombok.extern.slf4j.Slf4j; | |
21 | 19 | import org.springframework.beans.factory.annotation.Autowired; |
22 | 20 | import org.springframework.data.domain.PageRequest; |
23 | 21 | import org.springframework.data.repository.CrudRepository; |
... | ... | @@ -26,7 +24,6 @@ import org.thingsboard.server.common.data.Device; |
26 | 24 | import org.thingsboard.server.common.data.EntitySubtype; |
27 | 25 | import org.thingsboard.server.common.data.EntityType; |
28 | 26 | import org.thingsboard.server.common.data.UUIDConverter; |
29 | -import org.thingsboard.server.common.data.device.DeviceStatusQuery; | |
30 | 27 | import org.thingsboard.server.common.data.id.TenantId; |
31 | 28 | import org.thingsboard.server.common.data.page.TextPageLink; |
32 | 29 | import org.thingsboard.server.dao.DaoUtil; |
... | ... | @@ -46,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; |
46 | 43 | */ |
47 | 44 | @Component |
48 | 45 | @SqlDao |
49 | -@Slf4j | |
50 | 46 | public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { |
51 | 47 | |
52 | 48 | @Autowired |
... | ... | @@ -128,73 +124,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> |
128 | 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 | 127 | private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) { |
199 | 128 | List<EntitySubtype> list = Collections.emptyList(); |
200 | 129 | if (types != null && !types.isEmpty()) { | ... | ... |
... | ... | @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors; |
22 | 22 | import lombok.Getter; |
23 | 23 | import lombok.extern.slf4j.Slf4j; |
24 | 24 | import org.springframework.beans.factory.annotation.Value; |
25 | -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
26 | 25 | import org.springframework.stereotype.Component; |
27 | 26 | import org.thingsboard.server.common.msg.TbMsg; |
28 | 27 | import org.thingsboard.server.dao.queue.MsgQueue; |
... | ... | @@ -30,12 +29,7 @@ import org.thingsboard.server.dao.util.SqlDao; |
30 | 29 | |
31 | 30 | import javax.annotation.PostConstruct; |
32 | 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 | 33 | import java.util.concurrent.ExecutionException; |
40 | 34 | import java.util.concurrent.Executors; |
41 | 35 | import java.util.concurrent.atomic.AtomicLong; |
... | ... | @@ -72,13 +66,13 @@ public class InMemoryMsgQueue implements MsgQueue { |
72 | 66 | |
73 | 67 | @Override |
74 | 68 | public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) { |
75 | - if (pendingMsgCount.get() < maxSize) { | |
69 | + if (pendingMsgCount.incrementAndGet() < maxSize) { | |
76 | 70 | return queueExecutor.submit(() -> { |
77 | 71 | data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); |
78 | - pendingMsgCount.incrementAndGet(); | |
79 | 72 | return null; |
80 | 73 | }); |
81 | 74 | } else { |
75 | + pendingMsgCount.decrementAndGet(); | |
82 | 76 | return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); |
83 | 77 | } |
84 | 78 | } | ... | ... |
... | ... | @@ -118,9 +118,7 @@ CREATE TABLE IF NOT EXISTS device ( |
118 | 118 | type varchar(255), |
119 | 119 | name varchar(255), |
120 | 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 | 124 | CREATE TABLE IF NOT EXISTS device_credentials ( | ... | ... |
... | ... | @@ -40,8 +40,6 @@ import java.util.Set; |
40 | 40 | */ |
41 | 41 | public interface TbContext { |
42 | 42 | |
43 | - void tellNext(TbMsg msg); | |
44 | - | |
45 | 43 | void tellNext(TbMsg msg, String relationType); |
46 | 44 | |
47 | 45 | void tellNext(TbMsg msg, String relationType, Throwable th); |
... | ... | @@ -54,8 +52,6 @@ public interface TbContext { |
54 | 52 | |
55 | 53 | void tellSibling(TbMsg msg, ServerAddress address); |
56 | 54 | |
57 | - void spawn(TbMsg msg); | |
58 | - | |
59 | 55 | void ack(TbMsg msg); |
60 | 56 | |
61 | 57 | void tellError(TbMsg msg, Throwable th); | ... | ... |
... | ... | @@ -73,16 +73,6 @@ |
73 | 73 | <artifactId>guava</artifactId> |
74 | 74 | </dependency> |
75 | 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 | 76 | <groupId>org.springframework</groupId> |
87 | 77 | <artifactId>spring-web</artifactId> |
88 | 78 | <scope>provided</scope> | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
... | ... | @@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
47 | 47 | "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + |
48 | 48 | "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", |
49 | 49 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
50 | - configDirective = "tbActionNodeAlarmConfig") | |
50 | + configDirective = "tbActionNodeAlarmConfig", | |
51 | + icon = "notifications_active" | |
52 | +) | |
51 | 53 | |
52 | 54 | public class TbAlarmNode implements TbNode { |
53 | 55 | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
22 | 22 | import org.thingsboard.server.common.msg.TbMsg; |
23 | 23 | |
24 | 24 | import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
25 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
25 | 26 | |
26 | 27 | @Slf4j |
27 | 28 | @RuleNode( |
... | ... | @@ -29,11 +30,13 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
29 | 30 | name = "log", |
30 | 31 | configClazz = TbLogNodeConfiguration.class, |
31 | 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 | 34 | "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + |
34 | 35 | "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", |
35 | 36 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
36 | - configDirective = "tbActionNodeLogConfig") | |
37 | + configDirective = "tbActionNodeLogConfig", | |
38 | + icon = "menu" | |
39 | +) | |
37 | 40 | |
38 | 41 | public class TbLogNode implements TbNode { |
39 | 42 | |
... | ... | @@ -52,7 +55,7 @@ public class TbLogNode implements TbNode { |
52 | 55 | withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), |
53 | 56 | toString -> { |
54 | 57 | log.info(toString); |
55 | - ctx.tellNext(msg); | |
58 | + ctx.tellNext(msg, SUCCESS); | |
56 | 59 | }, |
57 | 60 | t -> ctx.tellError(msg, t)); |
58 | 61 | } | ... | ... |
... | ... | @@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
38 | 38 | |
39 | 39 | @Slf4j |
40 | 40 | @RuleNode( |
41 | - type = ComponentType.ACTION, | |
41 | + type = ComponentType.EXTERNAL, | |
42 | 42 | name = "aws sns", |
43 | 43 | configClazz = TbSnsNodeConfiguration.class, |
44 | 44 | nodeDescription = "Publish messages to AWS SNS", |
45 | 45 | nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.", |
46 | 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 | 50 | public class TbSnsNode implements TbNode { |
50 | 51 | ... | ... |
... | ... | @@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
41 | 41 | |
42 | 42 | @Slf4j |
43 | 43 | @RuleNode( |
44 | - type = ComponentType.ACTION, | |
44 | + type = ComponentType.EXTERNAL, | |
45 | 45 | name = "aws sqs", |
46 | 46 | configClazz = TbSqsNodeConfiguration.class, |
47 | 47 | nodeDescription = "Publish messages to AWS SQS", |
48 | 48 | nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.", |
49 | 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 | 53 | public class TbSqsNode implements TbNode { |
53 | 54 | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import java.util.UUID; |
30 | 30 | import java.util.concurrent.TimeUnit; |
31 | 31 | |
32 | 32 | import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
33 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
33 | 34 | |
34 | 35 | @Slf4j |
35 | 36 | @RuleNode( |
... | ... | @@ -37,10 +38,11 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
37 | 38 | name = "generator", |
38 | 39 | configClazz = TbMsgGeneratorNodeConfiguration.class, |
39 | 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 | 42 | inEnabled = false, |
42 | 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 | 48 | public class TbMsgGeneratorNode implements TbNode { |
... | ... | @@ -71,7 +73,7 @@ public class TbMsgGeneratorNode implements TbNode { |
71 | 73 | public void onMsg(TbContext ctx, TbMsg msg) { |
72 | 74 | if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { |
73 | 75 | withCallback(generate(ctx), |
74 | - m -> {ctx.tellNext(m); sentTickMsg(ctx);}, | |
76 | + m -> {ctx.tellNext(m, SUCCESS); sentTickMsg(ctx);}, | |
75 | 77 | t -> {ctx.tellError(msg, t); sentTickMsg(ctx);}); |
76 | 78 | } |
77 | 79 | } | ... | ... |
... | ... | @@ -26,7 +26,8 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter |
26 | 26 | @Override |
27 | 27 | public TbJsFilterNodeConfiguration defaultConfiguration() { |
28 | 28 | TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); |
29 | - configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && 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 | 31 | return configuration; |
31 | 32 | } |
32 | 33 | } | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
29 | 29 | @RuleNode( |
30 | 30 | type = ComponentType.FILTER, |
31 | 31 | name = "switch", customRelations = true, |
32 | + relationTypes = {}, | |
32 | 33 | configClazz = TbJsSwitchNodeConfiguration.class, |
33 | 34 | nodeDescription = "Route incoming Message to one or multiple output chains", |
34 | 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 | 30 | public TbJsSwitchNodeConfiguration defaultConfiguration() { |
31 | 31 | TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); |
32 | 32 | configuration.setJsScript("function nextRelation(metadata, msg) {\n" + |
33 | - " return ['one','nine'];" + | |
33 | + " return ['one','nine'];\n" + | |
34 | 34 | "};\n" + |
35 | - "\n" + | |
35 | + "if(msgType === 'POST_TELEMETRY') {\n" + | |
36 | + " return ['two'];\n" + | |
37 | + "}\n" + | |
36 | 38 | "return nextRelation(metadata, msg);"); |
37 | 39 | return configuration; |
38 | 40 | } | ... | ... |
... | ... | @@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; |
31 | 31 | configClazz = TbMsgTypeFilterNodeConfiguration.class, |
32 | 32 | relationTypes = {"True", "False"}, |
33 | 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 | 35 | uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, |
36 | 36 | configDirective = "tbFilterNodeMessageTypeConfig") |
37 | 37 | public class TbMsgTypeFilterNode implements TbNode { | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | 19 | import org.thingsboard.rule.engine.api.NodeConfiguration; |
20 | +import org.thingsboard.server.common.msg.session.SessionMsgType; | |
20 | 21 | |
21 | 22 | import java.util.Arrays; |
22 | 23 | import java.util.Collections; |
... | ... | @@ -33,7 +34,10 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsg |
33 | 34 | @Override |
34 | 35 | public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { |
35 | 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 | 41 | return configuration; |
38 | 42 | } |
39 | 43 | } | ... | ... |
... | ... | @@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; |
30 | 30 | configClazz = EmptyNodeConfiguration.class, |
31 | 31 | relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"}, |
32 | 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 | 34 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
35 | 35 | configDirective = "tbNodeEmptyConfig") |
36 | 36 | public class TbMsgTypeSwitchNode implements TbNode { | ... | ... |
... | ... | @@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException; |
28 | 28 | |
29 | 29 | @Slf4j |
30 | 30 | @RuleNode( |
31 | - type = ComponentType.ACTION, | |
31 | + type = ComponentType.EXTERNAL, | |
32 | 32 | name = "kafka", |
33 | 33 | configClazz = TbKafkaNodeConfiguration.class, |
34 | 34 | nodeDescription = "Publish messages to Kafka server", |
35 | 35 | nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.", |
36 | 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 | 40 | public class TbKafkaNode implements TbNode { |
40 | 41 | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/RuleVelocityUtils.java
deleted
100644 → 0
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 | import com.fasterxml.jackson.core.JsonProcessingException; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 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 | 21 | import org.springframework.util.StringUtils; |
25 | 22 | import org.thingsboard.rule.engine.TbNodeUtils; |
26 | 23 | import org.thingsboard.rule.engine.api.*; |
27 | 24 | import org.thingsboard.server.common.data.plugin.ComponentType; |
28 | 25 | import org.thingsboard.server.common.msg.TbMsg; |
26 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
29 | 27 | |
30 | 28 | import java.io.IOException; |
31 | -import java.util.Optional; | |
32 | 29 | |
30 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
33 | 31 | import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; |
34 | 32 | |
35 | 33 | @Slf4j |
... | ... | @@ -41,34 +39,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; |
41 | 39 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
42 | 40 | "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", |
43 | 41 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
44 | - configDirective = "tbTransformationNodeToEmailConfig") | |
42 | + configDirective = "tbTransformationNodeToEmailConfig", | |
43 | + icon = "email" | |
44 | +) | |
45 | 45 | public class TbMsgToEmailNode implements TbNode { |
46 | 46 | |
47 | 47 | private static final ObjectMapper MAPPER = new ObjectMapper(); |
48 | 48 | |
49 | 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 | 51 | @Override |
59 | 52 | public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { |
60 | 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 | 56 | @Override |
... | ... | @@ -76,7 +58,7 @@ public class TbMsgToEmailNode implements TbNode { |
76 | 58 | try { |
77 | 59 | EmailPojo email = convert(msg); |
78 | 60 | TbMsg emailMsg = buildEmailMsg(ctx, msg, email); |
79 | - ctx.tellNext(emailMsg); | |
61 | + ctx.tellNext(emailMsg, SUCCESS); | |
80 | 62 | } catch (Exception ex) { |
81 | 63 | log.warn("Can not convert message to email " + ex.getMessage()); |
82 | 64 | ctx.tellError(msg, ex); |
... | ... | @@ -90,21 +72,20 @@ public class TbMsgToEmailNode implements TbNode { |
90 | 72 | |
91 | 73 | private EmailPojo convert(TbMsg msg) throws IOException { |
92 | 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 | 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 | 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 | 32 | public TbMsgToEmailNodeConfiguration defaultConfiguration() { |
33 | 33 | TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); |
34 | 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 | 38 | return configuration; |
40 | 39 | } |
41 | 40 | } | ... | ... |
... | ... | @@ -15,37 +15,57 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.mail; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | 21 | import lombok.extern.slf4j.Slf4j; |
20 | 22 | import org.apache.commons.lang3.StringUtils; |
23 | +import org.springframework.mail.javamail.JavaMailSenderImpl; | |
24 | +import org.springframework.mail.javamail.MimeMessageHelper; | |
21 | 25 | import org.thingsboard.rule.engine.TbNodeUtils; |
22 | 26 | import org.thingsboard.rule.engine.api.*; |
23 | 27 | import org.thingsboard.server.common.data.plugin.ComponentType; |
24 | 28 | import org.thingsboard.server.common.msg.TbMsg; |
25 | 29 | |
30 | +import javax.mail.internet.MimeMessage; | |
26 | 31 | import java.io.IOException; |
32 | +import java.util.Properties; | |
27 | 33 | |
28 | 34 | import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
35 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
29 | 36 | |
30 | 37 | @Slf4j |
31 | 38 | @RuleNode( |
32 | - type = ComponentType.ACTION, | |
39 | + type = ComponentType.EXTERNAL, | |
33 | 40 | name = "send email", |
34 | 41 | configClazz = TbSendEmailNodeConfiguration.class, |
35 | 42 | nodeDescription = "Log incoming messages using JS script for transformation Message into String", |
36 | 43 | nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + |
37 | 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 | 50 | public class TbSendEmailNode implements TbNode { |
40 | 51 | |
52 | + private static final String MAIL_PROP = "mail."; | |
41 | 53 | static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; |
42 | 54 | private static final ObjectMapper MAPPER = new ObjectMapper(); |
43 | 55 | |
44 | 56 | private TbSendEmailNodeConfiguration config; |
57 | + private JavaMailSenderImpl mailSender; | |
45 | 58 | |
46 | 59 | @Override |
47 | 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 | 71 | @Override |
... | ... | @@ -54,17 +74,37 @@ public class TbSendEmailNode implements TbNode { |
54 | 74 | validateType(msg.getType()); |
55 | 75 | EmailPojo email = getEmail(msg); |
56 | 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 | 78 | return null; |
60 | 79 | }), |
61 | - ok -> ctx.tellNext(msg), | |
80 | + ok -> ctx.tellNext(msg, SUCCESS), | |
62 | 81 | fail -> ctx.tellError(msg, fail)); |
63 | 82 | } catch (Exception ex) { |
64 | 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 | 108 | private EmailPojo getEmail(TbMsg msg) throws IOException { |
69 | 109 | EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class); |
70 | 110 | if (StringUtils.isBlank(email.getTo())) { |
... | ... | @@ -83,4 +123,26 @@ public class TbSendEmailNode implements TbNode { |
83 | 123 | @Override |
84 | 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 | 21 | @Data |
22 | 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 | 33 | @Override |
27 | 34 | public TbSendEmailNodeConfiguration defaultConfiguration() { |
28 | 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 | 42 | return configuration; |
31 | 43 | } |
32 | 44 | } | ... | ... |
... | ... | @@ -18,8 +18,12 @@ package org.thingsboard.rule.engine.metadata; |
18 | 18 | import com.google.common.base.Function; |
19 | 19 | import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | +import lombok.extern.slf4j.Slf4j; | |
21 | 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 | 27 | import org.thingsboard.server.common.data.id.EntityId; |
24 | 28 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
25 | 29 | import org.thingsboard.server.common.data.kv.KvEntry; |
... | ... | @@ -30,8 +34,11 @@ import java.util.List; |
30 | 34 | import java.util.stream.Collectors; |
31 | 35 | |
32 | 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 | 39 | import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
34 | 40 | |
41 | +@Slf4j | |
35 | 42 | public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode { |
36 | 43 | |
37 | 44 | private TbGetEntityAttrNodeConfiguration config; |
... | ... | @@ -46,17 +53,24 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode |
46 | 53 | try { |
47 | 54 | withCallback( |
48 | 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 | 57 | t -> ctx.tellError(msg, t)); |
55 | 58 | } catch (Throwable th) { |
56 | 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 | 74 | private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) { |
61 | 75 | ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); |
62 | 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 | 89 | String attrName = config.getAttrMapping().get(r.getKey()); |
76 | 90 | msg.getMetaData().putValue(attrName, r.getValueAsString()); |
77 | 91 | }); |
78 | - ctx.tellNext(msg); | |
92 | + ctx.tellNext(msg, SUCCESS); | |
79 | 93 | } |
80 | 94 | |
81 | 95 | @Override | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg; |
30 | 30 | import java.util.List; |
31 | 31 | |
32 | 32 | import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
33 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
33 | 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 | 42 | configClazz = TbGetAttributesNodeConfiguration.class, |
42 | 43 | nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", |
43 | 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 | 48 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
48 | 49 | configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") |
49 | 50 | public class TbGetAttributesNode implements TbNode { |
... | ... | @@ -57,22 +58,17 @@ public class TbGetAttributesNode implements TbNode { |
57 | 58 | |
58 | 59 | @Override |
59 | 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 | 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 | 72 | return Futures.immediateFuture(null); |
77 | 73 | } |
78 | 74 | ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys); |
... | ... | @@ -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 | 83 | return Futures.immediateFuture(null); |
88 | 84 | } |
89 | 85 | ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys); | ... | ... |
... | ... | @@ -29,8 +29,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
29 | 29 | configClazz = TbGetEntityAttrNodeConfiguration.class, |
30 | 30 | nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", |
31 | 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 | 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 | 35 | uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, |
35 | 36 | configDirective = "tbEnrichmentNodeCustomerAttributesConfig") |
36 | 37 | public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { | ... | ... |
... | ... | @@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
31 | 31 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
32 | 32 | "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + |
33 | 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 | 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 | 37 | uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, |
37 | 38 | configDirective = "tbEnrichmentNodeRelatedAttributesConfig") |
38 | 39 | ... | ... |
... | ... | @@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
31 | 31 | configClazz = TbGetEntityAttrNodeConfiguration.class, |
32 | 32 | nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", |
33 | 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 | 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 | 37 | uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, |
37 | 38 | configDirective = "tbEnrichmentNodeTenantAttributesConfig") |
38 | 39 | public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { | ... | ... |
... | ... | @@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException; |
43 | 43 | |
44 | 44 | @Slf4j |
45 | 45 | @RuleNode( |
46 | - type = ComponentType.ACTION, | |
46 | + type = ComponentType.EXTERNAL, | |
47 | 47 | name = "mqtt", |
48 | 48 | configClazz = TbMqttNodeConfiguration.class, |
49 | 49 | nodeDescription = "Publish messages to MQTT broker", |
50 | 50 | nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.", |
51 | 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 | 55 | public class TbMqttNode implements TbNode { |
55 | 56 | ... | ... |
... | ... | @@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
33 | 33 | |
34 | 34 | @Slf4j |
35 | 35 | @RuleNode( |
36 | - type = ComponentType.ACTION, | |
36 | + type = ComponentType.EXTERNAL, | |
37 | 37 | name = "rabbitmq", |
38 | 38 | configClazz = TbRabbitMqNodeConfiguration.class, |
39 | 39 | nodeDescription = "Publish messages to RabbitMQ", |
40 | 40 | nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.", |
41 | 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 | 45 | public class TbRabbitMqNode implements TbNode { |
45 | 46 | ... | ... |
... | ... | @@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity; |
26 | 26 | import org.springframework.http.client.Netty4ClientHttpRequestFactory; |
27 | 27 | import org.springframework.util.concurrent.ListenableFuture; |
28 | 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 | 31 | import org.thingsboard.rule.engine.TbNodeUtils; |
31 | 32 | import org.thingsboard.rule.engine.api.*; |
32 | 33 | import org.thingsboard.server.common.data.plugin.ComponentType; |
... | ... | @@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg; |
34 | 35 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
35 | 36 | |
36 | 37 | import javax.net.ssl.SSLException; |
37 | -import java.util.Map; | |
38 | 38 | import java.util.concurrent.ExecutionException; |
39 | 39 | import java.util.concurrent.TimeUnit; |
40 | 40 | |
41 | 41 | @Slf4j |
42 | 42 | @RuleNode( |
43 | - type = ComponentType.ACTION, | |
43 | + type = ComponentType.EXTERNAL, | |
44 | 44 | name = "rest api call", |
45 | 45 | configClazz = TbRestApiCallNodeConfiguration.class, |
46 | 46 | nodeDescription = "Invoke REST API calls to external REST server", |
47 | 47 | nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.", |
48 | 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 | 52 | public class TbRestApiCallNode implements TbNode { |
52 | 53 | ... | ... |
... | ... | @@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg; |
36 | 36 | nodeDescription = "Sends reply to the RPC call from device", |
37 | 37 | nodeDetails = "Expects messages with any message type. Will forward message body to the device.", |
38 | 38 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
39 | - configDirective = "tbActionNodeRpcReplyConfig" | |
39 | + configDirective = "tbActionNodeRpcReplyConfig", | |
40 | + icon = "call_merge" | |
40 | 41 | ) |
41 | 42 | public class TbSendRPCReplyNode implements TbNode { |
42 | 43 | ... | ... |
... | ... | @@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit; |
43 | 43 | nodeDescription = "Sends one-way RPC call to device", |
44 | 44 | nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.", |
45 | 45 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
46 | - configDirective = "tbActionNodeRpcRequestConfig" | |
46 | + configDirective = "tbActionNodeRpcRequestConfig", | |
47 | + icon = "call_made" | |
47 | 48 | ) |
48 | 49 | public class TbSendRPCRequestNode implements TbNode { |
49 | 50 | ... | ... |
... | ... | @@ -48,7 +48,8 @@ import java.util.Set; |
48 | 48 | nodeDescription = "Saves attributes data", |
49 | 49 | nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type", |
50 | 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 | 54 | public class TbMsgAttributesNode implements TbNode { |
54 | 55 | ... | ... |
... | ... | @@ -45,7 +45,8 @@ import java.util.Map; |
45 | 45 | nodeDescription = "Saves timeseries data", |
46 | 46 | nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type", |
47 | 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 | 51 | public class TbMsgTimeseriesNode implements TbNode { |
51 | 52 | ... | ... |
... | ... | @@ -22,6 +22,8 @@ import org.thingsboard.server.common.msg.TbMsg; |
22 | 22 | |
23 | 23 | import javax.annotation.Nullable; |
24 | 24 | |
25 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
26 | + | |
25 | 27 | /** |
26 | 28 | * Created by ashvayka on 02.04.18. |
27 | 29 | */ |
... | ... | @@ -32,7 +34,7 @@ class TelemetryNodeCallback implements FutureCallback<Void> { |
32 | 34 | |
33 | 35 | @Override |
34 | 36 | public void onSuccess(@Nullable Void result) { |
35 | - ctx.tellNext(msg); | |
37 | + ctx.tellNext(msg, SUCCESS); | |
36 | 38 | } |
37 | 39 | |
38 | 40 | @Override | ... | ... |
... | ... | @@ -18,10 +18,15 @@ package org.thingsboard.rule.engine.transform; |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import lombok.extern.slf4j.Slf4j; |
20 | 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 | 25 | import org.thingsboard.server.common.msg.TbMsg; |
23 | 26 | |
24 | 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 | 32 | * Created by ashvayka on 19.01.18. |
... | ... | @@ -39,20 +44,18 @@ public abstract class TbAbstractTransformNode implements TbNode { |
39 | 44 | @Override |
40 | 45 | public void onMsg(TbContext ctx, TbMsg msg) { |
41 | 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 | 54 | t -> ctx.tellError(msg, t)); |
44 | 55 | } |
45 | 56 | |
46 | 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 | 59 | public void setConfig(TbTransformNodeConfiguration config) { |
57 | 60 | this.config = config; |
58 | 61 | } | ... | ... |
... | ... | @@ -21,7 +21,10 @@ import com.google.common.util.concurrent.Futures; |
21 | 21 | import com.google.common.util.concurrent.ListenableFuture; |
22 | 22 | import lombok.extern.slf4j.Slf4j; |
23 | 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 | 28 | import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; |
26 | 29 | import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; |
27 | 30 | import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; |
... | ... | @@ -34,13 +37,15 @@ import java.util.HashSet; |
34 | 37 | @Slf4j |
35 | 38 | @RuleNode( |
36 | 39 | type = ComponentType.TRANSFORMATION, |
37 | - name="change originator", | |
40 | + name = "change originator", | |
38 | 41 | configClazz = TbChangeOriginatorNodeConfiguration.class, |
39 | 42 | nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", |
40 | 43 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
41 | 44 | "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", |
42 | 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 | 49 | public class TbChangeOriginatorNode extends TbAbstractTransformNode { |
45 | 50 | |
46 | 51 | protected static final String CUSTOMER_SOURCE = "CUSTOMER"; |
... | ... | @@ -59,7 +64,12 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { |
59 | 64 | @Override |
60 | 65 | protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) { |
61 | 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 | 75 | private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) { | ... | ... |
... | ... | @@ -43,7 +43,6 @@ public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfigur |
43 | 43 | relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); |
44 | 44 | configuration.setRelationsQuery(relationsQuery); |
45 | 45 | |
46 | - configuration.setStartNewChain(false); | |
47 | 46 | return configuration; |
48 | 47 | } |
49 | 48 | } | ... | ... |
... | ... | @@ -26,7 +26,7 @@ import org.thingsboard.server.common.msg.TbMsg; |
26 | 26 | name = "script", |
27 | 27 | configClazz = TbTransformMsgNodeConfiguration.class, |
28 | 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 | 30 | "<code>metadata</code> - is a Message metadata.<br/>" + |
31 | 31 | "<code>msg</code> - is a Message payload.<br/>" + |
32 | 32 | "<code>msgType</code> - is a Message type.<br/>" + | ... | ... |
... | ... | @@ -26,7 +26,6 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio |
26 | 26 | @Override |
27 | 27 | public TbTransformMsgNodeConfiguration defaultConfiguration() { |
28 | 28 | TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); |
29 | - configuration.setStartNewChain(false); | |
30 | 29 | configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};"); |
31 | 30 | return configuration; |
32 | 31 | } | ... | ... |
... | ... | @@ -45,6 +45,6 @@ public class EntitiesCustomerIdAsyncLoader { |
45 | 45 | private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) { |
46 | 46 | return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> { |
47 | 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 | 40 | if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { |
41 | 41 | return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
42 | 42 | r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) |
43 | - : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); | |
43 | + : Futures.immediateFuture(null)); | |
44 | 44 | } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { |
45 | 45 | return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
46 | 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 | 49 | return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); |
50 | 50 | } | ... | ... |
... | ... | @@ -53,6 +53,6 @@ public class EntitiesTenantIdAsyncLoader { |
53 | 53 | private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) { |
54 | 54 | return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> { |
55 | 55 | return in != null ? Futures.immediateFuture(in.getTenantId()) |
56 | - : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); | |
56 | + : Futures.immediateFuture(null);}); | |
57 | 57 | } |
58 | 58 | } | ... | ... |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
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:'...'}}" }\'>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> </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:'...'}}" }\'>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> </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 | 4 | //# sourceMappingURL=rulenode-core-config.js.map |
\ No newline at end of file | ... | ... |
... | ... | @@ -59,6 +59,9 @@ public class TbMsgToEmailNodeTest { |
59 | 59 | initWithScript(); |
60 | 60 | metaData.putValue("username", "oreo"); |
61 | 61 | metaData.putValue("userEmail", "user@email.io"); |
62 | + metaData.putValue("name", "temp"); | |
63 | + metaData.putValue("passed", "5"); | |
64 | + metaData.putValue("count", "100"); | |
62 | 65 | TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); |
63 | 66 | |
64 | 67 | emailNode.onMsg(ctx, msg); |
... | ... | @@ -91,9 +94,9 @@ public class TbMsgToEmailNodeTest { |
91 | 94 | try { |
92 | 95 | TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); |
93 | 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 | 100 | ObjectMapper mapper = new ObjectMapper(); |
98 | 101 | TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); |
99 | 102 | ... | ... |
... | ... | @@ -31,12 +31,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; |
31 | 31 | import org.thingsboard.server.common.data.Device; |
32 | 32 | import org.thingsboard.server.common.data.User; |
33 | 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 | 35 | import org.thingsboard.server.common.data.kv.*; |
41 | 36 | import org.thingsboard.server.common.msg.TbMsg; |
42 | 37 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
... | ... | @@ -56,6 +51,8 @@ import static org.junit.Assert.assertTrue; |
56 | 51 | import static org.mockito.Matchers.same; |
57 | 52 | import static org.mockito.Mockito.verify; |
58 | 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 | 56 | import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
60 | 57 | |
61 | 58 | @RunWith(MockitoJUnitRunner.class) |
... | ... | @@ -148,7 +145,7 @@ public class TbGetCustomerAttributeNodeTest { |
148 | 145 | } |
149 | 146 | |
150 | 147 | @Test |
151 | - public void errorThrownIfCustomerCannotBeFound() { | |
148 | + public void failedChainUsedIfCustomerCannotBeFound() { | |
152 | 149 | UserId userId = new UserId(UUIDs.timeBased()); |
153 | 150 | CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
154 | 151 | User user = new User(); |
... | ... | @@ -159,13 +156,9 @@ public class TbGetCustomerAttributeNodeTest { |
159 | 156 | when(ctx.getUserService()).thenReturn(userService); |
160 | 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 | 162 | assertTrue(msg.getMetaData().getData().isEmpty()); |
170 | 163 | } |
171 | 164 | |
... | ... | @@ -252,7 +245,7 @@ public class TbGetCustomerAttributeNodeTest { |
252 | 245 | .thenReturn(Futures.immediateFuture(timeseries)); |
253 | 246 | |
254 | 247 | node.onMsg(ctx, msg); |
255 | - verify(ctx).tellNext(msg); | |
248 | + verify(ctx).tellNext(msg, SUCCESS); | |
256 | 249 | assertEquals(msg.getMetaData().getValue("tempo"), "highest"); |
257 | 250 | } |
258 | 251 | |
... | ... | @@ -264,7 +257,7 @@ public class TbGetCustomerAttributeNodeTest { |
264 | 257 | .thenReturn(Futures.immediateFuture(attributes)); |
265 | 258 | |
266 | 259 | node.onMsg(ctx, msg); |
267 | - verify(ctx).tellNext(msg); | |
260 | + verify(ctx).tellNext(msg, SUCCESS); | |
268 | 261 | assertEquals(msg.getMetaData().getValue("tempo"), "high"); |
269 | 262 | } |
270 | 263 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals; |
40 | 40 | import static org.mockito.Matchers.same; |
41 | 41 | import static org.mockito.Mockito.verify; |
42 | 42 | import static org.mockito.Mockito.when; |
43 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; | |
43 | 44 | |
44 | 45 | @RunWith(MockitoJUnitRunner.class) |
45 | 46 | public class TbChangeOriginatorNodeTest { |
... | ... | @@ -54,7 +55,7 @@ public class TbChangeOriginatorNodeTest { |
54 | 55 | |
55 | 56 | @Test |
56 | 57 | public void originatorCanBeChangedToCustomerId() throws TbNodeException { |
57 | - init(false); | |
58 | + init(); | |
58 | 59 | AssetId assetId = new AssetId(UUIDs.timeBased()); |
59 | 60 | CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
60 | 61 | Asset asset = new Asset(); |
... | ... | @@ -82,7 +83,7 @@ public class TbChangeOriginatorNodeTest { |
82 | 83 | |
83 | 84 | @Test |
84 | 85 | public void newChainCanBeStarted() throws TbNodeException { |
85 | - init(true); | |
86 | + init(); | |
86 | 87 | AssetId assetId = new AssetId(UUIDs.timeBased()); |
87 | 88 | CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
88 | 89 | Asset asset = new Asset(); |
... | ... | @@ -109,7 +110,7 @@ public class TbChangeOriginatorNodeTest { |
109 | 110 | |
110 | 111 | @Test |
111 | 112 | public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException { |
112 | - init(true); | |
113 | + init(); | |
113 | 114 | AssetId assetId = new AssetId(UUIDs.timeBased()); |
114 | 115 | CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
115 | 116 | Asset asset = new Asset(); |
... | ... | @@ -121,19 +122,15 @@ public class TbChangeOriginatorNodeTest { |
121 | 122 | TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); |
122 | 123 | |
123 | 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 | 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 | 132 | TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration(); |
135 | 133 | config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); |
136 | - config.setStartNewChain(startNewChain); | |
137 | 134 | ObjectMapper mapper = new ObjectMapper(); |
138 | 135 | TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); |
139 | 136 | ... | ... |
... | ... | @@ -38,6 +38,7 @@ import java.util.concurrent.Callable; |
38 | 38 | import static org.junit.Assert.assertEquals; |
39 | 39 | import static org.mockito.Matchers.same; |
40 | 40 | import static org.mockito.Mockito.*; |
41 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
41 | 42 | |
42 | 43 | @RunWith(MockitoJUnitRunner.class) |
43 | 44 | public class TbTransformMsgNodeTest { |
... | ... | @@ -53,7 +54,7 @@ public class TbTransformMsgNodeTest { |
53 | 54 | |
54 | 55 | @Test |
55 | 56 | public void metadataCanBeUpdated() throws TbNodeException, ScriptException { |
56 | - initWithScript(false); | |
57 | + initWithScript(); | |
57 | 58 | TbMsgMetaData metaData = new TbMsgMetaData(); |
58 | 59 | metaData.putValue("temp", "7"); |
59 | 60 | String rawJson = "{\"passed\": 5}"; |
... | ... | @@ -68,37 +69,14 @@ public class TbTransformMsgNodeTest { |
68 | 69 | node.onMsg(ctx, msg); |
69 | 70 | verify(ctx).getJsExecutor(); |
70 | 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 | 73 | TbMsg actualMsg = captor.getValue(); |
96 | 74 | assertEquals(transformedMsg, actualMsg); |
97 | 75 | } |
98 | 76 | |
99 | 77 | @Test |
100 | 78 | public void exceptionHandledCorrectly() throws TbNodeException, ScriptException { |
101 | - initWithScript(false); | |
79 | + initWithScript(); | |
102 | 80 | TbMsgMetaData metaData = new TbMsgMetaData(); |
103 | 81 | metaData.putValue("temp", "7"); |
104 | 82 | String rawJson = "{\"passed\": 5"; |
... | ... | @@ -113,10 +91,9 @@ public class TbTransformMsgNodeTest { |
113 | 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 | 95 | TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration(); |
118 | 96 | config.setJsScript("scr"); |
119 | - config.setStartNewChain(startChain); | |
120 | 97 | ObjectMapper mapper = new ObjectMapper(); |
121 | 98 | TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); |
122 | 99 | ... | ... |
... | ... | @@ -35,7 +35,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; |
35 | 35 | import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
36 | 36 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
37 | 37 | import org.thingsboard.server.common.transport.quota.QuotaService; |
38 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
39 | 38 | import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
40 | 39 | import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; |
41 | 40 | import org.thingsboard.server.transport.coap.session.CoapSessionCtx; |
... | ... | @@ -54,17 +53,15 @@ public class CoapTransportResource extends CoapResource { |
54 | 53 | private final SessionMsgProcessor processor; |
55 | 54 | private final DeviceAuthService authService; |
56 | 55 | private final QuotaService quotaService; |
57 | - private final DeviceOfflineService offlineService; | |
58 | 56 | private final Field observerField; |
59 | 57 | private final long timeout; |
60 | 58 | |
61 | 59 | public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, |
62 | - long timeout, QuotaService quotaService, DeviceOfflineService offlineService) { | |
60 | + long timeout, QuotaService quotaService) { | |
63 | 61 | super(name); |
64 | 62 | this.processor = processor; |
65 | 63 | this.authService = authService; |
66 | 64 | this.quotaService = quotaService; |
67 | - this.offlineService = offlineService; | |
68 | 65 | this.adaptor = adaptor; |
69 | 66 | this.timeout = timeout; |
70 | 67 | // This is important to turn off existing observable logic in |
... | ... | @@ -171,7 +168,6 @@ public class CoapTransportResource extends CoapResource { |
171 | 168 | case TO_SERVER_RPC_REQUEST: |
172 | 169 | ctx.setSessionType(SessionType.SYNC); |
173 | 170 | msg = adaptor.convertToActorMsg(ctx, type, request); |
174 | - offlineService.online(ctx.getDevice(), true); | |
175 | 171 | break; |
176 | 172 | case SUBSCRIBE_ATTRIBUTES_REQUEST: |
177 | 173 | case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
... | ... | @@ -179,13 +175,11 @@ public class CoapTransportResource extends CoapResource { |
179 | 175 | advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); |
180 | 176 | ctx.setSessionType(SessionType.ASYNC); |
181 | 177 | msg = adaptor.convertToActorMsg(ctx, type, request); |
182 | - offlineService.online(ctx.getDevice(), false); | |
183 | 178 | break; |
184 | 179 | case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
185 | 180 | case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
186 | 181 | ctx.setSessionType(SessionType.ASYNC); |
187 | 182 | msg = adaptor.convertToActorMsg(ctx, type, request); |
188 | - offlineService.online(ctx.getDevice(), false); | |
189 | 183 | break; |
190 | 184 | default: |
191 | 185 | log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); | ... | ... |
... | ... | @@ -27,7 +27,6 @@ import org.springframework.stereotype.Service; |
27 | 27 | import org.thingsboard.server.common.transport.SessionMsgProcessor; |
28 | 28 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
29 | 29 | import org.thingsboard.server.common.transport.quota.QuotaService; |
30 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
31 | 30 | import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
32 | 31 | |
33 | 32 | import javax.annotation.PostConstruct; |
... | ... | @@ -58,9 +57,6 @@ public class CoapTransportService { |
58 | 57 | @Autowired(required = false) |
59 | 58 | private QuotaService quotaService; |
60 | 59 | |
61 | - @Autowired(required = false) | |
62 | - private DeviceOfflineService offlineService; | |
63 | - | |
64 | 60 | |
65 | 61 | @Value("${coap.bind_address}") |
66 | 62 | private String host; |
... | ... | @@ -90,7 +86,7 @@ public class CoapTransportService { |
90 | 86 | |
91 | 87 | private void createResources() { |
92 | 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 | 90 | server.add(api); |
95 | 91 | } |
96 | 92 | ... | ... |
... | ... | @@ -15,7 +15,6 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.transport.coap; |
17 | 17 | |
18 | -import com.google.common.util.concurrent.ListenableFuture; | |
19 | 18 | import lombok.extern.slf4j.Slf4j; |
20 | 19 | import org.eclipse.californium.core.CoapClient; |
21 | 20 | import org.eclipse.californium.core.CoapResponse; |
... | ... | @@ -32,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext; |
32 | 31 | import org.springframework.test.annotation.DirtiesContext.ClassMode; |
33 | 32 | import org.springframework.test.context.junit4.SpringRunner; |
34 | 33 | import org.thingsboard.server.common.data.Device; |
35 | -import org.thingsboard.server.common.data.device.DeviceStatusQuery; | |
36 | 34 | import org.thingsboard.server.common.data.id.CustomerId; |
37 | 35 | import org.thingsboard.server.common.data.id.DeviceId; |
38 | 36 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -53,7 +51,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; |
53 | 51 | import org.thingsboard.server.common.transport.auth.DeviceAuthResult; |
54 | 52 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
55 | 53 | import org.thingsboard.server.common.transport.quota.QuotaService; |
56 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
57 | 54 | |
58 | 55 | import java.util.ArrayList; |
59 | 56 | import java.util.List; |
... | ... | @@ -140,31 +137,6 @@ public class CoapServerTest { |
140 | 137 | public static QuotaService quotaService() { |
141 | 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 | 142 | @Autowired | ... | ... |
... | ... | @@ -26,7 +26,6 @@ import org.springframework.http.ResponseEntity; |
26 | 26 | import org.springframework.util.StringUtils; |
27 | 27 | import org.springframework.web.bind.annotation.*; |
28 | 28 | import org.springframework.web.context.request.async.DeferredResult; |
29 | -import org.thingsboard.server.common.data.Device; | |
30 | 29 | import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
31 | 30 | import org.thingsboard.server.common.msg.core.*; |
32 | 31 | import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; |
... | ... | @@ -37,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; |
37 | 36 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
38 | 37 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
39 | 38 | import org.thingsboard.server.common.transport.quota.QuotaService; |
40 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
41 | 39 | import org.thingsboard.server.transport.http.session.HttpSessionCtx; |
42 | 40 | |
43 | 41 | import javax.servlet.http.HttpServletRequest; |
... | ... | @@ -65,9 +63,6 @@ public class DeviceApiController { |
65 | 63 | @Autowired(required = false) |
66 | 64 | private QuotaService quotaService; |
67 | 65 | |
68 | - @Autowired(required = false) | |
69 | - private DeviceOfflineService offlineService; | |
70 | - | |
71 | 66 | @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") |
72 | 67 | public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, |
73 | 68 | @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, |
... | ... | @@ -87,7 +82,7 @@ public class DeviceApiController { |
87 | 82 | Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null; |
88 | 83 | request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet); |
89 | 84 | } |
90 | - process(ctx, request, false); | |
85 | + process(ctx, request); | |
91 | 86 | } else { |
92 | 87 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); |
93 | 88 | } |
... | ... | @@ -105,7 +100,7 @@ public class DeviceApiController { |
105 | 100 | HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); |
106 | 101 | if (ctx.login(new DeviceTokenCredentials(deviceToken))) { |
107 | 102 | try { |
108 | - process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)), true); | |
103 | + process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json))); | |
109 | 104 | } catch (IllegalStateException | JsonSyntaxException ex) { |
110 | 105 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
111 | 106 | } |
... | ... | @@ -125,7 +120,7 @@ public class DeviceApiController { |
125 | 120 | HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); |
126 | 121 | if (ctx.login(new DeviceTokenCredentials(deviceToken))) { |
127 | 122 | try { |
128 | - process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)), true); | |
123 | + process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json))); | |
129 | 124 | } catch (IllegalStateException | JsonSyntaxException ex) { |
130 | 125 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
131 | 126 | } |
... | ... | @@ -155,7 +150,7 @@ public class DeviceApiController { |
155 | 150 | if (ctx.login(new DeviceTokenCredentials(deviceToken))) { |
156 | 151 | try { |
157 | 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 | 154 | } catch (IllegalStateException | JsonSyntaxException ex) { |
160 | 155 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
161 | 156 | } |
... | ... | @@ -178,7 +173,7 @@ public class DeviceApiController { |
178 | 173 | JsonObject request = new JsonParser().parse(json).getAsJsonObject(); |
179 | 174 | process(ctx, new ToServerRpcRequestMsg(0, |
180 | 175 | request.get("method").getAsString(), |
181 | - request.get("params").toString()), true); | |
176 | + request.get("params").toString())); | |
182 | 177 | } catch (IllegalStateException | JsonSyntaxException ex) { |
183 | 178 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
184 | 179 | } |
... | ... | @@ -204,7 +199,7 @@ public class DeviceApiController { |
204 | 199 | HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout); |
205 | 200 | if (ctx.login(new DeviceTokenCredentials(deviceToken))) { |
206 | 201 | try { |
207 | - process(ctx, msg, false); | |
202 | + process(ctx, msg); | |
208 | 203 | } catch (IllegalStateException | JsonSyntaxException ex) { |
209 | 204 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
210 | 205 | } |
... | ... | @@ -222,10 +217,9 @@ public class DeviceApiController { |
222 | 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 | 221 | AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request); |
227 | 222 | processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg)); |
228 | - offlineService.online(ctx.getDevice(), isUpdate); | |
229 | 223 | } |
230 | 224 | |
231 | 225 | private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) { | ... | ... |
... | ... | @@ -37,7 +37,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
37 | 37 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
38 | 38 | import org.thingsboard.server.common.transport.quota.QuotaService; |
39 | 39 | import org.thingsboard.server.dao.EncryptionUtil; |
40 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
41 | 40 | import org.thingsboard.server.dao.device.DeviceService; |
42 | 41 | import org.thingsboard.server.dao.relation.RelationService; |
43 | 42 | import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; |
... | ... | @@ -73,14 +72,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
73 | 72 | private final DeviceAuthService authService; |
74 | 73 | private final RelationService relationService; |
75 | 74 | private final QuotaService quotaService; |
76 | - private final DeviceOfflineService offlineService; | |
77 | 75 | private final SslHandler sslHandler; |
78 | 76 | private volatile boolean connected; |
79 | 77 | private volatile InetSocketAddress address; |
80 | 78 | private volatile GatewaySessionCtx gatewaySessionCtx; |
81 | 79 | |
82 | 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 | 82 | this.processor = processor; |
85 | 83 | this.deviceService = deviceService; |
86 | 84 | this.relationService = relationService; |
... | ... | @@ -90,7 +88,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
90 | 88 | this.sessionId = deviceSessionCtx.getSessionId().toUidStr(); |
91 | 89 | this.sslHandler = sslHandler; |
92 | 90 | this.quotaService = quotaService; |
93 | - this.offlineService = offlineService; | |
94 | 91 | } |
95 | 92 | |
96 | 93 | @Override |
... | ... | @@ -132,13 +129,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
132 | 129 | case PINGREQ: |
133 | 130 | if (checkConnected(ctx)) { |
134 | 131 | ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); |
135 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
136 | 132 | } |
137 | 133 | break; |
138 | 134 | case DISCONNECT: |
139 | 135 | if (checkConnected(ctx)) { |
140 | 136 | processDisconnect(ctx); |
141 | - offlineService.offline(deviceSessionCtx.getDevice()); | |
142 | 137 | } |
143 | 138 | break; |
144 | 139 | default: |
... | ... | @@ -190,28 +185,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
190 | 185 | try { |
191 | 186 | if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) { |
192 | 187 | msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg); |
193 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
194 | 188 | } else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) { |
195 | 189 | msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg); |
196 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
197 | 190 | } else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { |
198 | 191 | msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg); |
199 | 192 | if (msgId >= 0) { |
200 | 193 | ctx.writeAndFlush(createMqttPubAckMsg(msgId)); |
201 | 194 | } |
202 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
203 | 195 | } else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) { |
204 | 196 | msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg); |
205 | 197 | if (msgId >= 0) { |
206 | 198 | ctx.writeAndFlush(createMqttPubAckMsg(msgId)); |
207 | 199 | } |
208 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
209 | 200 | } else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) { |
210 | 201 | msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg); |
211 | 202 | if (msgId >= 0) { |
212 | 203 | ctx.writeAndFlush(createMqttPubAckMsg(msgId)); |
213 | 204 | } |
214 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
215 | 205 | } |
216 | 206 | } catch (AdaptorException e) { |
217 | 207 | log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); |
... | ... | @@ -260,7 +250,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
260 | 250 | } |
261 | 251 | } |
262 | 252 | ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); |
263 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
264 | 253 | } |
265 | 254 | |
266 | 255 | private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { |
... | ... | @@ -284,7 +273,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
284 | 273 | } |
285 | 274 | } |
286 | 275 | ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); |
287 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
288 | 276 | } |
289 | 277 | |
290 | 278 | private MqttMessage createUnSubAckMessage(int msgId) { |
... | ... | @@ -316,7 +304,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
316 | 304 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); |
317 | 305 | connected = true; |
318 | 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 | 315 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); |
329 | 316 | connected = true; |
330 | 317 | checkGatewaySession(); |
331 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
332 | 318 | } else { |
333 | 319 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); |
334 | 320 | ctx.close(); |
... | ... | @@ -379,9 +365,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
379 | 365 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { |
380 | 366 | log.error("[{}] Unexpected Exception", sessionId, cause); |
381 | 367 | ctx.close(); |
382 | - if(deviceSessionCtx.getDevice() != null) { | |
383 | - offlineService.offline(deviceSessionCtx.getDevice()); | |
384 | - } | |
385 | 368 | } |
386 | 369 | |
387 | 370 | private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) { |
... | ... | @@ -420,8 +403,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
420 | 403 | if (infoNode != null) { |
421 | 404 | JsonNode gatewayNode = infoNode.get("gateway"); |
422 | 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 | 411 | @Override |
430 | 412 | public void operationComplete(Future<? super Void> future) throws Exception { |
431 | 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 | 24 | import org.thingsboard.server.common.transport.SessionMsgProcessor; |
25 | 25 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
26 | 26 | import org.thingsboard.server.common.transport.quota.QuotaService; |
27 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
28 | 27 | import org.thingsboard.server.dao.device.DeviceService; |
29 | 28 | import org.thingsboard.server.dao.relation.RelationService; |
30 | 29 | import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; |
... | ... | @@ -43,11 +42,10 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha |
43 | 42 | private final MqttTransportAdaptor adaptor; |
44 | 43 | private final MqttSslHandlerProvider sslHandlerProvider; |
45 | 44 | private final QuotaService quotaService; |
46 | - private final DeviceOfflineService offlineService; | |
47 | 45 | |
48 | 46 | public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, |
49 | 47 | MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider, |
50 | - QuotaService quotaService, DeviceOfflineService offlineService) { | |
48 | + QuotaService quotaService) { | |
51 | 49 | this.processor = processor; |
52 | 50 | this.deviceService = deviceService; |
53 | 51 | this.authService = authService; |
... | ... | @@ -55,7 +53,6 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha |
55 | 53 | this.adaptor = adaptor; |
56 | 54 | this.sslHandlerProvider = sslHandlerProvider; |
57 | 55 | this.quotaService = quotaService; |
58 | - this.offlineService = offlineService; | |
59 | 56 | } |
60 | 57 | |
61 | 58 | @Override |
... | ... | @@ -70,7 +67,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha |
70 | 67 | pipeline.addLast("encoder", MqttEncoder.INSTANCE); |
71 | 68 | |
72 | 69 | MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService, |
73 | - adaptor, sslHandler, quotaService, offlineService); | |
70 | + adaptor, sslHandler, quotaService); | |
74 | 71 | |
75 | 72 | pipeline.addLast(handler); |
76 | 73 | ch.closeFuture().addListener(handler); | ... | ... |
... | ... | @@ -30,7 +30,6 @@ import org.springframework.stereotype.Service; |
30 | 30 | import org.thingsboard.server.common.transport.SessionMsgProcessor; |
31 | 31 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
32 | 32 | import org.thingsboard.server.common.transport.quota.QuotaService; |
33 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
34 | 33 | import org.thingsboard.server.dao.device.DeviceService; |
35 | 34 | import org.thingsboard.server.dao.relation.RelationService; |
36 | 35 | import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; |
... | ... | @@ -70,9 +69,6 @@ public class MqttTransportService { |
70 | 69 | @Autowired(required = false) |
71 | 70 | private QuotaService quotaService; |
72 | 71 | |
73 | - @Autowired(required = false) | |
74 | - private DeviceOfflineService offlineService; | |
75 | - | |
76 | 72 | @Value("${mqtt.bind_address}") |
77 | 73 | private String host; |
78 | 74 | @Value("${mqtt.bind_port}") |
... | ... | @@ -110,7 +106,7 @@ public class MqttTransportService { |
110 | 106 | b.group(bossGroup, workerGroup) |
111 | 107 | .channel(NioServerSocketChannel.class) |
112 | 108 | .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService, |
113 | - adaptor, sslHandlerProvider, quotaService, offlineService)); | |
109 | + adaptor, sslHandlerProvider, quotaService)); | |
114 | 110 | |
115 | 111 | serverChannel = b.bind(host, port).sync().channel(); |
116 | 112 | log.info("Mqtt transport started!"); | ... | ... |
... | ... | @@ -36,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor; |
36 | 36 | import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
37 | 37 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
38 | 38 | import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
39 | -import org.thingsboard.server.dao.device.DeviceOfflineService; | |
40 | 39 | import org.thingsboard.server.dao.device.DeviceService; |
41 | 40 | import org.thingsboard.server.dao.relation.RelationService; |
42 | 41 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
... | ... | @@ -62,17 +61,14 @@ public class GatewaySessionCtx { |
62 | 61 | private final DeviceService deviceService; |
63 | 62 | private final DeviceAuthService authService; |
64 | 63 | private final RelationService relationService; |
65 | - private final DeviceOfflineService offlineService; | |
66 | 64 | private final Map<String, GatewayDeviceSessionCtx> devices; |
67 | 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 | 68 | this.processor = processor; |
72 | 69 | this.deviceService = deviceService; |
73 | 70 | this.authService = authService; |
74 | 71 | this.relationService = relationService; |
75 | - this.offlineService = offlineService; | |
76 | 72 | this.gateway = gatewaySessionCtx.getDevice(); |
77 | 73 | this.gatewaySessionId = gatewaySessionCtx.getSessionId(); |
78 | 74 | this.devices = new HashMap<>(); |
... | ... | @@ -102,7 +98,6 @@ public class GatewaySessionCtx { |
102 | 98 | log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName); |
103 | 99 | processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg()))); |
104 | 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 | 107 | if (deviceSessionCtx != null) { |
113 | 108 | processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId())); |
114 | 109 | deviceSessionCtx.setClosed(true); |
115 | - offlineService.offline(deviceSessionCtx.getDevice()); | |
116 | 110 | log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName); |
117 | 111 | } else { |
118 | 112 | log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName); |
... | ... | @@ -123,7 +117,6 @@ public class GatewaySessionCtx { |
123 | 117 | public void onGatewayDisconnect() { |
124 | 118 | devices.forEach((k, v) -> { |
125 | 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 | 138 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
146 | 139 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
147 | 140 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
148 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
149 | 141 | } |
150 | 142 | } else { |
151 | 143 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
... | ... | @@ -162,7 +154,6 @@ public class GatewaySessionCtx { |
162 | 154 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
163 | 155 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
164 | 156 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); |
165 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
166 | 157 | } else { |
167 | 158 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
168 | 159 | } |
... | ... | @@ -185,7 +176,6 @@ public class GatewaySessionCtx { |
185 | 176 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
186 | 177 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
187 | 178 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
188 | - offlineService.online(deviceSessionCtx.getDevice(), true); | |
189 | 179 | } |
190 | 180 | } else { |
191 | 181 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
... | ... | @@ -220,7 +210,6 @@ public class GatewaySessionCtx { |
220 | 210 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
221 | 211 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
222 | 212 | ack(msg); |
223 | - offlineService.online(deviceSessionCtx.getDevice(), false); | |
224 | 213 | } else { |
225 | 214 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
226 | 215 | } | ... | ... |
... | ... | @@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', []) |
485 | 485 | clientSide: false |
486 | 486 | } |
487 | 487 | }, |
488 | - ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], | |
488 | + ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"], | |
489 | 489 | ruleChainNodeComponent: { |
490 | 490 | type: 'RULE_CHAIN', |
491 | 491 | name: 'rule chain', |
... | ... | @@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', []) |
536 | 536 | nodeClass: "tb-action-type", |
537 | 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 | 546 | RULE_CHAIN: { |
540 | 547 | value: "RULE_CHAIN", |
541 | 548 | name: "rulenode.type-rule-chain", | ... | ... |
... | ... | @@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', []) |
1228 | 1228 | "type-transformation-details": "Change Message payload and Metadata", |
1229 | 1229 | "type-action": "Action", |
1230 | 1230 | "type-action-details": "Perform special action", |
1231 | + "type-external": "External", | |
1232 | + "type-external-details": "Interacts with external system", | |
1231 | 1233 | "type-rule-chain": "Rule Chain", |
1232 | 1234 | "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", |
1233 | 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 | 246 | var contextInfo = { |
247 | 247 | headerClass: node.nodeClass, |
248 | 248 | icon: node.icon, |
249 | + iconUrl: node.iconUrl, | |
249 | 250 | title: node.name, |
250 | 251 | subtitle: node.component.name |
251 | 252 | }; |
... | ... | @@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time |
805 | 806 | var ruleNodeComponent = ruleNodeComponents[i]; |
806 | 807 | componentType = ruleNodeComponent.type; |
807 | 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 | 817 | var node = { |
809 | 818 | id: 'node-lib-' + componentType + '-' + model.nodes.length, |
810 | 819 | component: ruleNodeComponent, |
811 | 820 | name: '', |
812 | 821 | nodeClass: vm.types.ruleNodeType[componentType].nodeClass, |
813 | - icon: vm.types.ruleNodeType[componentType].icon, | |
822 | + icon: icon, | |
823 | + iconUrl: iconUrl, | |
814 | 824 | x: 30, |
815 | 825 | y: 10+50*model.nodes.length, |
816 | 826 | connectors: [] |
... | ... | @@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time |
904 | 914 | var ruleNode = vm.ruleChainMetaData.nodes[i]; |
905 | 915 | var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); |
906 | 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 | 925 | var node = { |
908 | 926 | id: 'rule-chain-node-' + vm.nextNodeID++, |
909 | 927 | ruleNodeId: ruleNode.id, |
... | ... | @@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time |
915 | 933 | component: component, |
916 | 934 | name: ruleNode.name, |
917 | 935 | nodeClass: vm.types.ruleNodeType[component.type].nodeClass, |
918 | - icon: vm.types.ruleNodeType[component.type].icon, | |
936 | + icon: icon, | |
937 | + iconUrl: iconUrl, | |
919 | 938 | connectors: [] |
920 | 939 | }; |
921 | 940 | if (component.configurationDescriptor.nodeDefinition.inEnabled) { | ... | ... |
... | ... | @@ -76,7 +76,7 @@ |
76 | 76 | -moz-user-select: none; |
77 | 77 | -ms-user-select: none; |
78 | 78 | user-select: none; |
79 | - min-width: 180px; | |
79 | + min-width: 150px; | |
80 | 80 | } |
81 | 81 | .fc-canvas { |
82 | 82 | background: #f9f9f9; |
... | ... | @@ -161,6 +161,9 @@ |
161 | 161 | &.tb-action-type { |
162 | 162 | background-color: #f1928f; |
163 | 163 | } |
164 | + &.tb-external-type { | |
165 | + background-color: #fbc766; | |
166 | + } | |
164 | 167 | &.tb-rule-chain-type { |
165 | 168 | background-color: #d6c4f1; |
166 | 169 | } | ... | ... |
... | ... | @@ -75,6 +75,8 @@ |
75 | 75 | <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel"> |
76 | 76 | <md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" |
77 | 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 | 80 | <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> |
79 | 81 | <md-expansion-panel-icon></md-expansion-panel-icon> |
80 | 82 | </md-expansion-panel-collapsed> |
... | ... | @@ -82,6 +84,8 @@ |
82 | 84 | <md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" |
83 | 85 | ng-mouseleave="vm.destroyTooltips()" |
84 | 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 | 89 | <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> |
86 | 90 | <md-expansion-panel-icon></md-expansion-panel-icon> |
87 | 91 | </md-expansion-panel-header> |
... | ... | @@ -114,8 +118,10 @@ |
114 | 118 | </div> |
115 | 119 | <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()"> |
116 | 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 | 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 | 125 | <div flex> |
120 | 126 | <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div> |
121 | 127 | <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div> | ... | ... |
... | ... | @@ -24,8 +24,10 @@ |
24 | 24 | ng-mouseleave="callbacks.mouseLeave($event, node)"> |
25 | 25 | <div class="{{flowchartConstants.nodeOverlayClass}}"></div> |
26 | 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 | 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 | 31 | <div layout="column" flex="85" layout-align="center"> |
30 | 32 | <span class="tb-node-type">{{ node.component.name }}</span> |
31 | 33 | <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span> | ... | ... |
... | ... | @@ -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 | 293 | .md-caption { |
271 | 294 | &.tb-required:after { |
272 | 295 | content: ' *'; | ... | ... |