Commit ee8f7405fc7129411af2036cf49612074de9d18b

Authored by Igor Kulikov
2 parents 3d9156b5 75c0c5b3

Merge with master. Version set to 2.0.2.

Showing 57 changed files with 2329 additions and 1029 deletions

Too many changes to show.

To preserve performance only 57 of 60 files are displayed.

@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>application</artifactId> 26 <artifactId>application</artifactId>
@@ -294,7 +294,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso @@ -294,7 +294,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
294 private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) { 294 private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) {
295 GetAttributesRequest request = (GetAttributesRequest) src.getPayload(); 295 GetAttributesRequest request = (GetAttributesRequest) src.getPayload();
296 ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames()); 296 ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames());
297 - ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getClientAttributeNames()); 297 + ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getSharedAttributeNames());
298 298
299 Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() { 299 Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
300 @Override 300 @Override
@@ -32,6 +32,7 @@ import org.thingsboard.server.actors.rpc.RpcManagerActor; @@ -32,6 +32,7 @@ import org.thingsboard.server.actors.rpc.RpcManagerActor;
32 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; 32 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
33 import org.thingsboard.server.actors.session.SessionManagerActor; 33 import org.thingsboard.server.actors.session.SessionManagerActor;
34 import org.thingsboard.server.actors.stats.StatsActor; 34 import org.thingsboard.server.actors.stats.StatsActor;
  35 +import org.thingsboard.server.common.data.Device;
35 import org.thingsboard.server.common.data.id.DeviceId; 36 import org.thingsboard.server.common.data.id.DeviceId;
36 import org.thingsboard.server.common.data.id.EntityId; 37 import org.thingsboard.server.common.data.id.EntityId;
37 import org.thingsboard.server.common.data.id.TenantId; 38 import org.thingsboard.server.common.data.id.TenantId;
@@ -48,6 +49,7 @@ import org.thingsboard.server.gen.cluster.ClusterAPIProtos; @@ -48,6 +49,7 @@ import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
48 import org.thingsboard.server.service.cluster.discovery.DiscoveryService; 49 import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
49 import org.thingsboard.server.service.cluster.discovery.ServerInstance; 50 import org.thingsboard.server.service.cluster.discovery.ServerInstance;
50 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; 51 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  52 +import org.thingsboard.server.service.state.DeviceStateService;
51 import scala.concurrent.Await; 53 import scala.concurrent.Await;
52 import scala.concurrent.Future; 54 import scala.concurrent.Future;
53 import scala.concurrent.duration.Duration; 55 import scala.concurrent.duration.Duration;
@@ -81,6 +83,9 @@ public class DefaultActorService implements ActorService { @@ -81,6 +83,9 @@ public class DefaultActorService implements ActorService {
81 @Autowired 83 @Autowired
82 private DiscoveryService discoveryService; 84 private DiscoveryService discoveryService;
83 85
  86 + @Autowired
  87 + private DeviceStateService deviceStateService;
  88 +
84 private ActorSystem system; 89 private ActorSystem system;
85 90
86 private ActorRef appActor; 91 private ActorRef appActor;
@@ -199,7 +204,7 @@ public class DefaultActorService implements ActorService { @@ -199,7 +204,7 @@ public class DefaultActorService implements ActorService {
199 public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) { 204 public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) {
200 ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort()); 205 ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort());
201 log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress); 206 log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
202 - if(log.isDebugEnabled()){ 207 + if (log.isDebugEnabled()) {
203 log.info("MSG: ", msg); 208 log.info("MSG: ", msg);
204 } 209 }
205 switch (msg.getMessageType()) { 210 switch (msg.getMessageType()) {
@@ -236,6 +241,9 @@ public class DefaultActorService implements ActorService { @@ -236,6 +241,9 @@ public class DefaultActorService implements ActorService {
236 case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE: 241 case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
237 actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray()); 242 actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray());
238 break; 243 break;
  244 + case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE:
  245 + actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray());
  246 + break;
239 } 247 }
240 } 248 }
241 249
@@ -254,4 +262,8 @@ public class DefaultActorService implements ActorService { @@ -254,4 +262,8 @@ public class DefaultActorService implements ActorService {
254 rpcManagerActor.tell(msg, ActorRef.noSender()); 262 rpcManagerActor.tell(msg, ActorRef.noSender());
255 } 263 }
256 264
  265 + @Override
  266 + public void onDeviceAdded(Device device) {
  267 + deviceStateService.onDeviceAdded(device);
  268 + }
257 } 269 }
@@ -33,9 +33,7 @@ public class TenantRuleChainManager extends RuleChainManager { @@ -33,9 +33,7 @@ public class TenantRuleChainManager extends RuleChainManager {
33 33
34 @Override 34 @Override
35 public void init(ActorContext context) { 35 public void init(ActorContext context) {
36 - if (systemContext.isTenantComponentsInitEnabled()) {  
37 - super.init(context);  
38 - } 36 + super.init(context);
39 } 37 }
40 38
41 @Override 39 @Override
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Lazy; @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Lazy;
36 import org.springframework.stereotype.Service; 36 import org.springframework.stereotype.Service;
37 import org.springframework.util.Assert; 37 import org.springframework.util.Assert;
38 import org.thingsboard.server.common.msg.cluster.ServerAddress; 38 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  39 +import org.thingsboard.server.service.state.DeviceStateService;
39 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; 40 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
40 import org.thingsboard.server.utils.MiscUtils; 41 import org.thingsboard.server.utils.MiscUtils;
41 42
@@ -74,6 +75,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @@ -74,6 +75,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
74 @Lazy 75 @Lazy
75 private TelemetrySubscriptionService tsSubService; 76 private TelemetrySubscriptionService tsSubService;
76 77
  78 + @Autowired
  79 + @Lazy
  80 + private DeviceStateService deviceStateService;
  81 +
77 private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>(); 82 private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>();
78 83
79 private CuratorFramework client; 84 private CuratorFramework client;
@@ -203,6 +208,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @@ -203,6 +208,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
203 switch (pathChildrenCacheEvent.getType()) { 208 switch (pathChildrenCacheEvent.getType()) {
204 case CHILD_ADDED: 209 case CHILD_ADDED:
205 tsSubService.onClusterUpdate(); 210 tsSubService.onClusterUpdate();
  211 + deviceStateService.onClusterUpdate();
206 listeners.forEach(listener -> listener.onServerAdded(instance)); 212 listeners.forEach(listener -> listener.onServerAdded(instance));
207 break; 213 break;
208 case CHILD_UPDATED: 214 case CHILD_UPDATED:
@@ -210,6 +216,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @@ -210,6 +216,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
210 break; 216 break;
211 case CHILD_REMOVED: 217 case CHILD_REMOVED:
212 tsSubService.onClusterUpdate(); 218 tsSubService.onClusterUpdate();
  219 + deviceStateService.onClusterUpdate();
213 listeners.forEach(listener -> listener.onServerRemoved(instance)); 220 listeners.forEach(listener -> listener.onServerRemoved(instance));
214 break; 221 break;
215 default: 222 default:
@@ -211,7 +211,13 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -211,7 +211,13 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
211 211
212 private void loadCql(Path cql) throws Exception { 212 private void loadCql(Path cql) throws Exception {
213 List<String> statements = new CQLStatementsParser(cql).getStatements(); 213 List<String> statements = new CQLStatementsParser(cql).getStatements();
214 - statements.forEach(statement -> installCluster.getSession().execute(statement)); 214 + statements.forEach(statement -> {
  215 + installCluster.getSession().execute(statement);
  216 + try {
  217 + Thread.sleep(2500);
  218 + } catch (InterruptedException e) {}
  219 + });
  220 + Thread.sleep(5000);
215 } 221 }
216 222
217 } 223 }
@@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j; @@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.context.annotation.Profile; 20 import org.springframework.context.annotation.Profile;
21 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.data.SearchTextBased;
22 import org.thingsboard.server.common.data.Tenant; 23 import org.thingsboard.server.common.data.Tenant;
23 -import org.thingsboard.server.common.data.id.IdBased; 24 +import org.thingsboard.server.common.data.id.UUIDBased;
  25 +import org.thingsboard.server.common.data.page.TextPageData;
24 import org.thingsboard.server.common.data.page.TextPageLink; 26 import org.thingsboard.server.common.data.page.TextPageLink;
25 import org.thingsboard.server.common.data.rule.RuleChain; 27 import org.thingsboard.server.common.data.rule.RuleChain;
26 import org.thingsboard.server.dao.rule.RuleChainService; 28 import org.thingsboard.server.dao.rule.RuleChainService;
27 import org.thingsboard.server.dao.tenant.TenantService; 29 import org.thingsboard.server.dao.tenant.TenantService;
28 30
29 -import java.util.List;  
30 -import java.util.UUID;  
31 -  
32 @Service 31 @Service
33 @Profile("install") 32 @Profile("install")
34 @Slf4j 33 @Slf4j
@@ -59,8 +58,8 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -59,8 +58,8 @@ public class DefaultDataUpdateService implements DataUpdateService {
59 new PaginatedUpdater<String, Tenant>() { 58 new PaginatedUpdater<String, Tenant>() {
60 59
61 @Override 60 @Override
62 - protected List<Tenant> findEntities(String region, TextPageLink pageLink) {  
63 - return tenantService.findTenants(pageLink).getData(); 61 + protected TextPageData<Tenant> findEntities(String region, TextPageLink pageLink) {
  62 + return tenantService.findTenants(pageLink);
64 } 63 }
65 64
66 @Override 65 @Override
@@ -76,7 +75,7 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -76,7 +75,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
76 } 75 }
77 }; 76 };
78 77
79 - public abstract class PaginatedUpdater<I, D extends IdBased<?>> { 78 + public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> {
80 79
81 private static final int DEFAULT_LIMIT = 100; 80 private static final int DEFAULT_LIMIT = 100;
82 81
@@ -84,20 +83,18 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -84,20 +83,18 @@ public class DefaultDataUpdateService implements DataUpdateService {
84 TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); 83 TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
85 boolean hasNext = true; 84 boolean hasNext = true;
86 while (hasNext) { 85 while (hasNext) {
87 - List<D> entities = findEntities(id, pageLink);  
88 - for (D entity : entities) { 86 + TextPageData<D> entities = findEntities(id, pageLink);
  87 + for (D entity : entities.getData()) {
89 updateEntity(entity); 88 updateEntity(entity);
90 } 89 }
91 - hasNext = entities.size() == pageLink.getLimit(); 90 + hasNext = entities.hasNext();
92 if (hasNext) { 91 if (hasNext) {
93 - int index = entities.size() - 1;  
94 - UUID idOffset = entities.get(index).getUuidId();  
95 - pageLink.setIdOffset(idOffset); 92 + pageLink = entities.getNextPageLink();
96 } 93 }
97 } 94 }
98 } 95 }
99 96
100 - protected abstract List<D> findEntities(I id, TextPageLink pageLink); 97 + protected abstract TextPageData<D> findEntities(I id, TextPageLink pageLink);
101 98
102 protected abstract void updateEntity(D entity); 99 protected abstract void updateEntity(D entity);
103 100
@@ -70,8 +70,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -70,8 +70,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
70 log.info("Updating schema ..."); 70 log.info("Updating schema ...");
71 Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL); 71 Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
72 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 72 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
73 - String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));  
74 - conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script 73 + loadSql(schemaUpdateFile, conn);
75 } 74 }
76 log.info("Schema updated."); 75 log.info("Schema updated.");
77 break; 76 break;
@@ -87,8 +86,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -87,8 +86,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
87 86
88 log.info("Updating schema ..."); 87 log.info("Updating schema ...");
89 schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); 88 schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
90 - String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));  
91 - conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script 89 + loadSql(schemaUpdateFile, conn);
92 log.info("Schema updated."); 90 log.info("Schema updated.");
93 91
94 log.info("Restoring dashboards ..."); 92 log.info("Restoring dashboards ...");
@@ -105,8 +103,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -105,8 +103,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
105 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 103 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
106 log.info("Updating schema ..."); 104 log.info("Updating schema ...");
107 schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.0.0", SCHEMA_UPDATE_SQL); 105 schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.0.0", SCHEMA_UPDATE_SQL);
108 - String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));  
109 - conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script 106 + loadSql(schemaUpdateFile, conn);
110 log.info("Schema updated."); 107 log.info("Schema updated.");
111 } 108 }
112 break; 109 break;
@@ -114,4 +111,10 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -114,4 +111,10 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
114 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 111 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
115 } 112 }
116 } 113 }
  114 +
  115 + private void loadSql(Path sqlFile, Connection conn) throws Exception {
  116 + String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8"));
  117 + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  118 + Thread.sleep(5000);
  119 + }
117 } 120 }
@@ -23,11 +23,13 @@ import com.google.common.util.concurrent.Futures; @@ -23,11 +23,13 @@ import com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.ListenableFuture; 23 import com.google.common.util.concurrent.ListenableFuture;
24 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 24 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
25 import com.google.common.util.concurrent.MoreExecutors; 25 import com.google.common.util.concurrent.MoreExecutors;
  26 +import com.google.protobuf.InvalidProtocolBufferException;
26 import lombok.Getter; 27 import lombok.Getter;
27 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
28 import org.springframework.beans.factory.annotation.Autowired; 29 import org.springframework.beans.factory.annotation.Autowired;
29 import org.springframework.beans.factory.annotation.Value; 30 import org.springframework.beans.factory.annotation.Value;
30 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
  32 +import org.thingsboard.rule.engine.api.RpcError;
31 import org.thingsboard.server.actors.service.ActorService; 33 import org.thingsboard.server.actors.service.ActorService;
32 import org.thingsboard.server.common.data.DataConstants; 34 import org.thingsboard.server.common.data.DataConstants;
33 import org.thingsboard.server.common.data.Device; 35 import org.thingsboard.server.common.data.Device;
@@ -36,13 +38,19 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -36,13 +38,19 @@ import org.thingsboard.server.common.data.id.DeviceId;
36 import org.thingsboard.server.common.data.id.TenantId; 38 import org.thingsboard.server.common.data.id.TenantId;
37 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 39 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
38 import org.thingsboard.server.common.data.page.TextPageLink; 40 import org.thingsboard.server.common.data.page.TextPageLink;
  41 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
39 import org.thingsboard.server.common.msg.TbMsg; 42 import org.thingsboard.server.common.msg.TbMsg;
40 import org.thingsboard.server.common.msg.TbMsgDataType; 43 import org.thingsboard.server.common.msg.TbMsgDataType;
41 import org.thingsboard.server.common.msg.TbMsgMetaData; 44 import org.thingsboard.server.common.msg.TbMsgMetaData;
  45 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
42 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; 46 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
43 import org.thingsboard.server.dao.attributes.AttributesService; 47 import org.thingsboard.server.dao.attributes.AttributesService;
44 import org.thingsboard.server.dao.device.DeviceService; 48 import org.thingsboard.server.dao.device.DeviceService;
45 import org.thingsboard.server.dao.tenant.TenantService; 49 import org.thingsboard.server.dao.tenant.TenantService;
  50 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  51 +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  52 +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  53 +import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
46 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; 54 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
47 55
48 import javax.annotation.Nullable; 56 import javax.annotation.Nullable;
@@ -54,6 +62,7 @@ import java.util.HashSet; @@ -54,6 +62,7 @@ import java.util.HashSet;
54 import java.util.List; 62 import java.util.List;
55 import java.util.Optional; 63 import java.util.Optional;
56 import java.util.Set; 64 import java.util.Set;
  65 +import java.util.UUID;
57 import java.util.concurrent.ConcurrentHashMap; 66 import java.util.concurrent.ConcurrentHashMap;
58 import java.util.concurrent.ConcurrentMap; 67 import java.util.concurrent.ConcurrentMap;
59 import java.util.concurrent.ExecutionException; 68 import java.util.concurrent.ExecutionException;
@@ -98,6 +107,12 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -98,6 +107,12 @@ public class DefaultDeviceStateService implements DeviceStateService {
98 @Autowired 107 @Autowired
99 private TelemetrySubscriptionService tsSubService; 108 private TelemetrySubscriptionService tsSubService;
100 109
  110 + @Autowired
  111 + private ClusterRoutingService routingService;
  112 +
  113 + @Autowired
  114 + private ClusterRpcService clusterRpcService;
  115 +
101 @Value("${state.defaultInactivityTimeoutInSec}") 116 @Value("${state.defaultInactivityTimeoutInSec}")
102 @Getter 117 @Getter
103 private long defaultInactivityTimeoutInSec; 118 private long defaultInactivityTimeoutInSec;
@@ -172,12 +187,57 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -172,12 +187,57 @@ public class DefaultDeviceStateService implements DeviceStateService {
172 } 187 }
173 188
174 @Override 189 @Override
175 - public Optional<DeviceState> getDeviceState(DeviceId deviceId) {  
176 - DeviceStateData state = deviceStates.get(deviceId);  
177 - if (state != null) {  
178 - return Optional.of(state.getState()); 190 + public void onClusterUpdate() {
  191 + queueExecutor.submit(this::onClusterUpdateSync);
  192 + }
  193 +
  194 + @Override
  195 + public void onRemoteMsg(ServerAddress serverAddress, byte[] data) {
  196 + ClusterAPIProtos.DeviceStateServiceMsgProto proto;
  197 + try {
  198 + proto = ClusterAPIProtos.DeviceStateServiceMsgProto.parseFrom(data);
  199 + } catch (InvalidProtocolBufferException e) {
  200 + throw new RuntimeException(e);
  201 + }
  202 + TenantId tenantId = new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
  203 + DeviceId deviceId = new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB()));
  204 + if (proto.getDeleted()) {
  205 + queueExecutor.submit(() -> onDeviceDeleted(tenantId, deviceId));
179 } else { 206 } else {
180 - return Optional.empty(); 207 + Device device = deviceService.findDeviceById(deviceId);
  208 + if (device != null) {
  209 + if (proto.getAdded()) {
  210 + onDeviceAdded(device);
  211 + } else if (proto.getUpdated()) {
  212 + onDeviceUpdated(device);
  213 + }
  214 + }
  215 + }
  216 + }
  217 +
  218 + private void onClusterUpdateSync() {
  219 + List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData();
  220 + for (Tenant tenant : tenants) {
  221 + List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
  222 + List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
  223 + for (Device device : devices) {
  224 + if (!routingService.resolveById(device.getId()).isPresent()) {
  225 + if (!deviceStates.containsKey(device.getId())) {
  226 + fetchFutures.add(fetchDeviceState(device));
  227 + }
  228 + } else {
  229 + Set<DeviceId> tenantDeviceSet = tenantDevices.get(tenant.getId());
  230 + if (tenantDeviceSet != null) {
  231 + tenantDeviceSet.remove(device.getId());
  232 + }
  233 + deviceStates.remove(device.getId());
  234 + }
  235 + }
  236 + try {
  237 + Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
  238 + } catch (InterruptedException | ExecutionException e) {
  239 + log.warn("Failed to init device state service from DB", e);
  240 + }
181 } 241 }
182 } 242 }
183 243
@@ -187,7 +247,9 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -187,7 +247,9 @@ public class DefaultDeviceStateService implements DeviceStateService {
187 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>(); 247 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
188 List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); 248 List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
189 for (Device device : devices) { 249 for (Device device : devices) {
190 - fetchFutures.add(fetchDeviceState(device)); 250 + if (!routingService.resolveById(device.getId()).isPresent()) {
  251 + fetchFutures.add(fetchDeviceState(device));
  252 + }
191 } 253 }
192 try { 254 try {
193 Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); 255 Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
@@ -209,7 +271,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -209,7 +271,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
209 DeviceStateData stateData = deviceStates.get(deviceId); 271 DeviceStateData stateData = deviceStates.get(deviceId);
210 DeviceState state = stateData.getState(); 272 DeviceState state = stateData.getState();
211 state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); 273 state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
212 - if (!state.isActive() && state.getLastInactivityAlarmTime() < state.getLastActivityTime()) { 274 + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) {
213 state.setLastInactivityAlarmTime(ts); 275 state.setLastInactivityAlarmTime(ts);
214 pushRuleEngineMessage(stateData, INACTIVITY_EVENT); 276 pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
215 saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts); 277 saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts);
@@ -219,7 +281,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -219,7 +281,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
219 } 281 }
220 282
221 private void onDeviceConnectSync(DeviceId deviceId) { 283 private void onDeviceConnectSync(DeviceId deviceId) {
222 - DeviceStateData stateData = deviceStates.get(deviceId); 284 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
223 if (stateData != null) { 285 if (stateData != null) {
224 long ts = System.currentTimeMillis(); 286 long ts = System.currentTimeMillis();
225 stateData.getState().setLastConnectTime(ts); 287 stateData.getState().setLastConnectTime(ts);
@@ -229,7 +291,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -229,7 +291,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
229 } 291 }
230 292
231 private void onDeviceDisconnectSync(DeviceId deviceId) { 293 private void onDeviceDisconnectSync(DeviceId deviceId) {
232 - DeviceStateData stateData = deviceStates.get(deviceId); 294 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
233 if (stateData != null) { 295 if (stateData != null) {
234 long ts = System.currentTimeMillis(); 296 long ts = System.currentTimeMillis();
235 stateData.getState().setLastDisconnectTime(ts); 297 stateData.getState().setLastDisconnectTime(ts);
@@ -239,7 +301,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -239,7 +301,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
239 } 301 }
240 302
241 private void onDeviceActivitySync(DeviceId deviceId) { 303 private void onDeviceActivitySync(DeviceId deviceId) {
242 - DeviceStateData stateData = deviceStates.get(deviceId); 304 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
243 if (stateData != null) { 305 if (stateData != null) {
244 DeviceState state = stateData.getState(); 306 DeviceState state = stateData.getState();
245 long ts = System.currentTimeMillis(); 307 long ts = System.currentTimeMillis();
@@ -251,6 +313,23 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -251,6 +313,23 @@ public class DefaultDeviceStateService implements DeviceStateService {
251 } 313 }
252 } 314 }
253 315
  316 + private DeviceStateData getOrFetchDeviceStateData(DeviceId deviceId) {
  317 + DeviceStateData deviceStateData = deviceStates.get(deviceId);
  318 + if (deviceStateData == null) {
  319 + if (!routingService.resolveById(deviceId).isPresent()) {
  320 + Device device = deviceService.findDeviceById(deviceId);
  321 + if (device != null) {
  322 + try {
  323 + deviceStateData = fetchDeviceState(device).get();
  324 + } catch (InterruptedException | ExecutionException e) {
  325 + log.debug("[{}] Failed to fetch device state!", deviceId, e);
  326 + }
  327 + }
  328 + }
  329 + }
  330 + return deviceStateData;
  331 + }
  332 +
254 private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) { 333 private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
255 if (inactivityTimeout == 0L) { 334 if (inactivityTimeout == 0L) {
256 return; 335 return;
@@ -269,37 +348,65 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -269,37 +348,65 @@ public class DefaultDeviceStateService implements DeviceStateService {
269 } 348 }
270 349
271 private void onDeviceAddedSync(Device device) { 350 private void onDeviceAddedSync(Device device) {
272 - Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {  
273 - @Override  
274 - public void onSuccess(@Nullable DeviceStateData state) {  
275 - addDeviceUsingState(state);  
276 - } 351 + Optional<ServerAddress> address = routingService.resolveById(device.getId());
  352 + if (!address.isPresent()) {
  353 + Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
  354 + @Override
  355 + public void onSuccess(@Nullable DeviceStateData state) {
  356 + addDeviceUsingState(state);
  357 + }
  358 +
  359 + @Override
  360 + public void onFailure(Throwable t) {
  361 + log.warn("Failed to register device to the state service", t);
  362 + }
  363 + });
  364 + } else {
  365 + sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), true, false, false);
  366 + }
  367 + }
277 368
278 - @Override  
279 - public void onFailure(Throwable t) {  
280 - log.warn("Failed to register device to the state service", t);  
281 - }  
282 - }); 369 + private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, ServerAddress address, boolean added, boolean updated, boolean deleted) {
  370 + log.trace("[{}][{}] Device is monitored on other server: {}", tenantId, deviceId, address);
  371 + ClusterAPIProtos.DeviceStateServiceMsgProto.Builder builder = ClusterAPIProtos.DeviceStateServiceMsgProto.newBuilder();
  372 + builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
  373 + builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
  374 + builder.setDeviceIdMSB(deviceId.getId().getMostSignificantBits());
  375 + builder.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits());
  376 + builder.setAdded(added);
  377 + builder.setUpdated(updated);
  378 + builder.setDeleted(deleted);
  379 + clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_DEVICE_STATE_SERVICE_MESSAGE, builder.build().toByteArray());
283 } 380 }
284 381
285 private void onDeviceUpdatedSync(Device device) { 382 private void onDeviceUpdatedSync(Device device) {
286 - DeviceStateData stateData = deviceStates.get(device.getId());  
287 - if (stateData != null) {  
288 - TbMsgMetaData md = new TbMsgMetaData();  
289 - md.putValue("deviceName", device.getName());  
290 - md.putValue("deviceType", device.getType());  
291 - stateData.setMetaData(md); 383 + Optional<ServerAddress> address = routingService.resolveById(device.getId());
  384 + if (!address.isPresent()) {
  385 + DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
  386 + if (stateData != null) {
  387 + TbMsgMetaData md = new TbMsgMetaData();
  388 + md.putValue("deviceName", device.getName());
  389 + md.putValue("deviceType", device.getType());
  390 + stateData.setMetaData(md);
  391 + }
  392 + } else {
  393 + sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), false, true, false);
292 } 394 }
293 } 395 }
294 396
295 private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { 397 private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
296 - deviceStates.remove(deviceId);  
297 - Set<DeviceId> deviceIds = tenantDevices.get(tenantId);  
298 - if (deviceIds != null) {  
299 - deviceIds.remove(deviceId);  
300 - if (deviceIds.isEmpty()) {  
301 - tenantDevices.remove(tenantId); 398 + Optional<ServerAddress> address = routingService.resolveById(deviceId);
  399 + if (!address.isPresent()) {
  400 + deviceStates.remove(deviceId);
  401 + Set<DeviceId> deviceIds = tenantDevices.get(tenantId);
  402 + if (deviceIds != null) {
  403 + deviceIds.remove(deviceId);
  404 + if (deviceIds.isEmpty()) {
  405 + tenantDevices.remove(tenantId);
  406 + }
302 } 407 }
  408 + } else {
  409 + sendDeviceEvent(tenantId, deviceId, address.get(), false, false, true);
303 } 410 }
304 } 411 }
305 412
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.state; @@ -17,6 +17,7 @@ package org.thingsboard.server.service.state;
17 17
18 import org.thingsboard.server.common.data.Device; 18 import org.thingsboard.server.common.data.Device;
19 import org.thingsboard.server.common.data.id.DeviceId; 19 import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
20 21
21 import java.util.Optional; 22 import java.util.Optional;
22 23
@@ -39,6 +40,7 @@ public interface DeviceStateService { @@ -39,6 +40,7 @@ public interface DeviceStateService {
39 40
40 void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout); 41 void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout);
41 42
42 - Optional<DeviceState> getDeviceState(DeviceId deviceId); 43 + void onClusterUpdate();
43 44
  45 + void onRemoteMsg(ServerAddress serverAddress, byte[] bytes);
44 } 46 }
@@ -362,7 +362,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @@ -362,7 +362,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
362 362
363 DonAsynchron.withCallback(tsService.findAll(entityId, queries), 363 DonAsynchron.withCallback(tsService.findAll(entityId, queries),
364 missedUpdates -> { 364 missedUpdates -> {
365 - if (!missedUpdates.isEmpty()) { 365 + if (missedUpdates != null && !missedUpdates.isEmpty()) {
366 tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); 366 tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
367 } 367 }
368 }, 368 },
@@ -57,6 +57,8 @@ enum MessageType { @@ -57,6 +57,8 @@ enum MessageType {
57 CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE = 10; 57 CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE = 10;
58 CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE = 11; 58 CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE = 11;
59 CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12; 59 CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12;
  60 +
  61 + CLUSTER_DEVICE_STATE_SERVICE_MESSAGE = 13;
60 } 62 }
61 63
62 // Messages related to CLUSTER_TELEMETRY_MESSAGE 64 // Messages related to CLUSTER_TELEMETRY_MESSAGE
@@ -128,3 +130,13 @@ message FromDeviceRPCResponseProto { @@ -128,3 +130,13 @@ message FromDeviceRPCResponseProto {
128 string response = 3; 130 string response = 3;
129 int32 error = 4; 131 int32 error = 4;
130 } 132 }
  133 +
  134 +message DeviceStateServiceMsgProto {
  135 + int64 tenantIdMSB = 1;
  136 + int64 tenantIdLSB = 2;
  137 + int64 deviceIdMSB = 3;
  138 + int64 deviceIdLSB = 4;
  139 + bool added = 5;
  140 + bool updated = 6;
  141 + bool deleted = 7;
  142 +}
@@ -64,6 +64,7 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { @@ -64,6 +64,7 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
64 mockMvc.perform( 64 mockMvc.perform(
65 asyncDispatch(doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", attrMap, new String[]{}).andReturn())) 65 asyncDispatch(doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", attrMap, new String[]{}).andReturn()))
66 .andExpect(status().isOk()); 66 .andExpect(status().isOk());
  67 + Thread.sleep(2000);
67 doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk()); 68 doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
68 } 69 }
69 70
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -98,6 +98,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -98,6 +98,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
98 public boolean updateAssignedCustomer(Customer customer) { 98 public boolean updateAssignedCustomer(Customer customer) {
99 ShortCustomerInfo customerInfo = customer.toShortCustomerInfo(); 99 ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
100 if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) { 100 if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  101 + this.assignedCustomers.remove(customerInfo);
101 this.assignedCustomers.add(customerInfo); 102 this.assignedCustomers.add(customerInfo);
102 return true; 103 return true;
103 } else { 104 } else {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -15,11 +15,13 @@ @@ -15,11 +15,13 @@
15 */ 15 */
16 package org.thingsboard.server.common.transport; 16 package org.thingsboard.server.common.transport;
17 17
18 -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; 18 +import org.thingsboard.server.common.data.Device;
19 import org.thingsboard.server.common.msg.aware.SessionAwareMsg; 19 import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
20 20
21 public interface SessionMsgProcessor { 21 public interface SessionMsgProcessor {
22 22
23 void process(SessionAwareMsg msg); 23 void process(SessionAwareMsg msg);
24 24
  25 + void onDeviceAdded(Device device);
  26 +
25 } 27 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>dao</artifactId> 26 <artifactId>dao</artifactId>
@@ -67,9 +67,9 @@ public abstract class AbstractCassandraCluster { @@ -67,9 +67,9 @@ public abstract class AbstractCassandraCluster {
67 private long initTimeout; 67 private long initTimeout;
68 @Value("${cassandra.init_retry_interval_ms}") 68 @Value("${cassandra.init_retry_interval_ms}")
69 private long initRetryInterval; 69 private long initRetryInterval;
70 - @Value("${cassandra.max_requests_per_connection_local:128}") 70 + @Value("${cassandra.max_requests_per_connection_local:32768}")
71 private int max_requests_local; 71 private int max_requests_local;
72 - @Value("${cassandra.max_requests_per_connection_remote:128}") 72 + @Value("${cassandra.max_requests_per_connection_remote:32768}")
73 private int max_requests_remote; 73 private int max_requests_remote;
74 74
75 @Autowired 75 @Autowired
1 -VERSION=2.0.1 1 +VERSION=2.0.2
2 PROJECT=thingsboard 2 PROJECT=thingsboard
3 APP=cassandra-setup 3 APP=cassandra-setup
4 4
1 -VERSION=2.0.1 1 +VERSION=2.0.2
2 PROJECT=thingsboard 2 PROJECT=thingsboard
3 APP=cassandra 3 APP=cassandra
4 4
@@ -18,7 +18,7 @@ version: '2' @@ -18,7 +18,7 @@ version: '2'
18 18
19 services: 19 services:
20 tb: 20 tb:
21 - image: "thingsboard/application:2.0.1" 21 + image: "thingsboard/application:2.0.2"
22 ports: 22 ports:
23 - "8080:8080" 23 - "8080:8080"
24 - "1883:1883" 24 - "1883:1883"
@@ -22,7 +22,7 @@ spec: @@ -22,7 +22,7 @@ spec:
22 containers: 22 containers:
23 - name: cassandra-setup 23 - name: cassandra-setup
24 imagePullPolicy: Always 24 imagePullPolicy: Always
25 - image: thingsboard/cassandra-setup:2.0.1 25 + image: thingsboard/cassandra-setup:2.0.2
26 env: 26 env:
27 - name: ADD_DEMO_DATA 27 - name: ADD_DEMO_DATA
28 value: "true" 28 value: "true"
@@ -54,7 +54,7 @@ spec: @@ -54,7 +54,7 @@ spec:
54 topologyKey: "kubernetes.io/hostname" 54 topologyKey: "kubernetes.io/hostname"
55 containers: 55 containers:
56 - name: cassandra 56 - name: cassandra
57 - image: thingsboard/cassandra:2.0.1 57 + image: thingsboard/cassandra:2.0.2
58 imagePullPolicy: Always 58 imagePullPolicy: Always
59 ports: 59 ports:
60 - containerPort: 7000 60 - containerPort: 7000
@@ -84,7 +84,7 @@ spec: @@ -84,7 +84,7 @@ spec:
84 containers: 84 containers:
85 - name: tb 85 - name: tb
86 imagePullPolicy: Always 86 imagePullPolicy: Always
87 - image: thingsboard/application:2.0.1 87 + image: thingsboard/application:2.0.2
88 ports: 88 ports:
89 - containerPort: 8080 89 - containerPort: 8080
90 name: ui 90 name: ui
@@ -87,7 +87,7 @@ spec: @@ -87,7 +87,7 @@ spec:
87 containers: 87 containers:
88 - name: zk 88 - name: zk
89 imagePullPolicy: Always 89 imagePullPolicy: Always
90 - image: thingsboard/zk:2.0.1 90 + image: thingsboard/zk:2.0.2
91 ports: 91 ports:
92 - containerPort: 2181 92 - containerPort: 2181
93 name: client 93 name: client
1 -VERSION=2.0.1 1 +VERSION=2.0.2
2 PROJECT=thingsboard 2 PROJECT=thingsboard
3 APP=application 3 APP=application
4 4
1 -VERSION=2.0.1 1 +VERSION=2.0.2
2 PROJECT=thingsboard 2 PROJECT=thingsboard
3 APP=zk 3 APP=zk
4 4
@@ -19,12 +19,12 @@ @@ -19,12 +19,12 @@
19 <modelVersion>4.0.0</modelVersion> 19 <modelVersion>4.0.0</modelVersion>
20 <parent> 20 <parent>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 - <version>2.0.1</version> 22 + <version>2.0.2</version>
23 <artifactId>thingsboard</artifactId> 23 <artifactId>thingsboard</artifactId>
24 </parent> 24 </parent>
25 <groupId>org.thingsboard</groupId> 25 <groupId>org.thingsboard</groupId>
26 <artifactId>netty-mqtt</artifactId> 26 <artifactId>netty-mqtt</artifactId>
27 - <version>2.0.1</version> 27 + <version>2.0.2</version>
28 <packaging>jar</packaging> 28 <packaging>jar</packaging>
29 29
30 <name>Netty MQTT Client</name> 30 <name>Netty MQTT Client</name>
@@ -98,33 +98,25 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> @@ -98,33 +98,25 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
98 } 98 }
99 99
100 private void invokeHandlersForIncomingPublish(MqttPublishMessage message) { 100 private void invokeHandlersForIncomingPublish(MqttPublishMessage message) {
101 - for (MqttSubscribtion subscribtion : ImmutableSet.copyOf(this.client.getSubscriptions().values())) {  
102 - if (subscribtion.matches(message.variableHeader().topicName())) {  
103 - if (subscribtion.isOnce() && subscribtion.isCalled()) { 101 + boolean handlerInvoked = false;
  102 + for (MqttSubscription subscription : ImmutableSet.copyOf(this.client.getSubscriptions().values())) {
  103 + if (subscription.matches(message.variableHeader().topicName())) {
  104 + if (subscription.isOnce() && subscription.isCalled()) {
104 continue; 105 continue;
105 } 106 }
106 message.payload().markReaderIndex(); 107 message.payload().markReaderIndex();
107 - subscribtion.setCalled(true);  
108 - subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload());  
109 - if (subscribtion.isOnce()) {  
110 - this.client.off(subscribtion.getTopic(), subscribtion.getHandler()); 108 + subscription.setCalled(true);
  109 + subscription.getHandler().onMessage(message.variableHeader().topicName(), message.payload());
  110 + if (subscription.isOnce()) {
  111 + this.client.off(subscription.getTopic(), subscription.getHandler());
111 } 112 }
112 message.payload().resetReaderIndex(); 113 message.payload().resetReaderIndex();
  114 + handlerInvoked = true;
113 } 115 }
114 } 116 }
115 - /*Set<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.client.getSubscriptions().get(message.variableHeader().topicName()));  
116 - for (MqttSubscribtion subscribtion : subscribtions) {  
117 - if(subscribtion.isOnce() && subscribtion.isCalled()){  
118 - continue;  
119 - }  
120 - message.payload().markReaderIndex();  
121 - subscribtion.setCalled(true);  
122 - subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload());  
123 - if(subscribtion.isOnce()){  
124 - this.client.off(subscribtion.getTopic(), subscribtion.getHandler());  
125 - }  
126 - message.payload().resetReaderIndex();  
127 - }*/ 117 + if (!handlerInvoked && client.getDefaultHandler() != null) {
  118 + client.getDefaultHandler().onMessage(message.variableHeader().topicName(), message.payload());
  119 + }
128 message.payload().release(); 120 message.payload().release();
129 } 121 }
130 122
@@ -133,7 +125,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> @@ -133,7 +125,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
133 case CONNECTION_ACCEPTED: 125 case CONNECTION_ACCEPTED:
134 this.connectFuture.setSuccess(new MqttConnectResult(true, MqttConnectReturnCode.CONNECTION_ACCEPTED, channel.closeFuture())); 126 this.connectFuture.setSuccess(new MqttConnectResult(true, MqttConnectReturnCode.CONNECTION_ACCEPTED, channel.closeFuture()));
135 127
136 - this.client.getPendingSubscribtions().entrySet().stream().filter((e) -> !e.getValue().isSent()).forEach((e) -> { 128 + this.client.getPendingSubscriptions().entrySet().stream().filter((e) -> !e.getValue().isSent()).forEach((e) -> {
137 channel.write(e.getValue().getSubscribeMessage()); 129 channel.write(e.getValue().getSubscribeMessage());
138 e.getValue().setSent(true); 130 e.getValue().setSent(true);
139 }); 131 });
@@ -148,6 +140,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> @@ -148,6 +140,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
148 } 140 }
149 }); 141 });
150 channel.flush(); 142 channel.flush();
  143 + if (this.client.isReconnect()) {
  144 + this.client.onSuccessfulReconnect();
  145 + }
151 break; 146 break;
152 147
153 case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD: 148 case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD:
@@ -163,19 +158,19 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> @@ -163,19 +158,19 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
163 } 158 }
164 159
165 private void handleSubAck(MqttSubAckMessage message) { 160 private void handleSubAck(MqttSubAckMessage message) {
166 - MqttPendingSubscribtion pendingSubscription = this.client.getPendingSubscribtions().remove(message.variableHeader().messageId()); 161 + MqttPendingSubscription pendingSubscription = this.client.getPendingSubscriptions().remove(message.variableHeader().messageId());
167 if (pendingSubscription == null) { 162 if (pendingSubscription == null) {
168 return; 163 return;
169 } 164 }
170 pendingSubscription.onSubackReceived(); 165 pendingSubscription.onSubackReceived();
171 - for (MqttPendingSubscribtion.MqttPendingHandler handler : pendingSubscription.getHandlers()) {  
172 - MqttSubscribtion subscribtion = new MqttSubscribtion(pendingSubscription.getTopic(), handler.getHandler(), handler.isOnce());  
173 - this.client.getSubscriptions().put(pendingSubscription.getTopic(), subscribtion);  
174 - this.client.getHandlerToSubscribtion().put(handler.getHandler(), subscribtion); 166 + for (MqttPendingSubscription.MqttPendingHandler handler : pendingSubscription.getHandlers()) {
  167 + MqttSubscription subscription = new MqttSubscription(pendingSubscription.getTopic(), handler.getHandler(), handler.isOnce());
  168 + this.client.getSubscriptions().put(pendingSubscription.getTopic(), subscription);
  169 + this.client.getHandlerToSubscribtion().put(handler.getHandler(), subscription);
175 } 170 }
176 this.client.getPendingSubscribeTopics().remove(pendingSubscription.getTopic()); 171 this.client.getPendingSubscribeTopics().remove(pendingSubscription.getTopic());
177 172
178 - this.client.getServerSubscribtions().add(pendingSubscription.getTopic()); 173 + this.client.getServerSubscriptions().add(pendingSubscription.getTopic());
179 174
180 if (!pendingSubscription.getFuture().isDone()) { 175 if (!pendingSubscription.getFuture().isDone()) {
181 pendingSubscription.getFuture().setSuccess(null); 176 pendingSubscription.getFuture().setSuccess(null);
@@ -215,13 +210,13 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> @@ -215,13 +210,13 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
215 } 210 }
216 211
217 private void handleUnsuback(MqttUnsubAckMessage message) { 212 private void handleUnsuback(MqttUnsubAckMessage message) {
218 - MqttPendingUnsubscribtion unsubscribtion = this.client.getPendingServerUnsubscribes().get(message.variableHeader().messageId());  
219 - if (unsubscribtion == null) { 213 + MqttPendingUnsubscription unsubscription = this.client.getPendingServerUnsubscribes().get(message.variableHeader().messageId());
  214 + if (unsubscription == null) {
220 return; 215 return;
221 } 216 }
222 - unsubscribtion.onUnsubackReceived();  
223 - this.client.getServerSubscribtions().remove(unsubscribtion.getTopic());  
224 - unsubscribtion.getFuture().setSuccess(null); 217 + unsubscription.onUnsubackReceived();
  218 + this.client.getServerSubscriptions().remove(unsubscription.getTopic());
  219 + unsubscription.getFuture().setSuccess(null);
225 this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId()); 220 this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId());
226 } 221 }
227 222
@@ -92,7 +92,7 @@ public interface MqttClient { @@ -92,7 +92,7 @@ public interface MqttClient {
92 92
93 /** 93 /**
94 * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 94 * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
95 - * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed 95 + * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed
96 * 96 *
97 * @param topic The topic filter to subscribe to 97 * @param topic The topic filter to subscribe to
98 * @param handler The handler to invoke when we receive a message 98 * @param handler The handler to invoke when we receive a message
@@ -102,7 +102,7 @@ public interface MqttClient { @@ -102,7 +102,7 @@ public interface MqttClient {
102 102
103 /** 103 /**
104 * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 104 * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
105 - * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed 105 + * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed
106 * 106 *
107 * @param topic The topic filter to subscribe to 107 * @param topic The topic filter to subscribe to
108 * @param handler The handler to invoke when we receive a message 108 * @param handler The handler to invoke when we receive a message
@@ -112,7 +112,7 @@ public interface MqttClient { @@ -112,7 +112,7 @@ public interface MqttClient {
112 Future<Void> once(String topic, MqttHandler handler, MqttQoS qos); 112 Future<Void> once(String topic, MqttHandler handler, MqttQoS qos);
113 113
114 /** 114 /**
115 - * Remove the subscribtion for the given topic and handler 115 + * Remove the subscription for the given topic and handler
116 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} 116 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
117 * 117 *
118 * @param topic The topic to unsubscribe for 118 * @param topic The topic to unsubscribe for
@@ -122,7 +122,7 @@ public interface MqttClient { @@ -122,7 +122,7 @@ public interface MqttClient {
122 Future<Void> off(String topic, MqttHandler handler); 122 Future<Void> off(String topic, MqttHandler handler);
123 123
124 /** 124 /**
125 - * Remove all subscribtions for the given topic. 125 + * Remove all subscriptions for the given topic.
126 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} 126 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
127 * 127 *
128 * @param topic The topic to unsubscribe for 128 * @param topic The topic to unsubscribe for
@@ -172,24 +172,18 @@ public interface MqttClient { @@ -172,24 +172,18 @@ public interface MqttClient {
172 */ 172 */
173 MqttClientConfig getClientConfig(); 173 MqttClientConfig getClientConfig();
174 174
175 - /**  
176 - * Construct the MqttClientImpl with default config  
177 - */  
178 - static MqttClient create(){  
179 - return new MqttClientImpl();  
180 - }  
181 175
182 /** 176 /**
183 * Construct the MqttClientImpl with additional config. 177 * Construct the MqttClientImpl with additional config.
184 * This config can also be changed using the {@link #getClientConfig()} function 178 * This config can also be changed using the {@link #getClientConfig()} function
185 * 179 *
186 * @param config The config object to use while looking for settings 180 * @param config The config object to use while looking for settings
  181 + * @param defaultHandler The handler for incoming messages that do not match any topic subscriptions
187 */ 182 */
188 - static MqttClient create(MqttClientConfig config){  
189 - return new MqttClientImpl(config); 183 + static MqttClient create(MqttClientConfig config, MqttHandler defaultHandler){
  184 + return new MqttClientImpl(config, defaultHandler);
190 } 185 }
191 186
192 -  
193 /** 187 /**
194 * Send disconnect and close channel 188 * Send disconnect and close channel
195 * 189 *
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 */ 15 */
16 package org.thingsboard.mqtt; 16 package org.thingsboard.mqtt;
17 17
  18 +import io.netty.channel.ChannelId;
  19 +
18 /** 20 /**
19 * Created by Valerii Sosliuk on 12/30/2017. 21 * Created by Valerii Sosliuk on 12/30/2017.
20 */ 22 */
@@ -25,5 +27,11 @@ public interface MqttClientCallback { @@ -25,5 +27,11 @@ public interface MqttClientCallback {
25 * 27 *
26 * @param cause the reason behind the loss of connection. 28 * @param cause the reason behind the loss of connection.
27 */ 29 */
28 - public void connectionLost(Throwable cause); 30 + void connectionLost(Throwable cause);
  31 +
  32 + /**
  33 + * This method is called when the connection to the server is recovered.
  34 + *
  35 + */
  36 + void onSuccessfulReconnect();
29 } 37 }
@@ -40,23 +40,26 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -40,23 +40,26 @@ import java.util.concurrent.atomic.AtomicInteger;
40 @SuppressWarnings({"WeakerAccess", "unused"}) 40 @SuppressWarnings({"WeakerAccess", "unused"})
41 final class MqttClientImpl implements MqttClient { 41 final class MqttClientImpl implements MqttClient {
42 42
43 - private final Set<String> serverSubscribtions = new HashSet<>();  
44 - private final IntObjectHashMap<MqttPendingUnsubscribtion> pendingServerUnsubscribes = new IntObjectHashMap<>(); 43 + private final Set<String> serverSubscriptions = new HashSet<>();
  44 + private final IntObjectHashMap<MqttPendingUnsubscription> pendingServerUnsubscribes = new IntObjectHashMap<>();
45 private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>(); 45 private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>();
46 private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>(); 46 private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>();
47 - private final HashMultimap<String, MqttSubscribtion> subscriptions = HashMultimap.create();  
48 - private final IntObjectHashMap<MqttPendingSubscribtion> pendingSubscribtions = new IntObjectHashMap<>(); 47 + private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create();
  48 + private final IntObjectHashMap<MqttPendingSubscription> pendingSubscriptions = new IntObjectHashMap<>();
49 private final Set<String> pendingSubscribeTopics = new HashSet<>(); 49 private final Set<String> pendingSubscribeTopics = new HashSet<>();
50 - private final HashMultimap<MqttHandler, MqttSubscribtion> handlerToSubscribtion = HashMultimap.create(); 50 + private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscribtion = HashMultimap.create();
51 private final AtomicInteger nextMessageId = new AtomicInteger(1); 51 private final AtomicInteger nextMessageId = new AtomicInteger(1);
52 52
53 private final MqttClientConfig clientConfig; 53 private final MqttClientConfig clientConfig;
54 54
  55 + private final MqttHandler defaultHandler;
  56 +
55 private EventLoopGroup eventLoop; 57 private EventLoopGroup eventLoop;
56 58
57 - private Channel channel; 59 + private volatile Channel channel;
58 60
59 - private boolean disconnected = false; 61 + private volatile boolean disconnected = false;
  62 + private volatile boolean reconnect = false;
60 private String host; 63 private String host;
61 private int port; 64 private int port;
62 private MqttClientCallback callback; 65 private MqttClientCallback callback;
@@ -65,8 +68,9 @@ final class MqttClientImpl implements MqttClient { @@ -65,8 +68,9 @@ final class MqttClientImpl implements MqttClient {
65 /** 68 /**
66 * Construct the MqttClientImpl with default config 69 * Construct the MqttClientImpl with default config
67 */ 70 */
68 - public MqttClientImpl() { 71 + public MqttClientImpl(MqttHandler defaultHandler) {
69 this.clientConfig = new MqttClientConfig(); 72 this.clientConfig = new MqttClientConfig();
  73 + this.defaultHandler = defaultHandler;
70 } 74 }
71 75
72 /** 76 /**
@@ -75,8 +79,9 @@ final class MqttClientImpl implements MqttClient { @@ -75,8 +79,9 @@ final class MqttClientImpl implements MqttClient {
75 * 79 *
76 * @param clientConfig The config object to use while looking for settings 80 * @param clientConfig The config object to use while looking for settings
77 */ 81 */
78 - public MqttClientImpl(MqttClientConfig clientConfig) { 82 + public MqttClientImpl(MqttClientConfig clientConfig, MqttHandler defaultHandler) {
79 this.clientConfig = clientConfig; 83 this.clientConfig = clientConfig;
  84 + this.defaultHandler = defaultHandler;
80 } 85 }
81 86
82 /** 87 /**
@@ -100,12 +105,15 @@ final class MqttClientImpl implements MqttClient { @@ -100,12 +105,15 @@ final class MqttClientImpl implements MqttClient {
100 */ 105 */
101 @Override 106 @Override
102 public Future<MqttConnectResult> connect(String host, int port) { 107 public Future<MqttConnectResult> connect(String host, int port) {
  108 + return connect(host, port, false);
  109 + }
  110 +
  111 + private Future<MqttConnectResult> connect(String host, int port, boolean reconnect) {
103 if (this.eventLoop == null) { 112 if (this.eventLoop == null) {
104 this.eventLoop = new NioEventLoopGroup(); 113 this.eventLoop = new NioEventLoopGroup();
105 } 114 }
106 this.host = host; 115 this.host = host;
107 this.port = port; 116 this.port = port;
108 -  
109 Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next()); 117 Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next());
110 Bootstrap bootstrap = new Bootstrap(); 118 Bootstrap bootstrap = new Bootstrap();
111 bootstrap.group(this.eventLoop); 119 bootstrap.group(this.eventLoop);
@@ -113,22 +121,47 @@ final class MqttClientImpl implements MqttClient { @@ -113,22 +121,47 @@ final class MqttClientImpl implements MqttClient {
113 bootstrap.remoteAddress(host, port); 121 bootstrap.remoteAddress(host, port);
114 bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext())); 122 bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext()));
115 ChannelFuture future = bootstrap.connect(); 123 ChannelFuture future = bootstrap.connect();
  124 +
116 future.addListener((ChannelFutureListener) f -> { 125 future.addListener((ChannelFutureListener) f -> {
117 if (f.isSuccess()) { 126 if (f.isSuccess()) {
118 MqttClientImpl.this.channel = f.channel(); 127 MqttClientImpl.this.channel = f.channel();
119 - } else if (clientConfig.isReconnect() && !disconnected) {  
120 - eventLoop.schedule((Runnable) () -> connect(host, port), 1L, TimeUnit.SECONDS); 128 + MqttClientImpl.this.channel.closeFuture().addListener((ChannelFutureListener) channelFuture -> {
  129 + if (isConnected()) {
  130 + return;
  131 + }
  132 + ChannelClosedException e = new ChannelClosedException("Channel is closed!");
  133 + if (callback != null) {
  134 + callback.connectionLost(e);
  135 + }
  136 + pendingSubscriptions.clear();
  137 + serverSubscriptions.clear();
  138 + subscriptions.clear();
  139 + pendingServerUnsubscribes.clear();
  140 + qos2PendingIncomingPublishes.clear();
  141 + pendingPublishes.clear();
  142 + pendingSubscribeTopics.clear();
  143 + handlerToSubscribtion.clear();
  144 + scheduleConnectIfRequired(host, port, true);
  145 + });
  146 + } else {
  147 + scheduleConnectIfRequired(host, port, reconnect);
121 } 148 }
122 }); 149 });
123 return connectFuture; 150 return connectFuture;
124 } 151 }
125 152
  153 + private void scheduleConnectIfRequired(String host, int port, boolean reconnect) {
  154 + if (clientConfig.isReconnect() && !disconnected) {
  155 + if (reconnect) {
  156 + this.reconnect = true;
  157 + }
  158 + eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), 1L, TimeUnit.SECONDS);
  159 + }
  160 + }
  161 +
126 @Override 162 @Override
127 public boolean isConnected() { 163 public boolean isConnected() {
128 - if (!disconnected) {  
129 - return channel == null ? false : channel.isActive();  
130 - };  
131 - return false; 164 + return !disconnected && channel != null && channel.isActive();
132 } 165 }
133 166
134 @Override 167 @Override
@@ -183,12 +216,12 @@ final class MqttClientImpl implements MqttClient { @@ -183,12 +216,12 @@ final class MqttClientImpl implements MqttClient {
183 */ 216 */
184 @Override 217 @Override
185 public Future<Void> on(String topic, MqttHandler handler, MqttQoS qos) { 218 public Future<Void> on(String topic, MqttHandler handler, MqttQoS qos) {
186 - return createSubscribtion(topic, handler, false, qos); 219 + return createSubscription(topic, handler, false, qos);
187 } 220 }
188 221
189 /** 222 /**
190 * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 223 * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
191 - * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed 224 + * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed
192 * 225 *
193 * @param topic The topic filter to subscribe to 226 * @param topic The topic filter to subscribe to
194 * @param handler The handler to invoke when we receive a message 227 * @param handler The handler to invoke when we receive a message
@@ -201,7 +234,7 @@ final class MqttClientImpl implements MqttClient { @@ -201,7 +234,7 @@ final class MqttClientImpl implements MqttClient {
201 234
202 /** 235 /**
203 * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 236 * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
204 - * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed 237 + * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed
205 * 238 *
206 * @param topic The topic filter to subscribe to 239 * @param topic The topic filter to subscribe to
207 * @param handler The handler to invoke when we receive a message 240 * @param handler The handler to invoke when we receive a message
@@ -210,11 +243,11 @@ final class MqttClientImpl implements MqttClient { @@ -210,11 +243,11 @@ final class MqttClientImpl implements MqttClient {
210 */ 243 */
211 @Override 244 @Override
212 public Future<Void> once(String topic, MqttHandler handler, MqttQoS qos) { 245 public Future<Void> once(String topic, MqttHandler handler, MqttQoS qos) {
213 - return createSubscribtion(topic, handler, true, qos); 246 + return createSubscription(topic, handler, true, qos);
214 } 247 }
215 248
216 /** 249 /**
217 - * Remove the subscribtion for the given topic and handler 250 + * Remove the subscription for the given topic and handler
218 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} 251 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
219 * 252 *
220 * @param topic The topic to unsubscribe for 253 * @param topic The topic to unsubscribe for
@@ -224,8 +257,8 @@ final class MqttClientImpl implements MqttClient { @@ -224,8 +257,8 @@ final class MqttClientImpl implements MqttClient {
224 @Override 257 @Override
225 public Future<Void> off(String topic, MqttHandler handler) { 258 public Future<Void> off(String topic, MqttHandler handler) {
226 Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); 259 Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
227 - for (MqttSubscribtion subscribtion : this.handlerToSubscribtion.get(handler)) {  
228 - this.subscriptions.remove(topic, subscribtion); 260 + for (MqttSubscription subscription : this.handlerToSubscribtion.get(handler)) {
  261 + this.subscriptions.remove(topic, subscription);
229 } 262 }
230 this.handlerToSubscribtion.removeAll(handler); 263 this.handlerToSubscribtion.removeAll(handler);
231 this.checkSubscribtions(topic, future); 264 this.checkSubscribtions(topic, future);
@@ -233,7 +266,7 @@ final class MqttClientImpl implements MqttClient { @@ -233,7 +266,7 @@ final class MqttClientImpl implements MqttClient {
233 } 266 }
234 267
235 /** 268 /**
236 - * Remove all subscribtions for the given topic. 269 + * Remove all subscriptions for the given topic.
237 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} 270 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
238 * 271 *
239 * @param topic The topic to unsubscribe for 272 * @param topic The topic to unsubscribe for
@@ -242,12 +275,12 @@ final class MqttClientImpl implements MqttClient { @@ -242,12 +275,12 @@ final class MqttClientImpl implements MqttClient {
242 @Override 275 @Override
243 public Future<Void> off(String topic) { 276 public Future<Void> off(String topic) {
244 Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); 277 Promise<Void> future = new DefaultPromise<>(this.eventLoop.next());
245 - ImmutableSet<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.subscriptions.get(topic));  
246 - for (MqttSubscribtion subscribtion : subscribtions) {  
247 - for (MqttSubscribtion handSub : this.handlerToSubscribtion.get(subscribtion.getHandler())) { 278 + ImmutableSet<MqttSubscription> subscriptions = ImmutableSet.copyOf(this.subscriptions.get(topic));
  279 + for (MqttSubscription subscription : subscriptions) {
  280 + for (MqttSubscription handSub : this.handlerToSubscribtion.get(subscription.getHandler())) {
248 this.subscriptions.remove(topic, handSub); 281 this.subscriptions.remove(topic, handSub);
249 } 282 }
250 - this.handlerToSubscribtion.remove(subscribtion.getHandler(), subscribtion); 283 + this.handlerToSubscribtion.remove(subscription.getHandler(), subscription);
251 } 284 }
252 this.checkSubscribtions(topic, future); 285 this.checkSubscribtions(topic, future);
253 return future; 286 return future;
@@ -310,7 +343,7 @@ final class MqttClientImpl implements MqttClient { @@ -310,7 +343,7 @@ final class MqttClientImpl implements MqttClient {
310 ChannelFuture channelFuture = this.sendAndFlushPacket(message); 343 ChannelFuture channelFuture = this.sendAndFlushPacket(message);
311 344
312 if (channelFuture != null) { 345 if (channelFuture != null) {
313 - pendingPublish.setSent(channelFuture != null); 346 + pendingPublish.setSent(true);
314 if (channelFuture.cause() != null) { 347 if (channelFuture.cause() != null) {
315 future.setFailure(channelFuture.cause()); 348 future.setFailure(channelFuture.cause());
316 return future; 349 return future;
@@ -352,6 +385,15 @@ final class MqttClientImpl implements MqttClient { @@ -352,6 +385,15 @@ final class MqttClientImpl implements MqttClient {
352 385
353 ///////////////////////////////////////////// PRIVATE API ///////////////////////////////////////////// 386 ///////////////////////////////////////////// PRIVATE API /////////////////////////////////////////////
354 387
  388 + public boolean isReconnect() {
  389 + return reconnect;
  390 + }
  391 +
  392 + public void onSuccessfulReconnect() {
  393 + callback.onSuccessfulReconnect();
  394 + }
  395 +
  396 +
355 ChannelFuture sendAndFlushPacket(Object message) { 397 ChannelFuture sendAndFlushPacket(Object message) {
356 if (this.channel == null) { 398 if (this.channel == null) {
357 return null; 399 return null;
@@ -359,11 +401,7 @@ final class MqttClientImpl implements MqttClient { @@ -359,11 +401,7 @@ final class MqttClientImpl implements MqttClient {
359 if (this.channel.isActive()) { 401 if (this.channel.isActive()) {
360 return this.channel.writeAndFlush(message); 402 return this.channel.writeAndFlush(message);
361 } 403 }
362 - ChannelClosedException e = new ChannelClosedException("Channel is closed");  
363 - if (callback != null) {  
364 - callback.connectionLost(e);  
365 - }  
366 - return this.channel.newFailedFuture(e); 404 + return this.channel.newFailedFuture(new ChannelClosedException("Channel is closed!"));
367 } 405 }
368 406
369 private MqttMessageIdVariableHeader getNewMessageId() { 407 private MqttMessageIdVariableHeader getNewMessageId() {
@@ -371,18 +409,18 @@ final class MqttClientImpl implements MqttClient { @@ -371,18 +409,18 @@ final class MqttClientImpl implements MqttClient {
371 return MqttMessageIdVariableHeader.from(this.nextMessageId.getAndIncrement()); 409 return MqttMessageIdVariableHeader.from(this.nextMessageId.getAndIncrement());
372 } 410 }
373 411
374 - private Future<Void> createSubscribtion(String topic, MqttHandler handler, boolean once, MqttQoS qos) { 412 + private Future<Void> createSubscription(String topic, MqttHandler handler, boolean once, MqttQoS qos) {
375 if (this.pendingSubscribeTopics.contains(topic)) { 413 if (this.pendingSubscribeTopics.contains(topic)) {
376 - Optional<Map.Entry<Integer, MqttPendingSubscribtion>> subscribtionEntry = this.pendingSubscribtions.entrySet().stream().filter((e) -> e.getValue().getTopic().equals(topic)).findAny();  
377 - if (subscribtionEntry.isPresent()) {  
378 - subscribtionEntry.get().getValue().addHandler(handler, once);  
379 - return subscribtionEntry.get().getValue().getFuture(); 414 + Optional<Map.Entry<Integer, MqttPendingSubscription>> subscriptionEntry = this.pendingSubscriptions.entrySet().stream().filter((e) -> e.getValue().getTopic().equals(topic)).findAny();
  415 + if (subscriptionEntry.isPresent()) {
  416 + subscriptionEntry.get().getValue().addHandler(handler, once);
  417 + return subscriptionEntry.get().getValue().getFuture();
380 } 418 }
381 } 419 }
382 - if (this.serverSubscribtions.contains(topic)) {  
383 - MqttSubscribtion subscribtion = new MqttSubscribtion(topic, handler, once);  
384 - this.subscriptions.put(topic, subscribtion);  
385 - this.handlerToSubscribtion.put(handler, subscribtion); 420 + if (this.serverSubscriptions.contains(topic)) {
  421 + MqttSubscription subscription = new MqttSubscription(topic, handler, once);
  422 + this.subscriptions.put(topic, subscription);
  423 + this.handlerToSubscribtion.put(handler, subscription);
386 return this.channel.newSucceededFuture(); 424 return this.channel.newSucceededFuture();
387 } 425 }
388 426
@@ -393,27 +431,27 @@ final class MqttClientImpl implements MqttClient { @@ -393,27 +431,27 @@ final class MqttClientImpl implements MqttClient {
393 MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription)); 431 MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription));
394 MqttSubscribeMessage message = new MqttSubscribeMessage(fixedHeader, variableHeader, payload); 432 MqttSubscribeMessage message = new MqttSubscribeMessage(fixedHeader, variableHeader, payload);
395 433
396 - final MqttPendingSubscribtion pendingSubscribtion = new MqttPendingSubscribtion(future, topic, message);  
397 - pendingSubscribtion.addHandler(handler, once);  
398 - this.pendingSubscribtions.put(variableHeader.messageId(), pendingSubscribtion); 434 + final MqttPendingSubscription pendingSubscription = new MqttPendingSubscription(future, topic, message);
  435 + pendingSubscription.addHandler(handler, once);
  436 + this.pendingSubscriptions.put(variableHeader.messageId(), pendingSubscription);
399 this.pendingSubscribeTopics.add(topic); 437 this.pendingSubscribeTopics.add(topic);
400 - pendingSubscribtion.setSent(this.sendAndFlushPacket(message) != null); //If not sent, we will send it when the connection is opened 438 + pendingSubscription.setSent(this.sendAndFlushPacket(message) != null); //If not sent, we will send it when the connection is opened
401 439
402 - pendingSubscribtion.startRetransmitTimer(this.eventLoop.next(), this::sendAndFlushPacket); 440 + pendingSubscription.startRetransmitTimer(this.eventLoop.next(), this::sendAndFlushPacket);
403 441
404 return future; 442 return future;
405 } 443 }
406 444
407 private void checkSubscribtions(String topic, Promise<Void> promise) { 445 private void checkSubscribtions(String topic, Promise<Void> promise) {
408 - if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscribtions.contains(topic)) { 446 + if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscriptions.contains(topic)) {
409 MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); 447 MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
410 MqttMessageIdVariableHeader variableHeader = getNewMessageId(); 448 MqttMessageIdVariableHeader variableHeader = getNewMessageId();
411 MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic)); 449 MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic));
412 MqttUnsubscribeMessage message = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload); 450 MqttUnsubscribeMessage message = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload);
413 451
414 - MqttPendingUnsubscribtion pendingUnsubscribtion = new MqttPendingUnsubscribtion(promise, topic, message);  
415 - this.pendingServerUnsubscribes.put(variableHeader.messageId(), pendingUnsubscribtion);  
416 - pendingUnsubscribtion.startRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); 452 + MqttPendingUnsubscription pendingUnsubscription = new MqttPendingUnsubscription(promise, topic, message);
  453 + this.pendingServerUnsubscribes.put(variableHeader.messageId(), pendingUnsubscription);
  454 + pendingUnsubscription.startRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket);
417 455
418 this.sendAndFlushPacket(message); 456 this.sendAndFlushPacket(message);
419 } else { 457 } else {
@@ -421,11 +459,11 @@ final class MqttClientImpl implements MqttClient { @@ -421,11 +459,11 @@ final class MqttClientImpl implements MqttClient {
421 } 459 }
422 } 460 }
423 461
424 - IntObjectHashMap<MqttPendingSubscribtion> getPendingSubscribtions() {  
425 - return pendingSubscribtions; 462 + IntObjectHashMap<MqttPendingSubscription> getPendingSubscriptions() {
  463 + return pendingSubscriptions;
426 } 464 }
427 465
428 - HashMultimap<String, MqttSubscribtion> getSubscriptions() { 466 + HashMultimap<String, MqttSubscription> getSubscriptions() {
429 return subscriptions; 467 return subscriptions;
430 } 468 }
431 469
@@ -433,15 +471,15 @@ final class MqttClientImpl implements MqttClient { @@ -433,15 +471,15 @@ final class MqttClientImpl implements MqttClient {
433 return pendingSubscribeTopics; 471 return pendingSubscribeTopics;
434 } 472 }
435 473
436 - HashMultimap<MqttHandler, MqttSubscribtion> getHandlerToSubscribtion() { 474 + HashMultimap<MqttHandler, MqttSubscription> getHandlerToSubscribtion() {
437 return handlerToSubscribtion; 475 return handlerToSubscribtion;
438 } 476 }
439 477
440 - Set<String> getServerSubscribtions() {  
441 - return serverSubscribtions; 478 + Set<String> getServerSubscriptions() {
  479 + return serverSubscriptions;
442 } 480 }
443 481
444 - IntObjectHashMap<MqttPendingUnsubscribtion> getPendingServerUnsubscribes() { 482 + IntObjectHashMap<MqttPendingUnsubscription> getPendingServerUnsubscribes() {
445 return pendingServerUnsubscribes; 483 return pendingServerUnsubscribes;
446 } 484 }
447 485
@@ -481,4 +519,9 @@ final class MqttClientImpl implements MqttClient { @@ -481,4 +519,9 @@ final class MqttClientImpl implements MqttClient {
481 ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture)); 519 ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture));
482 } 520 }
483 } 521 }
  522 +
  523 + MqttHandler getDefaultHandler() {
  524 + return defaultHandler;
  525 + }
  526 +
484 } 527 }
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscription.java renamed from netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscribtion.java
@@ -23,7 +23,7 @@ import java.util.HashSet; @@ -23,7 +23,7 @@ import java.util.HashSet;
23 import java.util.Set; 23 import java.util.Set;
24 import java.util.function.Consumer; 24 import java.util.function.Consumer;
25 25
26 -final class MqttPendingSubscribtion { 26 +final class MqttPendingSubscription {
27 27
28 private final Promise<Void> future; 28 private final Promise<Void> future;
29 private final String topic; 29 private final String topic;
@@ -34,7 +34,7 @@ final class MqttPendingSubscribtion { @@ -34,7 +34,7 @@ final class MqttPendingSubscribtion {
34 34
35 private boolean sent = false; 35 private boolean sent = false;
36 36
37 - MqttPendingSubscribtion(Promise<Void> future, String topic, MqttSubscribeMessage message) { 37 + MqttPendingSubscription(Promise<Void> future, String topic, MqttSubscribeMessage message) {
38 this.future = future; 38 this.future = future;
39 this.topic = topic; 39 this.topic = topic;
40 this.subscribeMessage = message; 40 this.subscribeMessage = message;
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscription.java renamed from netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscribtion.java
@@ -21,14 +21,14 @@ import io.netty.util.concurrent.Promise; @@ -21,14 +21,14 @@ import io.netty.util.concurrent.Promise;
21 21
22 import java.util.function.Consumer; 22 import java.util.function.Consumer;
23 23
24 -final class MqttPendingUnsubscribtion { 24 +final class MqttPendingUnsubscription {
25 25
26 private final Promise<Void> future; 26 private final Promise<Void> future;
27 private final String topic; 27 private final String topic;
28 28
29 private final RetransmissionHandler<MqttUnsubscribeMessage> retransmissionHandler = new RetransmissionHandler<>(); 29 private final RetransmissionHandler<MqttUnsubscribeMessage> retransmissionHandler = new RetransmissionHandler<>();
30 30
31 - MqttPendingUnsubscribtion(Promise<Void> future, String topic, MqttUnsubscribeMessage unsubscribeMessage) { 31 + MqttPendingUnsubscription(Promise<Void> future, String topic, MqttUnsubscribeMessage unsubscribeMessage) {
32 this.future = future; 32 this.future = future;
33 this.topic = topic; 33 this.topic = topic;
34 34
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java renamed from netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscribtion.java
@@ -17,7 +17,7 @@ package org.thingsboard.mqtt; @@ -17,7 +17,7 @@ package org.thingsboard.mqtt;
17 17
18 import java.util.regex.Pattern; 18 import java.util.regex.Pattern;
19 19
20 -final class MqttSubscribtion { 20 +final class MqttSubscription {
21 21
22 private final String topic; 22 private final String topic;
23 private final Pattern topicRegex; 23 private final Pattern topicRegex;
@@ -27,7 +27,7 @@ final class MqttSubscribtion { @@ -27,7 +27,7 @@ final class MqttSubscribtion {
27 27
28 private boolean called; 28 private boolean called;
29 29
30 - MqttSubscribtion(String topic, MqttHandler handler, boolean once) { 30 + MqttSubscription(String topic, MqttHandler handler, boolean once) {
31 if(topic == null){ 31 if(topic == null){
32 throw new NullPointerException("topic"); 32 throw new NullPointerException("topic");
33 } 33 }
@@ -65,7 +65,7 @@ final class MqttSubscribtion { @@ -65,7 +65,7 @@ final class MqttSubscribtion {
65 if (this == o) return true; 65 if (this == o) return true;
66 if (o == null || getClass() != o.getClass()) return false; 66 if (o == null || getClass() != o.getClass()) return false;
67 67
68 - MqttSubscribtion that = (MqttSubscribtion) o; 68 + MqttSubscription that = (MqttSubscription) o;
69 69
70 return once == that.once && topic.equals(that.topic) && handler.equals(that.handler); 70 return once == that.once && topic.equals(that.topic) && handler.equals(that.handler);
71 } 71 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 <artifactId>thingsboard</artifactId> 22 <artifactId>thingsboard</artifactId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <packaging>pom</packaging> 24 <packaging>pom</packaging>
25 25
26 <name>Thingsboard</name> 26 <name>Thingsboard</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>2.0.1</version> 25 + <version>2.0.2</version>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.rule-engine</groupId> 28 <groupId>org.thingsboard.rule-engine</groupId>
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>2.0.1</version> 25 + <version>2.0.2</version>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.rule-engine</groupId> 28 <groupId>org.thingsboard.rule-engine</groupId>
@@ -111,8 +111,9 @@ public class TbMqttNode implements TbNode { @@ -111,8 +111,9 @@ public class TbMqttNode implements TbNode {
111 if (!StringUtils.isEmpty(this.config.getClientId())) { 111 if (!StringUtils.isEmpty(this.config.getClientId())) {
112 config.setClientId(this.config.getClientId()); 112 config.setClientId(this.config.getClientId());
113 } 113 }
  114 + config.setCleanSession(this.config.isCleanSession());
114 this.config.getCredentials().configure(config); 115 this.config.getCredentials().configure(config);
115 - MqttClient client = MqttClient.create(config); 116 + MqttClient client = MqttClient.create(config, null);
116 client.setEventLoop(this.eventLoopGroup); 117 client.setEventLoop(this.eventLoopGroup);
117 Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort()); 118 Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort());
118 MqttConnectResult result; 119 MqttConnectResult result;
@@ -30,6 +30,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf @@ -30,6 +30,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
30 private int connectTimeoutSec; 30 private int connectTimeoutSec;
31 private String clientId; 31 private String clientId;
32 32
  33 + private boolean cleanSession;
33 private boolean ssl; 34 private boolean ssl;
34 private MqttClientCredentials credentials; 35 private MqttClientCredentials credentials;
35 36
@@ -40,6 +41,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf @@ -40,6 +41,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
40 configuration.setHost("localhost"); 41 configuration.setHost("localhost");
41 configuration.setPort(1883); 42 configuration.setPort(1883);
42 configuration.setConnectTimeoutSec(10); 43 configuration.setConnectTimeoutSec(10);
  44 + configuration.setCleanSession(true);
43 configuration.setSsl(false); 45 configuration.setSsl(false);
44 configuration.setCredentials(new AnonymousCredentials()); 46 configuration.setCredentials(new AnonymousCredentials());
45 return configuration; 47 return configuration;
@@ -16,7 +16,10 @@ @@ -16,7 +16,10 @@
16 package org.thingsboard.rule.engine.rpc; 16 package org.thingsboard.rule.engine.rpc;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.fasterxml.jackson.databind.JsonNode;
19 import com.google.gson.Gson; 21 import com.google.gson.Gson;
  22 +import com.google.gson.JsonElement;
20 import com.google.gson.JsonObject; 23 import com.google.gson.JsonObject;
21 import com.google.gson.JsonParser; 24 import com.google.gson.JsonParser;
22 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
@@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
35 import org.thingsboard.server.common.data.plugin.ComponentType; 38 import org.thingsboard.server.common.data.plugin.ComponentType;
36 import org.thingsboard.server.common.msg.TbMsg; 39 import org.thingsboard.server.common.msg.TbMsg;
37 40
  41 +import java.io.IOException;
38 import java.util.Random; 42 import java.util.Random;
39 import java.util.UUID; 43 import java.util.UUID;
40 import java.util.concurrent.TimeUnit; 44 import java.util.concurrent.TimeUnit;
@@ -86,10 +90,18 @@ public class TbSendRPCRequestNode implements TbNode { @@ -86,10 +90,18 @@ public class TbSendRPCRequestNode implements TbNode {
86 tmp = msg.getMetaData().getValue("expirationTime"); 90 tmp = msg.getMetaData().getValue("expirationTime");
87 long expirationTime = !StringUtils.isEmpty(tmp) ? Long.parseLong(tmp) : (System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(config.getTimeoutInSeconds())); 91 long expirationTime = !StringUtils.isEmpty(tmp) ? Long.parseLong(tmp) : (System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(config.getTimeoutInSeconds()));
88 92
  93 + String params;
  94 + JsonElement paramsEl = json.get("params");
  95 + if (paramsEl.isJsonPrimitive()) {
  96 + params = paramsEl.getAsString();
  97 + } else {
  98 + params = gson.toJson(paramsEl);
  99 + }
  100 +
89 RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() 101 RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder()
90 .oneway(oneway) 102 .oneway(oneway)
91 .method(json.get("method").getAsString()) 103 .method(json.get("method").getAsString())
92 - .body(gson.toJson(json.get("params"))) 104 + .body(params)
93 .deviceId(new DeviceId(msg.getOriginator().getId())) 105 .deviceId(new DeviceId(msg.getOriginator().getId()))
94 .requestId(requestId) 106 .requestId(requestId)
95 .requestUUID(requestUUID) 107 .requestUUID(requestUUID)
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(72)},function(e,t){},1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </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> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> ";  
2 -},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},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.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},21,function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <tb-entity-type-select style=min-width:100px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(51),i=a(r),o=n(36),l=a(o),s=n(39),u=a(s),d=n(38),c=a(d),m=n(37),p=a(m),g=n(42),f=a(g),b=n(46),v=a(b),y=n(47),q=a(y),h=n(45),T=a(h),$=n(41),k=a($),w=n(49),C=a(w),_=n(50),x=a(_),E=n(44),M=a(E),S=n(43),N=a(S),V=n(48),P=a(V);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",p.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",T.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",x.default).directive("tbActionNodeRabbitMqConfig",M.default).directive("tbActionNodeMqttConfig",N.default).directive("tbActionNodeSendEmailConfig",P.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(57),i=a(r),o=n(58),l=a(o),s=n(55),u=a(s),d=n(59),c=a(d),m=n(54),p=a(m),g=n(60),f=a(g);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",p.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}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(26),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(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(64),i=a(r),o=n(63),l=a(o),s=n(65),u=a(s),d=n(61),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.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 p={name:n.messageType[m].name,value:n.messageType[m].value};c.push(p)}a.transformMessageTypeChip=function(e){  
3 -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(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),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(31),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(32),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(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(68),i=a(r),o=n(70),l=a(o),s=n(71),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(34),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(35),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(75),i=a(r),o=n(62),l=a(o),s=n(56),u=a(s),d=n(69),c=a(d),m=n(40),p=a(m),g=n(53),f=a(g),b=n(67),v=a(b),y=n(52),q=a(y),h=n(66),T=a(h),$=n(74),k=a($);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,p.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",T.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","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","headers-hint":"Use <code>${metaKeyName}</code> in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","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":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"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(73),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 from Device",value:"TO_SERVER_RPC_REQUEST"},RPC_CALL_FROM_SERVER_TO_DEVICE:{name:"RPC Request to Device",value:"RPC_CALL_FROM_SERVER_TO_DEVICE"},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"},ENTITY_CREATED:{name:"Entity Created",value:"ENTITY_CREATED"},ENTITY_UPDATED:{name:"Entity Updated",value:"ENTITY_UPDATED"},ENTITY_DELETED:{name:"Entity Deleted",value:"ENTITY_DELETED"},ENTITY_ASSIGNED:{name:"Entity Assigned",value:"ENTITY_ASSIGNED"},ENTITY_UNASSIGNED:{name:"Entity Unassigned",value:"ENTITY_UNASSIGNED"},ATTRIBUTES_UPDATED:{name:"Attributes Updated",value:"ATTRIBUTES_UPDATED"},ATTRIBUTES_DELETED:{name:"Attributes Deleted",value:"ATTRIBUTES_DELETED"}},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(72)},function(e,t){},1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=' <section ng-form name=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> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </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> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> ";
  2 +},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},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.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},21,function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <tb-entity-type-select style=min-width:100px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(51),i=a(r),o=n(36),l=a(o),s=n(39),u=a(s),d=n(38),c=a(d),m=n(37),g=a(m),p=n(42),f=a(p),b=n(46),v=a(b),y=n(47),q=a(y),h=n(45),T=a(h),$=n(41),k=a($),w=n(49),C=a(w),_=n(50),x=a(_),E=n(44),M=a(E),S=n(43),N=a(S),V=n(48),P=a(V);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",T.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",x.default).directive("tbActionNodeRabbitMqConfig",M.default).directive("tbActionNodeMqttConfig",N.default).directive("tbActionNodeSendEmailConfig",P.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(57),i=a(r),o=n(58),l=a(o),s=n(55),u=a(s),d=n(59),c=a(d),m=n(54),g=a(m),p=n(60),f=a(p);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}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(26),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(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(64),i=a(r),o=n(63),l=a(o),s=n(65),u=a(s),d=n(61),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.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){
  3 +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(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),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(31),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(32),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(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(68),i=a(r),o=n(70),l=a(o),s=n(71),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(34),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(35),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(75),i=a(r),o=n(62),l=a(o),s=n(56),u=a(s),d=n(69),c=a(d),m=n(40),g=a(m),p=n(53),f=a(p),b=n(67),v=a(b),y=n(52),q=a(y),h=n(66),T=a(h),$=n(74),k=a($);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",T.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","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","headers-hint":"Use <code>${metaKeyName}</code> in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"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(73),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 from Device",value:"TO_SERVER_RPC_REQUEST"},RPC_CALL_FROM_SERVER_TO_DEVICE:{name:"RPC Request to Device",value:"RPC_CALL_FROM_SERVER_TO_DEVICE"},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"},ENTITY_CREATED:{name:"Entity Created",value:"ENTITY_CREATED"},ENTITY_UPDATED:{name:"Entity Updated",value:"ENTITY_UPDATED"},ENTITY_DELETED:{name:"Entity Deleted",value:"ENTITY_DELETED"},ENTITY_ASSIGNED:{name:"Entity Assigned",value:"ENTITY_ASSIGNED"},ENTITY_UNASSIGNED:{name:"Entity Unassigned",value:"ENTITY_UNASSIGNED"},ATTRIBUTES_UPDATED:{name:"Attributes Updated",value:"ATTRIBUTES_UPDATED"},ATTRIBUTES_DELETED:{name:"Attributes Deleted",value:"ATTRIBUTES_DELETED"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}]));
4 //# sourceMappingURL=rulenode-core-config.js.map 4 //# sourceMappingURL=rulenode-core-config.js.map
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -130,6 +130,13 @@ public class CoapServerTest { @@ -130,6 +130,13 @@ public class CoapServerTest {
130 } 130 }
131 } 131 }
132 } 132 }
  133 +
  134 + @Override
  135 + public void onDeviceAdded(Device device) {
  136 +
  137 + }
  138 +
  139 +
133 }; 140 };
134 } 141 }
135 142
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -92,6 +92,7 @@ public class GatewaySessionCtx { @@ -92,6 +92,7 @@ public class GatewaySessionCtx {
92 device.setType(deviceType); 92 device.setType(deviceType);
93 device = deviceService.saveDevice(device); 93 device = deviceService.saveDevice(device);
94 relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created")); 94 relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created"));
  95 + processor.onDeviceAdded(device);
95 } 96 }
96 GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device); 97 GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device);
97 devices.put(deviceName, ctx); 98 devices.put(deviceName, ctx);
@@ -154,6 +155,7 @@ public class GatewaySessionCtx { @@ -154,6 +155,7 @@ public class GatewaySessionCtx {
154 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); 155 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
155 processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), 156 processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(),
156 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); 157 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
  158 + ack(mqttMsg);
157 } else { 159 } else {
158 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); 160 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
159 } 161 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
1 { 1 {
2 "name": "thingsboard", 2 "name": "thingsboard",
3 "private": true, 3 "private": true,
4 - "version": "2.0.1", 4 + "version": "2.0.2",
5 "description": "Thingsboard UI", 5 "description": "Thingsboard UI",
6 "licenses": [ 6 "licenses": [
7 { 7 {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.0.1</version> 23 + <version>2.0.2</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -13,804 +13,1321 @@ @@ -13,804 +13,1321 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 - export default function addLocaleSpanish(locales) { 16 +export default function addLocaleSpanish(locales) {
17 var es_ES = { 17 var es_ES = {
18 - "access": {  
19 - "unauthorized": "No autorizado",  
20 - "unauthorized-access": "Acceso no autorizado",  
21 - "unauthorized-access-text": "Debes iniciar sesión para tener acceso a este recurso!",  
22 - "access-forbidden": "Acceso Prohibido",  
23 - "access-forbidden-text": "No tienes derechos para acceder a esta ubicación!<br/>Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.",  
24 - "refresh-token-expired": "La sesión ha expirado",  
25 - "refresh-token-failed": "No se puede actualizar la sesión" 18 + "access": {
  19 + "unauthorized": "No autorizado",
  20 + "unauthorized-access": "Acceso no autorizado",
  21 + "unauthorized-access-text": "Debes iniciar sesión para tener acceso a este recurso!",
  22 + "access-forbidden": "Acceso Prohibido",
  23 + "access-forbidden-text": "No tienes derechos para acceder a esta ubicación!<br/>Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.",
  24 + "refresh-token-expired": "La sesión ha expirado",
  25 + "refresh-token-failed": "No se puede actualizar la sesión"
26 }, 26 },
27 "action": { 27 "action": {
28 - "activate": "Activar",  
29 - "suspend": "Suspender",  
30 - "save": "Guardar",  
31 - "saveAs": "Guardar como",  
32 - "cancel": "Cancelar",  
33 - "ok": "OK",  
34 - "delete": "Borrar",  
35 - "add": "Agregar",  
36 - "yes": "Si",  
37 - "no": "No",  
38 - "update": "Actualizar",  
39 - "remove": "Eliminar",  
40 - "search": "Buscar",  
41 - "assign": "Asignar",  
42 - "unassign": "Cancelar asignación",  
43 - "share": "Compartir",  
44 - "make-private": "Hacer privado",  
45 - "apply": "Aplicar",  
46 - "apply-changes": "Aplicar cambios",  
47 - "edit-mode": "Modo Edición",  
48 - "enter-edit-mode": "Modo Edición",  
49 - "decline-changes": "Descartar cambios",  
50 - "close": "Cerrar",  
51 - "back": "Atrás",  
52 - "run": "Correr",  
53 - "sign-in": "Regístrate!",  
54 - "edit": "Editar",  
55 - "view": "Ver",  
56 - "create": "Crear",  
57 - "drag": "Arrastrar",  
58 - "refresh": "Refrescar",  
59 - "undo": "Deshacer",  
60 - "copy": "Copiar",  
61 - "paste": "Pegar",  
62 - "import": "Importar",  
63 - "export": "Exportar",  
64 - "share-via": "Compartir vía {{provider}}" 28 + "activate": "Activar",
  29 + "suspend": "Suspender",
  30 + "save": "Guardar",
  31 + "saveAs": "Guardar como",
  32 + "cancel": "Cancelar",
  33 + "ok": "OK",
  34 + "delete": "Borrar",
  35 + "add": "Agregar",
  36 + "yes": "Si",
  37 + "no": "No",
  38 + "update": "Actualizar",
  39 + "remove": "Eliminar",
  40 + "search": "Buscar",
  41 + "assign": "Asignar",
  42 + "unassign": "Cancelar asignación",
  43 + "share": "Compartir",
  44 + "make-private": "Hacer privado",
  45 + "apply": "Aplicar",
  46 + "apply-changes": "Aplicar cambios",
  47 + "edit-mode": "Modo Edición",
  48 + "enter-edit-mode": "Modo Edición",
  49 + "decline-changes": "Descartar cambios",
  50 + "close": "Cerrar",
  51 + "back": "Atrás",
  52 + "run": "Correr",
  53 + "sign-in": "Regístrate!",
  54 + "edit": "Editar",
  55 + "view": "Ver",
  56 + "create": "Crear",
  57 + "drag": "Arrastrar",
  58 + "refresh": "Refrescar",
  59 + "undo": "Deshacer",
  60 + "copy": "Copiar",
  61 + "paste": "Pegar",
  62 + "import": "Importar",
  63 + "export": "Exportar",
  64 + "share-via": "Compartir vía {{provider}}"
65 }, 65 },
66 "aggregation": { 66 "aggregation": {
67 - "aggregation": "Agregación",  
68 - "function": "Función de Agregación",  
69 - "limit": "Valores Max",  
70 - "group-interval": "Intervalo de agrupación",  
71 - "min": "Min",  
72 - "max": "Max",  
73 - "avg": "Promedio",  
74 - "sum": "Suma",  
75 - "count": "Cuenta",  
76 - "none": "Ninguno" 67 + "aggregation": "Agregación",
  68 + "function": "Función de Agregación",
  69 + "limit": "Valores Max",
  70 + "group-interval": "Intervalo de agrupación",
  71 + "min": "Min",
  72 + "max": "Max",
  73 + "avg": "Promedio",
  74 + "sum": "Suma",
  75 + "count": "Cuenta",
  76 + "none": "Ninguno"
77 }, 77 },
78 "admin": { 78 "admin": {
79 - "general": "General",  
80 - "general-settings": "Ajustes General",  
81 - "outgoing-mail": "Mail de Salida",  
82 - "outgoing-mail-settings": "Ajustes del Mail de Salida",  
83 - "system-settings": "Sistema",  
84 - "test-mail-sent": "Mail de prueba enviado correctamente!",  
85 - "base-url": "URL Base",  
86 - "base-url-required": "URL Base requerida.",  
87 - "mail-from": "Mail Desde",  
88 - "mail-from-required": "Mail Desde requerido.",  
89 - "smtp-protocol": "Protocolo SMTP",  
90 - "smtp-host": "Host SMTP",  
91 - "smtp-host-required": "Host SMTP requerido.",  
92 - "smtp-port": "Puerto SMTP",  
93 - "smtp-port-required": "Debe ingresar un Puerto SMTP.",  
94 - "smtp-port-invalid": "No parece un Puerto SMTP valido.",  
95 - "timeout-msec": "Timeout (ms)",  
96 - "timeout-required": "Timeout requerido.",  
97 - "timeout-invalid": "No parece un Timeout valido.",  
98 - "enable-tls": "Habilitar TLS",  
99 - "send-test-mail": "Enviar mail de prueba" 79 + "general": "General",
  80 + "general-settings": "Ajustes General",
  81 + "outgoing-mail": "Mail de Salida",
  82 + "outgoing-mail-settings": "Ajustes del Mail de Salida",
  83 + "system-settings": "Sistema",
  84 + "test-mail-sent": "Mail de prueba enviado correctamente!",
  85 + "base-url": "URL Base",
  86 + "base-url-required": "URL Base requerida.",
  87 + "mail-from": "Mail Desde",
  88 + "mail-from-required": "Mail Desde requerido.",
  89 + "smtp-protocol": "Protocolo SMTP",
  90 + "smtp-host": "Host SMTP",
  91 + "smtp-host-required": "Host SMTP requerido.",
  92 + "smtp-port": "Puerto SMTP",
  93 + "smtp-port-required": "Debe ingresar un Puerto SMTP.",
  94 + "smtp-port-invalid": "No parece un Puerto SMTP valido.",
  95 + "timeout-msec": "Timeout (ms)",
  96 + "timeout-required": "Timeout requerido.",
  97 + "timeout-invalid": "No parece un Timeout valido.",
  98 + "enable-tls": "Habilitar TLS",
  99 + "send-test-mail": "Enviar mail de prueba"
  100 + },
  101 + "alarm": { // TODO
  102 + "alarm": "Alarm",
  103 + "alarms": "Alarms",
  104 + "select-alarm": "Select alarm",
  105 + "no-alarms-matching": "No alarms matching '{{entity}}' were found.",
  106 + "alarm-required": "Alarm is required",
  107 + "alarm-status": "Alarm status",
  108 + "search-status": {
  109 + "ANY": "Any",
  110 + "ACTIVE": "Active",
  111 + "CLEARED": "Cleared",
  112 + "ACK": "Acknowledged",
  113 + "UNACK": "Unacknowledged"
  114 + },
  115 + "display-status": {
  116 + "ACTIVE_UNACK": "Active Unacknowledged",
  117 + "ACTIVE_ACK": "Active Acknowledged",
  118 + "CLEARED_UNACK": "Cleared Unacknowledged",
  119 + "CLEARED_ACK": "Cleared Acknowledged"
  120 + },
  121 + "no-alarms-prompt": "No alarms found",
  122 + "created-time": "Created time",
  123 + "type": "Type",
  124 + "severity": "Severity",
  125 + "originator": "Originator",
  126 + "originator-type": "Originator type",
  127 + "details": "Details",
  128 + "status": "Status",
  129 + "alarm-details": "Alarm details",
  130 + "start-time": "Start time",
  131 + "end-time": "End time",
  132 + "ack-time": "Acknowledged time",
  133 + "clear-time": "Cleared time",
  134 + "severity-critical": "Critical",
  135 + "severity-major": "Major",
  136 + "severity-minor": "Minor",
  137 + "severity-warning": "Warning",
  138 + "severity-indeterminate": "Indeterminate",
  139 + "acknowledge": "Acknowledge",
  140 + "clear": "Clear",
  141 + "search": "Search alarms",
  142 + "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected",
  143 + "no-data": "No data to display",
  144 + "polling-interval": "Alarms polling interval (sec)",
  145 + "polling-interval-required": "Alarms polling interval is required.",
  146 + "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
  147 + "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }",
  148 + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?",
  149 + "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }",
  150 + "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?"
  151 + },
  152 + "alias": { // TODO
  153 + "add": "Add alias",
  154 + "edit": "Edit alias",
  155 + "name": "Alias name",
  156 + "name-required": "Alias name is required",
  157 + "duplicate-alias": "Alias with same name is already exists.",
  158 + "filter-type-single-entity": "Single entity",
  159 + "filter-type-entity-list": "Entity list",
  160 + "filter-type-entity-name": "Entity name",
  161 + "filter-type-state-entity": "Entity from dashboard state",
  162 + "filter-type-state-entity-description": "Entity taken from dashboard state parameters",
  163 + "filter-type-asset-type": "Asset type",
  164 + "filter-type-asset-type-description": "Assets of type '{{assetType}}'",
  165 + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
  166 + "filter-type-device-type": "Device type",
  167 + "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
  168 + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
  169 + "filter-type-relations-query": "Relations query",
  170 + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  171 + "filter-type-asset-search-query": "Asset search query",
  172 + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  173 + "filter-type-device-search-query": "Device search query",
  174 + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  175 + "entity-filter": "Entity filter",
  176 + "resolve-multiple": "Resolve as multiple entities",
  177 + "filter-type": "Filter type",
  178 + "filter-type-required": "Filter type is required.",
  179 + "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
  180 + "no-entity-filter-specified": "No entity filter specified",
  181 + "root-state-entity": "Use dashboard state entity as root",
  182 + "root-entity": "Root entity",
  183 + "state-entity-parameter-name": "State entity parameter name",
  184 + "default-state-entity": "Default state entity",
  185 + "default-entity-parameter-name": "By default",
  186 + "max-relation-level": "Max relation level",
  187 + "unlimited-level": "Unlimited level",
  188 + "state-entity": "Dashboard state entity",
  189 + "all-entities": "All entities",
  190 + "any-relation": "any"
  191 + },
  192 + "asset": { // TODO
  193 + "asset": "Asset",
  194 + "assets": "Assets",
  195 + "management": "Asset management",
  196 + "view-assets": "View Assets",
  197 + "add": "Add Asset",
  198 + "assign-to-customer": "Assign to customer",
  199 + "assign-asset-to-customer": "Assign Asset(s) To Customer",
  200 + "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
  201 + "no-assets-text": "No assets found",
  202 + "assign-to-customer-text": "Please select the customer to assign the asset(s)",
  203 + "public": "Public",
  204 + "assignedToCustomer": "Assigned to customer",
  205 + "make-public": "Make asset public",
  206 + "make-private": "Make asset private",
  207 + "unassign-from-customer": "Unassign from customer",
  208 + "delete": "Delete asset",
  209 + "asset-public": "Asset is public",
  210 + "asset-type": "Asset type",
  211 + "asset-type-required": "Asset type is required.",
  212 + "select-asset-type": "Select asset type",
  213 + "enter-asset-type": "Enter asset type",
  214 + "any-asset": "Any asset",
  215 + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
  216 + "asset-type-list-empty": "No asset types selected.",
  217 + "asset-types": "Asset types",
  218 + "name": "Name",
  219 + "name-required": "Name is required.",
  220 + "description": "Description",
  221 + "type": "Type",
  222 + "type-required": "Type is required.",
  223 + "details": "Details",
  224 + "events": "Events",
  225 + "add-asset-text": "Add new asset",
  226 + "asset-details": "Asset details",
  227 + "assign-assets": "Assign assets",
  228 + "assign-assets-text": "Assign { count, select, 1 {1 asset} other {# assets} } to customer",
  229 + "delete-assets": "Delete assets",
  230 + "unassign-assets": "Unassign assets",
  231 + "unassign-assets-action-title": "Unassign { count, select, 1 {1 asset} other {# assets} } from customer",
  232 + "assign-new-asset": "Assign new asset",
  233 + "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?",
  234 + "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.",
  235 + "delete-assets-title": "Are you sure you want to delete { count, select, 1 {1 asset} other {# assets} }?",
  236 + "delete-assets-action-title": "Delete { count, select, 1 {1 asset} other {# assets} }",
  237 + "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.",
  238 + "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?",
  239 + "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.",
  240 + "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?",
  241 + "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.",
  242 + "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?",
  243 + "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.",
  244 + "unassign-asset": "Unassign asset",
  245 + "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
  246 + "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
  247 + "copyId": "Copy asset Id",
  248 + "idCopiedMessage": "Asset Id has been copied to clipboard",
  249 + "select-asset": "Select asset",
  250 + "no-assets-matching": "No assets matching '{{entity}}' were found.",
  251 + "asset-required": "Asset is required",
  252 + "name-starts-with": "Asset name starts with"
100 }, 253 },
101 "attribute": { 254 "attribute": {
102 - "attributes": "Atributos",  
103 - "latest-telemetry": "Última telemetría",  
104 - "attributes-scope": "Alcance de los atributos del dispositivo",  
105 - "scope-latest-telemetry": "Última telemetría",  
106 - "scope-client": "Atributos del Cliente",  
107 - "scope-server": "Atributos del Servidor",  
108 - "scope-shared": "Atributos Compartidos",  
109 - "add": "Agregar atributo",  
110 - "key": "Clave",  
111 - "key-required": "Clave del atributo requerida.",  
112 - "value": "Valor",  
113 - "value-required": "Valor del atributo requerido.",  
114 - "delete-attributes-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 atributo} other {# atributos} }?",  
115 - "delete-attributes-text": "Ten cuidado, luego de confirmar el atributo será eliminado, y la información relacionada será irrecuperable.",  
116 - "delete-attributes": "Borrar atributo",  
117 - "enter-attribute-value": "Ingresar valor del atributo",  
118 - "show-on-widget": "Mostrar en Widget",  
119 - "widget-mode": "Widget",  
120 - "next-widget": "Widget siguiente",  
121 - "prev-widget": "Widget anterior",  
122 - "add-to-dashboard": "Agregar al Panel",  
123 - "add-widget-to-dashboard": "Agregar widget al Panel",  
124 - "selected-attributes": "{ count, select, 1 {1 atributo} other {# atributos} } seleccionados",  
125 - "selected-telemetry": "{ count, select, 1 {1 unidad de telemetría } other {# unidades de telemetría} } seleccionadas." 255 + "attributes": "Atributos",
  256 + "latest-telemetry": "Última telemetría",
  257 + "attributes-scope": "Alcance de los atributos del dispositivo",
  258 + "scope-latest-telemetry": "Última telemetría",
  259 + "scope-client": "Atributos del Cliente",
  260 + "scope-server": "Atributos del Servidor",
  261 + "scope-shared": "Atributos Compartidos",
  262 + "add": "Agregar atributo",
  263 + "key": "Clave",
  264 + "key-required": "Clave del atributo requerida.",
  265 + "value": "Valor",
  266 + "value-required": "Valor del atributo requerido.",
  267 + "delete-attributes-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 atributo} other {# atributos} }?",
  268 + "delete-attributes-text": "Ten cuidado, luego de confirmar el atributo será eliminado, y la información relacionada será irrecuperable.",
  269 + "delete-attributes": "Borrar atributo",
  270 + "enter-attribute-value": "Ingresar valor del atributo",
  271 + "show-on-widget": "Mostrar en Widget",
  272 + "widget-mode": "Widget",
  273 + "next-widget": "Widget siguiente",
  274 + "prev-widget": "Widget anterior",
  275 + "add-to-dashboard": "Agregar al Panel",
  276 + "add-widget-to-dashboard": "Agregar widget al Panel",
  277 + "selected-attributes": "{ count, select, 1 {1 atributo} other {# atributos} } seleccionados",
  278 + "selected-telemetry": "{ count, select, 1 {1 unidad de telemetría } other {# unidades de telemetría} } seleccionadas."
  279 + },
  280 + "audit-log": { // TODO
  281 + "audit": "Audit",
  282 + "audit-logs": "Audit Logs",
  283 + "timestamp": "Timestamp",
  284 + "entity-type": "Entity Type",
  285 + "entity-name": "Entity Name",
  286 + "user": "User",
  287 + "type": "Type",
  288 + "status": "Status",
  289 + "details": "Details",
  290 + "type-added": "Added",
  291 + "type-deleted": "Deleted",
  292 + "type-updated": "Updated",
  293 + "type-attributes-updated": "Attributes updated",
  294 + "type-attributes-deleted": "Attributes deleted",
  295 + "type-rpc-call": "RPC call",
  296 + "type-credentials-updated": "Credentials updated",
  297 + "type-assigned-to-customer": "Assigned to Customer",
  298 + "type-unassigned-from-customer": "Unassigned from Customer",
  299 + "type-activated": "Activated",
  300 + "type-suspended": "Suspended",
  301 + "type-credentials-read": "Credentials read",
  302 + "type-attributes-read": "Attributes read",
  303 + "status-success": "Success",
  304 + "status-failure": "Failure",
  305 + "audit-log-details": "Audit log details",
  306 + "no-audit-logs-prompt": "No logs found",
  307 + "action-data": "Action data",
  308 + "failure-details": "Failure details",
  309 + "search": "Search audit logs",
  310 + "clear-search": "Clear search"
126 }, 311 },
127 "confirm-on-exit": { 312 "confirm-on-exit": {
128 - "message": "Tienes cambios sin guardar. ¿Estás seguro que quieres abandonar la página?",  
129 - "html-message": "Tienes cambios sin guardar.<br/>¿Estás seguro que quieres abandonar la página?",  
130 - "title": "Cambios sin guardar" 313 + "message": "Tienes cambios sin guardar. ¿Estás seguro que quieres abandonar la página?",
  314 + "html-message": "Tienes cambios sin guardar.<br/>¿Estás seguro que quieres abandonar la página?",
  315 + "title": "Cambios sin guardar"
131 }, 316 },
132 "contact": { 317 "contact": {
133 - "country": "País",  
134 - "city": "Ciudad",  
135 - "state": "Estado/Provincia",  
136 - "postal-code": "Código Postal",  
137 - "postal-code-invalid": "Solo se permiten dígitos.",  
138 - "address": "Dirección",  
139 - "address2": "Dirección 2",  
140 - "phone": "Teléfono",  
141 - "email": "Email",  
142 - "no-address": "Sin Dirección" 318 + "country": "País",
  319 + "city": "Ciudad",
  320 + "state": "Estado/Provincia",
  321 + "postal-code": "Código Postal",
  322 + "postal-code-invalid": "Solo se permiten dígitos.",
  323 + "address": "Dirección",
  324 + "address2": "Dirección 2",
  325 + "phone": "Teléfono",
  326 + "email": "Email",
  327 + "no-address": "Sin Dirección"
143 }, 328 },
144 "common": { 329 "common": {
145 - "username": "Usuario",  
146 - "password": "Contraseña",  
147 - "enter-username": "Ingresa el nombre de usuario.",  
148 - "enter-password": "Ingresa la contraseña",  
149 - "enter-search": "Ingresa búsqueda" 330 + "username": "Usuario",
  331 + "password": "Contraseña",
  332 + "enter-username": "Ingresa el nombre de usuario.",
  333 + "enter-password": "Ingresa la contraseña",
  334 + "enter-search": "Ingresa búsqueda"
  335 + },
  336 + "content-type": { // TODO
  337 + "json": "Json",
  338 + "text": "Text",
  339 + "binary": "Binary (Base64)"
150 }, 340 },
151 "customer": { 341 "customer": {
152 - "customers": "Clientes",  
153 - "management": "Gestión de Clientes",  
154 - "dashboard": "Panel del Cliente",  
155 - "dashboards": "Paneles del Cliente",  
156 - "devices": "Panel del Cliente",  
157 - "public-dashboards": "Paneles Públicos",  
158 - "public-devices": "Dispositivos Públicos",  
159 - "add": "Agregar cliente",  
160 - "delete": "Borrar cliente",  
161 - "manage-customer-users": "Gestionar usuarios del cliente",  
162 - "manage-customer-devices": "Gestionar dispositivos del cliente",  
163 - "manage-customer-dashboards": "Gestionar paneles del cliente",  
164 - "manage-public-devices": "Gestionar dispositivos públicos",  
165 - "manage-public-dashboards": "Gestionar paneles públicos",  
166 - "add-customer-text": "Agregar nuevo cliente",  
167 - "no-customers-text": "No se encontrar clientes",  
168 - "customer-details": "Detalles del cliente",  
169 - "delete-customer-title": "¿Estás seguro que quieres eliminar el cliente '{{customerTitle}}'?",  
170 - "delete-customer-text": "Ten cuidado, luego de confirmar el cliente será eliminado y toda la información relacionada será irrecuperable.",  
171 - "delete-customers-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 cliente} other {# clientes} }?",  
172 - "delete-customers-action-title": "Borrar { count, select, 1 {1 cliente} other {# clientes} }",  
173 - "delete-customers-text": "Ten cuidado, luego de confirmar todos los clientes seleccionados serán eliminados y su información relacionada será irrecuperable.",  
174 - "manage-users": "Gestionar usuarios",  
175 - "manage-devices": "Gestionar dispositivos",  
176 - "manage-dashboards": "Gestionar paneles",  
177 - "title": "Título",  
178 - "title-required": "Título requerido.",  
179 - "description": "Descripción" 342 + "customers": "Clientes",
  343 + "management": "Gestión de Clientes",
  344 + "dashboard": "Panel del Cliente",
  345 + "dashboards": "Paneles del Cliente",
  346 + "devices": "Panel del Cliente",
  347 + "public-dashboards": "Paneles Públicos",
  348 + "public-devices": "Dispositivos Públicos",
  349 + "add": "Agregar cliente",
  350 + "delete": "Borrar cliente",
  351 + "manage-customer-users": "Gestionar usuarios del cliente",
  352 + "manage-customer-devices": "Gestionar dispositivos del cliente",
  353 + "manage-customer-dashboards": "Gestionar paneles del cliente",
  354 + "manage-public-devices": "Gestionar dispositivos públicos",
  355 + "manage-public-dashboards": "Gestionar paneles públicos",
  356 + "add-customer-text": "Agregar nuevo cliente",
  357 + "no-customers-text": "No se encontrar clientes",
  358 + "customer-details": "Detalles del cliente",
  359 + "delete-customer-title": "¿Estás seguro que quieres eliminar el cliente '{{customerTitle}}'?",
  360 + "delete-customer-text": "Ten cuidado, luego de confirmar el cliente será eliminado y toda la información relacionada será irrecuperable.",
  361 + "delete-customers-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 cliente} other {# clientes} }?",
  362 + "delete-customers-action-title": "Borrar { count, select, 1 {1 cliente} other {# clientes} }",
  363 + "delete-customers-text": "Ten cuidado, luego de confirmar todos los clientes seleccionados serán eliminados y su información relacionada será irrecuperable.",
  364 + "manage-users": "Gestionar usuarios",
  365 + "manage-devices": "Gestionar dispositivos",
  366 + "manage-dashboards": "Gestionar paneles",
  367 + "title": "Título",
  368 + "title-required": "Título requerido.",
  369 + "description": "Descripción"
180 }, 370 },
181 "datetime": { 371 "datetime": {
182 - "date-from": "Fecha desde",  
183 - "time-from": "Tiempo desde",  
184 - "date-to": "Fecha hasta",  
185 - "time-to": "Tiempo hasta" 372 + "date-from": "Fecha desde",
  373 + "time-from": "Tiempo desde",
  374 + "date-to": "Fecha hasta",
  375 + "time-to": "Tiempo hasta"
186 }, 376 },
187 "dashboard": { 377 "dashboard": {
188 - "dashboard": "Panel",  
189 - "dashboards": "Paneles",  
190 - "management": "Gestión de Paneles",  
191 - "view-dashboards": "Ver paneles",  
192 - "add": "Agregar Panel",  
193 - "assign-dashboard-to-customer": "Asignar panel(es) a cliente",  
194 - "assign-dashboard-to-customer-text": "Por favor, seleccione algún panel para asignar al Cliente.",  
195 - "assign-to-customer-text": "Por favor, seleccione algún cliente para asignar al(los) panel(es).",  
196 - "assign-to-customer": "Asignar a cliente",  
197 - "unassign-from-customer": "Desasignar del cliente",  
198 - "make-public": "Hacer panel público",  
199 - "make-private": "Hacer panel privado",  
200 - "no-dashboards-text": "Ningún panel encontrado",  
201 - "no-widgets": "Ningún widget configurado",  
202 - "add-widget": "Agregar nuevo widget",  
203 - "title": "Titulo",  
204 - "select-widget-title": "Seleccionar widget",  
205 - "select-widget-subtitle": "Lista de tipos de widgets",  
206 - "delete": "Eliminar panel",  
207 - "title-required": "Título requerido.",  
208 - "description": "Descripción",  
209 - "details": "Detalles",  
210 - "dashboard-details": "Detalles del panel",  
211 - "add-dashboard-text": "Agregar nuevo panel",  
212 - "assign-dashboards": "Asignar paneles",  
213 - "assign-new-dashboard": "Asignar nuevo panel",  
214 - "assign-dashboards-text": "Asignar { count, select, 1 {1 panel} other {# paneles} } al cliente",  
215 - "delete-dashboards": "Eliminar paneles",  
216 - "unassign-dashboards": "Desasignar paneles",  
217 - "unassign-dashboards-action-title": "Desasignar { count, select, 1 {1 paneles} other {# paneles} } del cliente",  
218 - "delete-dashboard-title": "¿Estás seguro que quieres eliminar el panel '{{dashboardTitle}}'?",  
219 - "delete-dashboard-text": "Ten cuidado, el panel seleccionado será eliminado y la información relacionada sera irrecuperable.",  
220 - "delete-dashboards-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 panel} other {# paneles} }?",  
221 - "delete-dashboards-action-title": "Eliminar { count, select, 1 {1 panel} other {# paneles} }",  
222 - "delete-dashboards-text": "Ten cuidado, los paneles seleccionados serán eliminados y la información relacionada será irrecuperable.",  
223 - "unassign-dashboard-title": "¿Estás seguro que quieres desasignar el panel '{{dashboardTitle}}'?",  
224 - "unassign-dashboard-text": "Luego de confirmar, el panel será desasignado y no podrá ser accesible por el cliente.",  
225 - "unassign-dashboard": "Desasignar panel",  
226 - "unassign-dashboards-title": "¿Estás seguro que quieres desasignar { count, select, 1 {1 panel} other {# paneles} }?",  
227 - "unassign-dashboards-text": "Luego de confirmar, los paneles seleccionados serán desasignados y no podrán ser accesibles por el cliente.",  
228 - "public-dashboard-title": "El panel ahora es público",  
229 - "public-dashboard-text": "Tu panel <b>{{dashboardTitle}}</b> es ahora público y podrá ser accedido desde: <a href='{{publicLink}}' target='_blank'>aquí</a>:",  
230 - "public-dashboard-notice": "<b>Nota:</b> No olvides hacer públicos los dispositivos relacionados para acceder a sus datos.",  
231 - "make-private-dashboard-title": "¿Estás seguro que quieres hacer el panel '{{dashboardTitle}}' privado?",  
232 - "make-private-dashboard-text": "Luego de confirmar, el panel será privado y no podrá ser accesible por otros.",  
233 - "make-private-dashboard": "Hacer panel privado",  
234 - "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",  
235 - "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",  
236 - "select-dashboard": "Seleccionar panel",  
237 - "no-dashboards-matching": "Panel '{{entity}}' no encontrado.",  
238 - "dashboard-required": "Panel requerido.",  
239 - "select-existing": "Seleccionar paneles existentes",  
240 - "create-new": "Crear nuevo panel",  
241 - "new-dashboard-title": "Nuevo título",  
242 - "open-dashboard": "Abrir panel",  
243 - "set-background": "Definir fondo",  
244 - "background-color": "Color de fondo",  
245 - "background-image": "Imagen de fondo",  
246 - "background-size-mode": "Modo tamaño de fondo",  
247 - "no-image": "No se ha seleccionado ningúna imagen",  
248 - "drop-image": "Suelte una imagen o haga clic para seleccionar un archivo para cargar.",  
249 - "settings": "Ajustes",  
250 - "columns-count": "Número de columnas",  
251 - "columns-count-required": "Número de columnas requerido.",  
252 - "min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.",  
253 - "max-columns-count-message": "Solo se permite un número máximo de 1000 columnas.",  
254 - "widgets-margins": "Margen entre widgets",  
255 - "horizontal-margin": "Margen horizontal",  
256 - "horizontal-margin-required": "Margen horizontal requerido.",  
257 - "min-horizontal-margin-message": "Solo se permite margen horizontal mínimo de 0.",  
258 - "max-horizontal-margin-message": "Solo se permite margen horizontal máximo de 50.",  
259 - "vertical-margin": "Margen vertical",  
260 - "vertical-margin-required": "Margen vertical requerido.",  
261 - "min-vertical-margin-message": "Solo se permite margen vertical mínimo de 0.",  
262 - "max-vertical-margin-message": "Solo se permite margen vertical máximo de 50.",  
263 - "display-title": "Mostrar título del panel",  
264 - "title-color": "Color del título",  
265 - "display-device-selection": "Mostrar selección de dispositivo",  
266 - "display-dashboard-timewindow": "Mostrar ventana de tiempo",  
267 - "display-dashboard-export": "Mostrar exportar",  
268 - "import": "Importar panel",  
269 - "export": "Exportar panel",  
270 - "export-failed-error": "Imposible exportar panel: {{error}}",  
271 - "create-new-dashboard": "Crear nuevo panel",  
272 - "dashboard-file": "Archivo del panel",  
273 - "invalid-dashboard-file-error": "Imposible importar panel: Estructura de datos inválida.",  
274 - "dashboard-import-missing-aliases-title": "Configurar alias utilizados por el panel importado",  
275 - "create-new-widget": "Crear nuevo widget",  
276 - "import-widget": "Importar widget",  
277 - "widget-file": "Archivo de widget",  
278 - "invalid-widget-file-error": "Imposible importar widget: Estructura de datos inválida.",  
279 - "widget-import-missing-aliases-title": "Configurar alias utilizados por el widget",  
280 - "open-toolbar": "Abrir toolbar del panel",  
281 - "close-toolbar": "Cerrar toolbar",  
282 - "configuration-error": "Error de configuración",  
283 - "alias-resolution-error-title": "Error de configuración de alias del panel",  
284 - "invalid-aliases-config": "No se puede encontrar ningún dispositivo que coincida con algunos de los alias de filtro.<br/>" +  
285 - "Póngase en contacto con su administrador para resolver este problema.",  
286 - "select-devices": "Seleccionar dispositivos",  
287 - "assignedToCustomer": "Asignado al cliente",  
288 - "public": "Público",  
289 - "public-link": "Link público",  
290 - "copy-public-link": "Copiar link público",  
291 - "public-link-copied-message": "El link público del panel se ha copiado al portapapeles" 378 + "dashboard": "Panel",
  379 + "dashboards": "Paneles",
  380 + "management": "Gestión de Paneles",
  381 + "view-dashboards": "Ver paneles",
  382 + "add": "Agregar Panel",
  383 + "assign-dashboard-to-customer": "Asignar panel(es) a cliente",
  384 + "assign-dashboard-to-customer-text": "Por favor, seleccione algún panel para asignar al Cliente.",
  385 + "assign-to-customer-text": "Por favor, seleccione algún cliente para asignar al(los) panel(es).",
  386 + "assign-to-customer": "Asignar a cliente",
  387 + "unassign-from-customer": "Desasignar del cliente",
  388 + "make-public": "Hacer panel público",
  389 + "make-private": "Hacer panel privado",
  390 + "no-dashboards-text": "Ningún panel encontrado",
  391 + "no-widgets": "Ningún widget configurado",
  392 + "add-widget": "Agregar nuevo widget",
  393 + "title": "Titulo",
  394 + "select-widget-title": "Seleccionar widget",
  395 + "select-widget-subtitle": "Lista de tipos de widgets",
  396 + "delete": "Eliminar panel",
  397 + "title-required": "Título requerido.",
  398 + "description": "Descripción",
  399 + "details": "Detalles",
  400 + "dashboard-details": "Detalles del panel",
  401 + "add-dashboard-text": "Agregar nuevo panel",
  402 + "assign-dashboards": "Asignar paneles",
  403 + "assign-new-dashboard": "Asignar nuevo panel",
  404 + "assign-dashboards-text": "Asignar { count, select, 1 {1 panel} other {# paneles} } al cliente",
  405 + "delete-dashboards": "Eliminar paneles",
  406 + "unassign-dashboards": "Desasignar paneles",
  407 + "unassign-dashboards-action-title": "Desasignar { count, select, 1 {1 paneles} other {# paneles} } del cliente",
  408 + "delete-dashboard-title": "¿Estás seguro que quieres eliminar el panel '{{dashboardTitle}}'?",
  409 + "delete-dashboard-text": "Ten cuidado, el panel seleccionado será eliminado y la información relacionada sera irrecuperable.",
  410 + "delete-dashboards-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 panel} other {# paneles} }?",
  411 + "delete-dashboards-action-title": "Eliminar { count, select, 1 {1 panel} other {# paneles} }",
  412 + "delete-dashboards-text": "Ten cuidado, los paneles seleccionados serán eliminados y la información relacionada será irrecuperable.",
  413 + "unassign-dashboard-title": "¿Estás seguro que quieres desasignar el panel '{{dashboardTitle}}'?",
  414 + "unassign-dashboard-text": "Luego de confirmar, el panel será desasignado y no podrá ser accesible por el cliente.",
  415 + "unassign-dashboard": "Desasignar panel",
  416 + "unassign-dashboards-title": "¿Estás seguro que quieres desasignar { count, select, 1 {1 panel} other {# paneles} }?",
  417 + "unassign-dashboards-text": "Luego de confirmar, los paneles seleccionados serán desasignados y no podrán ser accesibles por el cliente.",
  418 + "public-dashboard-title": "El panel ahora es público",
  419 + "public-dashboard-text": "Tu panel <b>{{dashboardTitle}}</b> es ahora público y podrá ser accedido desde: <a href='{{publicLink}}' target='_blank'>aquí</a>:",
  420 + "public-dashboard-notice": "<b>Nota:</b> No olvides hacer públicos los dispositivos relacionados para acceder a sus datos.",
  421 + "make-private-dashboard-title": "¿Estás seguro que quieres hacer el panel '{{dashboardTitle}}' privado?",
  422 + "make-private-dashboard-text": "Luego de confirmar, el panel será privado y no podrá ser accesible por otros.",
  423 + "make-private-dashboard": "Hacer panel privado",
  424 + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
  425 + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
  426 + "select-dashboard": "Seleccionar panel",
  427 + "no-dashboards-matching": "Panel '{{entity}}' no encontrado.",
  428 + "dashboard-required": "Panel requerido.",
  429 + "select-existing": "Seleccionar paneles existentes",
  430 + "create-new": "Crear nuevo panel",
  431 + "new-dashboard-title": "Nuevo título",
  432 + "open-dashboard": "Abrir panel",
  433 + "set-background": "Definir fondo",
  434 + "background-color": "Color de fondo",
  435 + "background-image": "Imagen de fondo",
  436 + "background-size-mode": "Modo tamaño de fondo",
  437 + "no-image": "No se ha seleccionado ningúna imagen",
  438 + "drop-image": "Suelte una imagen o haga clic para seleccionar un archivo para cargar.",
  439 + "settings": "Ajustes",
  440 + "columns-count": "Número de columnas",
  441 + "columns-count-required": "Número de columnas requerido.",
  442 + "min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.",
  443 + "max-columns-count-message": "Solo se permite un número máximo de 1000 columnas.",
  444 + "widgets-margins": "Margen entre widgets",
  445 + "horizontal-margin": "Margen horizontal",
  446 + "horizontal-margin-required": "Margen horizontal requerido.",
  447 + "min-horizontal-margin-message": "Solo se permite margen horizontal mínimo de 0.",
  448 + "max-horizontal-margin-message": "Solo se permite margen horizontal máximo de 50.",
  449 + "vertical-margin": "Margen vertical",
  450 + "vertical-margin-required": "Margen vertical requerido.",
  451 + "min-vertical-margin-message": "Solo se permite margen vertical mínimo de 0.",
  452 + "max-vertical-margin-message": "Solo se permite margen vertical máximo de 50.",
  453 + "display-title": "Mostrar título del panel",
  454 + "title-color": "Color del título",
  455 + "display-device-selection": "Mostrar selección de dispositivo",
  456 + "display-dashboard-timewindow": "Mostrar ventana de tiempo",
  457 + "display-dashboard-export": "Mostrar exportar",
  458 + "import": "Importar panel",
  459 + "export": "Exportar panel",
  460 + "export-failed-error": "Imposible exportar panel: {{error}}",
  461 + "create-new-dashboard": "Crear nuevo panel",
  462 + "dashboard-file": "Archivo del panel",
  463 + "invalid-dashboard-file-error": "Imposible importar panel: Estructura de datos inválida.",
  464 + "dashboard-import-missing-aliases-title": "Configurar alias utilizados por el panel importado",
  465 + "create-new-widget": "Crear nuevo widget",
  466 + "import-widget": "Importar widget",
  467 + "widget-file": "Archivo de widget",
  468 + "invalid-widget-file-error": "Imposible importar widget: Estructura de datos inválida.",
  469 + "widget-import-missing-aliases-title": "Configurar alias utilizados por el widget",
  470 + "open-toolbar": "Abrir toolbar del panel",
  471 + "close-toolbar": "Cerrar toolbar",
  472 + "configuration-error": "Error de configuración",
  473 + "alias-resolution-error-title": "Error de configuración de alias del panel",
  474 + "invalid-aliases-config": "No se puede encontrar ningún dispositivo que coincida con algunos de los alias de filtro.<br/>" +
  475 + "Póngase en contacto con su administrador para resolver este problema.",
  476 + "select-devices": "Seleccionar dispositivos",
  477 + "assignedToCustomer": "Asignado al cliente",
  478 + "public": "Público",
  479 + "public-link": "Link público",
  480 + "copy-public-link": "Copiar link público",
  481 + "public-link-copied-message": "El link público del panel se ha copiado al portapapeles"
292 }, 482 },
293 "datakey": { 483 "datakey": {
294 - "settings": "Ajustes",  
295 - "advanced": "Avanzado",  
296 - "label": "Etiqueta",  
297 - "color": "Color",  
298 - "data-generation-func": "Función de generación de datos",  
299 - "use-data-post-processing-func": "Usar funcíon de post-procesamiendo de datos",  
300 - "configuration": "Ajustes de clave de datos",  
301 - "timeseries": "Serie de tiempos",  
302 - "attributes": "Atributos",  
303 - "timeseries-required": "Series de tiempo del dispositivo requerido.",  
304 - "timeseries-or-attributes-required": "Series de tiempo/Atributos requeridos.",  
305 - "function-types": "Tipos de funciones",  
306 - "function-types-required": "Tipos de funciones requerido." 484 + "settings": "Ajustes",
  485 + "advanced": "Avanzado",
  486 + "label": "Etiqueta",
  487 + "color": "Color",
  488 + "data-generation-func": "Función de generación de datos",
  489 + "use-data-post-processing-func": "Usar funcíon de post-procesamiendo de datos",
  490 + "configuration": "Ajustes de clave de datos",
  491 + "timeseries": "Serie de tiempos",
  492 + "attributes": "Atributos",
  493 + "timeseries-required": "Series de tiempo del dispositivo requerido.",
  494 + "timeseries-or-attributes-required": "Series de tiempo/Atributos requeridos.",
  495 + "function-types": "Tipos de funciones",
  496 + "function-types-required": "Tipos de funciones requerido."
307 }, 497 },
308 "datasource": { 498 "datasource": {
309 - "type": "Típo de fuente de datos",  
310 - "add-datasource-prompt": "Por favor, agrega una fuente de datos" 499 + "type": "Típo de fuente de datos",
  500 + "add-datasource-prompt": "Por favor, agrega una fuente de datos"
311 }, 501 },
312 "details": { 502 "details": {
313 - "edit-mode": "Modo Edición",  
314 - "toggle-edit-mode": "Ir a Modo Edición" 503 + "edit-mode": "Modo Edición",
  504 + "toggle-edit-mode": "Ir a Modo Edición"
315 }, 505 },
316 "device": { 506 "device": {
317 - "device": "Dispositivo",  
318 - "device-required": "Dispositivo requerido.",  
319 - "devices": "Dispositivos",  
320 - "management": "Gestión de Dispositivos",  
321 - "view-devices": "Ver dispositivos",  
322 - "device-alias": "Alias de dispositivo",  
323 - "aliases": "Alias de dispositivos",  
324 - "no-alias-matching": "'{{alias}}' no encontrado.",  
325 - "no-aliases-found": "Ningún alias encontrado.",  
326 - "no-key-matching": "'{{key}}' no encontrado.",  
327 - "no-keys-found": "Ninguna clave encontrada.",  
328 - "create-new-alias": "Crear nuevo alias!",  
329 - "create-new-key": "Crear nueva clave!",  
330 - "duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",  
331 - "configure-alias": "Configurar alias '{{alias}}'",  
332 - "no-devices-matching": "No se encontró dispositivo '{{entity}}'",  
333 - "alias": "Alias",  
334 - "alias-required": "Alias de dispositivo requerido.",  
335 - "remove-alias": "Eliminar alias",  
336 - "add-alias": "Agregar alias",  
337 - "name-starts-with": "Nombre empieza con",  
338 - "device-list": "Lista de dispositivos",  
339 - "use-device-name-filter": "Usar filtro",  
340 - "device-list-empty": "Ningún dispositivo seleccionado.",  
341 - "device-name-filter-required": "Nombre de filtro requerido.",  
342 - "device-name-filter-no-device-matched": "Ningún dispositivo encontrado que comience con '{{device}}'.",  
343 - "add": "Agregar dispositivo",  
344 - "assign-to-customer": "Asignar a cliente",  
345 - "assign-device-to-customer": "Asignar dispositivo(s) a Cliente",  
346 - "assign-device-to-customer-text": "Por favor, seleccione los dispositivos que serán asignados al cliente",  
347 - "make-public": "Hacer dispositivo público",  
348 - "make-private": "Hacer dispositivo privado",  
349 - "no-devices-text": "Ningún dispositivo encontrado",  
350 - "assign-to-customer-text": "Por favor, seleccione el cliente para asignar el(los) dispositivo(s)",  
351 - "device-details": "Detalles del dispositivo",  
352 - "add-device-text": "Agregar nuevo dispositivo",  
353 - "credentials": "Credenciales",  
354 - "manage-credentials": "Gestionar credenciales",  
355 - "delete": "Eliminar dispositivo",  
356 - "assign-devices": "Asignar dispositivo",  
357 - "assign-devices-text": "Asignar { count, select, 1 {1 dispositivo} other {# dispositivos} } al cliente",  
358 - "delete-devices": "Eliminar dispositivo",  
359 - "unassign-from-customer": "Desasignar del cliente",  
360 - "unassign-devices": "Desasignar dispositivos",  
361 - "unassign-devices-action-title": "Desasignar { count, select, 1 {1 dispositivo} other {# dispositivos} } del cliente",  
362 - "assign-new-device": "Asignar nuevo dispositivo",  
363 - "make-public-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' público?",  
364 - "make-public-device-text": "Luego de confirmar, el dispositivo y la información relacionada serán públicos y podrá ser accesible por otros.",  
365 - "make-private-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' privado?",  
366 - "make-private-device-text": "Luego de confirmar, el dispositivo y la información relacionada serán privados y no podrá ser accesible por otros.",  
367 - "view-credentials": "Ver credenciales",  
368 - "delete-device-title": "¿Estás seguro que quieres eliminar el dispositivo '{{deviceName}}'?",  
369 - "delete-device-text": "Ten cuidado, luego de confirmar los dispositivos serán eliminados y la información relacionada será irrecuperable.",  
370 - "delete-devices-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 dispositivo} other {# dispositivos} }?",  
371 - "delete-devices-action-title": "Eliminar { count, select, 1 {1 dispositivo} other {# dispositivos} }",  
372 - "delete-devices-text": "Ten cuidado, luego de confirmar los dispositivos seleccionados serán eliminados y la información relacionada será irrecuperable.",  
373 - "unassign-device-title": "¿Estás seguro que quieres desasignar el dispositivo '{{deviceName}}'?",  
374 - "unassign-device-text": "Luego de confirmar el dispositivo será desasignado y no podrá ser accesible por el cliente.",  
375 - "unassign-device": "Desasignar dispositivo",  
376 - "unassign-devices-title": "¿Estás seguro que quieres desasignar { count, select, 1 {1 dispositivo} other {# dispositivos} }?",  
377 - "unassign-devices-text": "Luego de confirmar los dispositivos seleccionados serán desasignados y no podrán ser accedidos por el cliente.",  
378 - "device-credentials": "Credenciales del dispositivo",  
379 - "credentials-type": "Tipo de credencial",  
380 - "access-token": "Access token",  
381 - "access-token-required": "Access token requerido.",  
382 - "access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.",  
383 - "rsa-key": "Clave pública RSA",  
384 - "rsa-key-required": "Clave pública RSA requerida.",  
385 - "secret": "Secreta",  
386 - "secret-required": "Secreta requerida.",  
387 - "name": "Nombre",  
388 - "name-required": "Nombre requerido.",  
389 - "description": "Descripción",  
390 - "events": "Eventos",  
391 - "details": "Detalles",  
392 - "copyId": "Copiar ID",  
393 - "copyAccessToken": "Copiar access token",  
394 - "idCopiedMessage": "Id del dispositivo copiado al portapapeles",  
395 - "accessTokenCopiedMessage": "Access token del dispositivo copiado al portapapeles",  
396 - "assignedToCustomer": "Asignado al cliente",  
397 - "unable-delete-device-alias-title": "Imposible eliminar alias del dispositivo",  
398 - "unable-delete-device-alias-text": "Alias '{{deviceAlias}}' no puede ser eliminado. Esta siendo usado por el(los) widget(s):<br/>{{widgetsList}}",  
399 - "is-gateway": "Es gateway",  
400 - "public": "Público",  
401 - "device-public": "Dispositivo público" 507 + "device": "Dispositivo",
  508 + "device-required": "Dispositivo requerido.",
  509 + "devices": "Dispositivos",
  510 + "management": "Gestión de Dispositivos",
  511 + "view-devices": "Ver dispositivos",
  512 + "device-alias": "Alias de dispositivo",
  513 + "aliases": "Alias de dispositivos",
  514 + "no-alias-matching": "'{{alias}}' no encontrado.",
  515 + "no-aliases-found": "Ningún alias encontrado.",
  516 + "no-key-matching": "'{{key}}' no encontrado.",
  517 + "no-keys-found": "Ninguna clave encontrada.",
  518 + "create-new-alias": "Crear nuevo alias!",
  519 + "create-new-key": "Crear nueva clave!",
  520 + "duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",
  521 + "configure-alias": "Configurar alias '{{alias}}'",
  522 + "no-devices-matching": "No se encontró dispositivo '{{entity}}'",
  523 + "alias": "Alias",
  524 + "alias-required": "Alias de dispositivo requerido.",
  525 + "remove-alias": "Eliminar alias",
  526 + "add-alias": "Agregar alias",
  527 + "name-starts-with": "Nombre empieza con",
  528 + "device-list": "Lista de dispositivos",
  529 + "use-device-name-filter": "Usar filtro",
  530 + "device-list-empty": "Ningún dispositivo seleccionado.",
  531 + "device-name-filter-required": "Nombre de filtro requerido.",
  532 + "device-name-filter-no-device-matched": "Ningún dispositivo encontrado que comience con '{{device}}'.",
  533 + "add": "Agregar dispositivo",
  534 + "assign-to-customer": "Asignar a cliente",
  535 + "assign-device-to-customer": "Asignar dispositivo(s) a Cliente",
  536 + "assign-device-to-customer-text": "Por favor, seleccione los dispositivos que serán asignados al cliente",
  537 + "make-public": "Hacer dispositivo público",
  538 + "make-private": "Hacer dispositivo privado",
  539 + "no-devices-text": "Ningún dispositivo encontrado",
  540 + "assign-to-customer-text": "Por favor, seleccione el cliente para asignar el(los) dispositivo(s)",
  541 + "device-details": "Detalles del dispositivo",
  542 + "add-device-text": "Agregar nuevo dispositivo",
  543 + "credentials": "Credenciales",
  544 + "manage-credentials": "Gestionar credenciales",
  545 + "delete": "Eliminar dispositivo",
  546 + "assign-devices": "Asignar dispositivo",
  547 + "assign-devices-text": "Asignar { count, select, 1 {1 dispositivo} other {# dispositivos} } al cliente",
  548 + "delete-devices": "Eliminar dispositivo",
  549 + "unassign-from-customer": "Desasignar del cliente",
  550 + "unassign-devices": "Desasignar dispositivos",
  551 + "unassign-devices-action-title": "Desasignar { count, select, 1 {1 dispositivo} other {# dispositivos} } del cliente",
  552 + "assign-new-device": "Asignar nuevo dispositivo",
  553 + "make-public-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' público?",
  554 + "make-public-device-text": "Luego de confirmar, el dispositivo y la información relacionada serán públicos y podrá ser accesible por otros.",
  555 + "make-private-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' privado?",
  556 + "make-private-device-text": "Luego de confirmar, el dispositivo y la información relacionada serán privados y no podrá ser accesible por otros.",
  557 + "view-credentials": "Ver credenciales",
  558 + "delete-device-title": "¿Estás seguro que quieres eliminar el dispositivo '{{deviceName}}'?",
  559 + "delete-device-text": "Ten cuidado, luego de confirmar los dispositivos serán eliminados y la información relacionada será irrecuperable.",
  560 + "delete-devices-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 dispositivo} other {# dispositivos} }?",
  561 + "delete-devices-action-title": "Eliminar { count, select, 1 {1 dispositivo} other {# dispositivos} }",
  562 + "delete-devices-text": "Ten cuidado, luego de confirmar los dispositivos seleccionados serán eliminados y la información relacionada será irrecuperable.",
  563 + "unassign-device-title": "¿Estás seguro que quieres desasignar el dispositivo '{{deviceName}}'?",
  564 + "unassign-device-text": "Luego de confirmar el dispositivo será desasignado y no podrá ser accesible por el cliente.",
  565 + "unassign-device": "Desasignar dispositivo",
  566 + "unassign-devices-title": "¿Estás seguro que quieres desasignar { count, select, 1 {1 dispositivo} other {# dispositivos} }?",
  567 + "unassign-devices-text": "Luego de confirmar los dispositivos seleccionados serán desasignados y no podrán ser accedidos por el cliente.",
  568 + "device-credentials": "Credenciales del dispositivo",
  569 + "credentials-type": "Tipo de credencial",
  570 + "access-token": "Access token",
  571 + "access-token-required": "Access token requerido.",
  572 + "access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.",
  573 + "rsa-key": "Clave pública RSA",
  574 + "rsa-key-required": "Clave pública RSA requerida.",
  575 + "secret": "Secreta",
  576 + "secret-required": "Secreta requerida.",
  577 + "name": "Nombre",
  578 + "name-required": "Nombre requerido.",
  579 + "description": "Descripción",
  580 + "events": "Eventos",
  581 + "details": "Detalles",
  582 + "copyId": "Copiar ID",
  583 + "copyAccessToken": "Copiar access token",
  584 + "idCopiedMessage": "Id del dispositivo copiado al portapapeles",
  585 + "accessTokenCopiedMessage": "Access token del dispositivo copiado al portapapeles",
  586 + "assignedToCustomer": "Asignado al cliente",
  587 + "unable-delete-device-alias-title": "Imposible eliminar alias del dispositivo",
  588 + "unable-delete-device-alias-text": "Alias '{{deviceAlias}}' no puede ser eliminado. Esta siendo usado por el(los) widget(s):<br/>{{widgetsList}}",
  589 + "is-gateway": "Es gateway",
  590 + "public": "Público",
  591 + "device-public": "Dispositivo público"
402 }, 592 },
403 "dialog": { 593 "dialog": {
404 - "close": "Cerrar cuadro de diálogo" 594 + "close": "Cerrar cuadro de diálogo"
405 }, 595 },
406 "error": { 596 "error": {
407 - "unable-to-connect": "Imposible conectar con el servidor! Por favor, revise su conexión a internet.",  
408 - "unhandled-error-code": "Código de error no manejado: {{errorCode}}",  
409 - "unknown-error": "Error desconocido" 597 + "unable-to-connect": "Imposible conectar con el servidor! Por favor, revise su conexión a internet.",
  598 + "unhandled-error-code": "Código de error no manejado: {{errorCode}}",
  599 + "unknown-error": "Error desconocido"
  600 + },
  601 + "entity": { // TODO
  602 + "entity": "Entity",
  603 + "entities": "Entities",
  604 + "aliases": "Entity aliases",
  605 + "entity-alias": "Entity alias",
  606 + "unable-delete-entity-alias-title": "Unable to delete entity alias",
  607 + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
  608 + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
  609 + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
  610 + "configure-alias": "Configure '{{alias}}' alias",
  611 + "alias": "Alias",
  612 + "alias-required": "Entity alias is required.",
  613 + "remove-alias": "Remove entity alias",
  614 + "add-alias": "Add entity alias",
  615 + "entity-list": "Entity list",
  616 + "entity-type": "Entity type",
  617 + "entity-types": "Entity types",
  618 + "entity-type-list": "Entity type list",
  619 + "any-entity": "Any entity",
  620 + "enter-entity-type": "Enter entity type",
  621 + "no-entities-matching": "No entities matching '{{entity}}' were found.",
  622 + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
  623 + "name-starts-with": "Name starts with",
  624 + "use-entity-name-filter": "Use filter",
  625 + "entity-list-empty": "No entities selected.",
  626 + "entity-type-list-empty": "No entity types selected.",
  627 + "entity-name-filter-required": "Entity name filter is required.",
  628 + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
  629 + "all-subtypes": "All",
  630 + "select-entities": "Select entities",
  631 + "no-aliases-found": "No aliases found.",
  632 + "no-alias-matching": "'{{alias}}' not found.",
  633 + "create-new-alias": "Create a new one!",
  634 + "key": "Key",
  635 + "key-name": "Key name",
  636 + "no-keys-found": "No keys found.",
  637 + "no-key-matching": "'{{key}}' not found.",
  638 + "create-new-key": "Create a new one!",
  639 + "type": "Type",
  640 + "type-required": "Entity type is required.",
  641 + "type-device": "Device",
  642 + "type-devices": "Devices",
  643 + "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
  644 + "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
  645 + "type-asset": "Asset",
  646 + "type-assets": "Assets",
  647 + "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
  648 + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
  649 + "type-rule": "Rule",
  650 + "type-rules": "Rules",
  651 + "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
  652 + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
  653 + "type-plugin": "Plugin",
  654 + "type-plugins": "Plugins",
  655 + "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
  656 + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
  657 + "type-tenant": "Tenant",
  658 + "type-tenants": "Tenants",
  659 + "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
  660 + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
  661 + "type-customer": "Customer",
  662 + "type-customers": "Customers",
  663 + "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
  664 + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
  665 + "type-user": "User",
  666 + "type-users": "Users",
  667 + "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
  668 + "user-name-starts-with": "Users whose names start with '{{prefix}}'",
  669 + "type-dashboard": "Dashboard",
  670 + "type-dashboards": "Dashboards",
  671 + "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
  672 + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
  673 + "type-alarm": "Alarm",
  674 + "type-alarms": "Alarms",
  675 + "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
  676 + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  677 + "type-rulechain": "Rule chain",
  678 + "type-rulechains": "Rule chains",
  679 + "list-of-rulechains": "{ count, select, 1 {One rule chain} other {List of # rule chains} }",
  680 + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
  681 + "type-current-customer": "Current Customer",
  682 + "search": "Search entities",
  683 + "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
  684 + "entity-name": "Entity name",
  685 + "details": "Entity details",
  686 + "no-entities-prompt": "No entities found",
  687 + "no-data": "No data to display"
410 }, 688 },
411 "event": { 689 "event": {
412 - "event-type": "Tipo de evento",  
413 - "type-error": "Error",  
414 - "type-lc-event": "Ciclo de vida",  
415 - "type-stats": "Estadísticas",  
416 - "no-events-prompt": "Ningún evento encontrado.",  
417 - "error": "Error",  
418 - "alarm": "Alarma",  
419 - "event-time": "Hora del evento",  
420 - "server": "Servidor",  
421 - "body": "Cuerpo",  
422 - "method": "Método",  
423 - "event": "Evento",  
424 - "status": "Status",  
425 - "success": "Éxito",  
426 - "failed": "Fallo",  
427 - "messages-processed": "Mensajes procesados",  
428 - "errors-occurred": "Ocurrieron errores" 690 + "event-type": "Tipo de evento",
  691 + "type-error": "Error",
  692 + "type-lc-event": "Ciclo de vida",
  693 + "type-stats": "Estadísticas",
  694 + "no-events-prompt": "Ningún evento encontrado.",
  695 + "error": "Error",
  696 + "alarm": "Alarma",
  697 + "event-time": "Hora del evento",
  698 + "server": "Servidor",
  699 + "body": "Cuerpo",
  700 + "method": "Método",
  701 + "event": "Evento",
  702 + "status": "Status",
  703 + "success": "Éxito",
  704 + "failed": "Fallo",
  705 + "messages-processed": "Mensajes procesados",
  706 + "errors-occurred": "Ocurrieron errores"
  707 + },
  708 + "extension": { // TODO
  709 + "extensions": "Extensions",
  710 + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
  711 + "type": "Type",
  712 + "key": "Key",
  713 + "value": "Value",
  714 + "id": "Id",
  715 + "extension-id": "Extension id",
  716 + "extension-type": "Extension type",
  717 + "transformer-json": "JSON *",
  718 + "unique-id-required": "Current extension id already exists.",
  719 + "delete": "Delete extension",
  720 + "add": "Add extension",
  721 + "edit": "Edit extension",
  722 + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
  723 + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
  724 + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
  725 + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
  726 + "converters": "Converters",
  727 + "converter-id": "Converter id",
  728 + "configuration": "Configuration",
  729 + "converter-configurations": "Converter configurations",
  730 + "token": "Security token",
  731 + "add-converter": "Add converter",
  732 + "add-config": "Add converter configuration",
  733 + "device-name-expression": "Device name expression",
  734 + "device-type-expression": "Device type expression",
  735 + "custom": "Custom",
  736 + "to-double": "To Double",
  737 + "transformer": "Transformer",
  738 + "json-required": "Transformer json is required.",
  739 + "json-parse": "Unable to parse transformer json.",
  740 + "attributes": "Attributes",
  741 + "add-attribute": "Add attribute",
  742 + "add-map": "Add mapping element",
  743 + "timeseries": "Timeseries",
  744 + "add-timeseries": "Add timeseries",
  745 + "field-required": "Field is required",
  746 + "brokers": "Brokers",
  747 + "add-broker": "Add broker",
  748 + "host": "Host",
  749 + "port": "Port",
  750 + "port-range": "Port should be in a range from 1 to 65535.",
  751 + "ssl": "Ssl",
  752 + "credentials": "Credentials",
  753 + "username": "Username",
  754 + "password": "Password",
  755 + "retry-interval": "Retry interval in milliseconds",
  756 + "anonymous": "Anonymous",
  757 + "basic": "Basic",
  758 + "pem": "PEM",
  759 + "ca-cert": "CA certificate file *",
  760 + "private-key": "Private key file *",
  761 + "cert": "Certificate file *",
  762 + "no-file": "No file selected.",
  763 + "drop-file": "Drop a file or click to select a file to upload.",
  764 + "mapping": "Mapping",
  765 + "topic-filter": "Topic filter",
  766 + "converter-type": "Converter type",
  767 + "converter-json": "Json",
  768 + "json-name-expression": "Device name json expression",
  769 + "topic-name-expression": "Device name topic expression",
  770 + "json-type-expression": "Device type json expression",
  771 + "topic-type-expression": "Device type topic expression",
  772 + "attribute-key-expression": "Attribute key expression",
  773 + "attr-json-key-expression": "Attribute key json expression",
  774 + "attr-topic-key-expression": "Attribute key topic expression",
  775 + "request-id-expression": "Request id expression",
  776 + "request-id-json-expression": "Request id json expression",
  777 + "request-id-topic-expression": "Request id topic expression",
  778 + "response-topic-expression": "Response topic expression",
  779 + "value-expression": "Value expression",
  780 + "topic": "Topic",
  781 + "timeout": "Timeout in milliseconds",
  782 + "converter-json-required": "Converter json is required.",
  783 + "converter-json-parse": "Unable to parse converter json.",
  784 + "filter-expression": "Filter expression",
  785 + "connect-requests": "Connect requests",
  786 + "add-connect-request": "Add connect request",
  787 + "disconnect-requests": "Disconnect requests",
  788 + "add-disconnect-request": "Add disconnect request",
  789 + "attribute-requests": "Attribute requests",
  790 + "add-attribute-request": "Add attribute request",
  791 + "attribute-updates": "Attribute updates",
  792 + "add-attribute-update": "Add attribute update",
  793 + "server-side-rpc": "Server side RPC",
  794 + "add-server-side-rpc-request": "Add server-side RPC request",
  795 + "device-name-filter": "Device name filter",
  796 + "attribute-filter": "Attribute filter",
  797 + "method-filter": "Method filter",
  798 + "request-topic-expression": "Request topic expression",
  799 + "response-timeout": "Response timeout in milliseconds",
  800 + "topic-expression": "Topic expression",
  801 + "client-scope": "Client scope",
  802 + "add-device": "Add device",
  803 + "opc-server": "Servers",
  804 + "opc-add-server": "Add server",
  805 + "opc-add-server-prompt": "Please add server",
  806 + "opc-application-name": "Application name",
  807 + "opc-application-uri": "Application uri",
  808 + "opc-scan-period-in-seconds": "Scan period in seconds",
  809 + "opc-security": "Security",
  810 + "opc-identity": "Identity",
  811 + "opc-keystore": "Keystore",
  812 + "opc-type": "Type",
  813 + "opc-keystore-type": "Type",
  814 + "opc-keystore-location": "Location *",
  815 + "opc-keystore-password": "Password",
  816 + "opc-keystore-alias": "Alias",
  817 + "opc-keystore-key-password": "Key password",
  818 + "opc-device-node-pattern": "Device node pattern",
  819 + "opc-device-name-pattern": "Device name pattern",
  820 + "modbus-server": "Servers/slaves",
  821 + "modbus-add-server": "Add server/slave",
  822 + "modbus-add-server-prompt": "Please add server/slave",
  823 + "modbus-transport": "Transport",
  824 + "modbus-port-name": "Serial port name",
  825 + "modbus-encoding": "Encoding",
  826 + "modbus-parity": "Parity",
  827 + "modbus-baudrate": "Baud rate",
  828 + "modbus-databits": "Data bits",
  829 + "modbus-stopbits": "Stop bits",
  830 + "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
  831 + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
  832 + "modbus-unit-id": "Unit ID",
  833 + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
  834 + "modbus-device-name": "Device name",
  835 + "modbus-poll-period": "Poll period (ms)",
  836 + "modbus-attributes-poll-period": "Attributes poll period (ms)",
  837 + "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
  838 + "modbus-poll-period-range": "Poll period should be positive value.",
  839 + "modbus-tag": "Tag",
  840 + "modbus-function": "Function",
  841 + "modbus-register-address": "Register address",
  842 + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
  843 + "modbus-register-bit-index": "Bit index",
  844 + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
  845 + "modbus-register-count": "Register count",
  846 + "modbus-register-count-range": "Register count should be a positive value.",
  847 + "modbus-byte-order": "Byte order",
  848 +
  849 + "sync": {
  850 + "status": "Status",
  851 + "sync": "Sync",
  852 + "not-sync": "Not sync",
  853 + "last-sync-time": "Last sync time",
  854 + "not-available": "Not available"
  855 + },
  856 +
  857 + "export-extensions-configuration": "Export extensions configuration",
  858 + "import-extensions-configuration": "Import extensions configuration",
  859 + "import-extensions": "Import extensions",
  860 + "import-extension": "Import extension",
  861 + "export-extension": "Export extension",
  862 + "file": "Extensions file",
  863 + "invalid-file-error": "Invalid extension file"
429 }, 864 },
430 "fullscreen": { 865 "fullscreen": {
431 - "expand": "Expandir a Pantalla Completa",  
432 - "exit": "Salir de Pantalla Completa",  
433 - "toggle": "Cambiar el modo de Pantalla Completa",  
434 - "fullscreen": "Pantalla Completa" 866 + "expand": "Expandir a Pantalla Completa",
  867 + "exit": "Salir de Pantalla Completa",
  868 + "toggle": "Cambiar el modo de Pantalla Completa",
  869 + "fullscreen": "Pantalla Completa"
435 }, 870 },
436 "function": { 871 "function": {
437 - "function": "Función" 872 + "function": "Función"
438 }, 873 },
439 "grid": { 874 "grid": {
440 - "delete-item-title": "¿Estás seguro que quieres eliminar este item?",  
441 - "delete-item-text": "Ten cuidado, luego de confirmar el item será eliminado y la información relacionada será irrecuperable.",  
442 - "delete-items-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 item} other {# items} }?",  
443 - "delete-items-action-title": "Eliminar { count, select, 1 {1 item} other {# items} }",  
444 - "delete-items-text": "Ten cuidado, luego de confirmar los items seleccionados serán eliminados y la información relacionada será irrecuperable.",  
445 - "add-item-text": "Agregar nuevo item",  
446 - "no-items-text": "Ningún item encontrado",  
447 - "item-details": "Detalles del item",  
448 - "delete-item": "Borrar Item",  
449 - "delete-items": "Borrar Items",  
450 - "scroll-to-top": "Ir hacia arriba" 875 + "delete-item-title": "¿Estás seguro que quieres eliminar este item?",
  876 + "delete-item-text": "Ten cuidado, luego de confirmar el item será eliminado y la información relacionada será irrecuperable.",
  877 + "delete-items-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 item} other {# items} }?",
  878 + "delete-items-action-title": "Eliminar { count, select, 1 {1 item} other {# items} }",
  879 + "delete-items-text": "Ten cuidado, luego de confirmar los items seleccionados serán eliminados y la información relacionada será irrecuperable.",
  880 + "add-item-text": "Agregar nuevo item",
  881 + "no-items-text": "Ningún item encontrado",
  882 + "item-details": "Detalles del item",
  883 + "delete-item": "Borrar Item",
  884 + "delete-items": "Borrar Items",
  885 + "scroll-to-top": "Ir hacia arriba"
451 }, 886 },
452 "help": { 887 "help": {
453 - "goto-help-page": "Ir a Página de Ayuda" 888 + "goto-help-page": "Ir a Página de Ayuda"
454 }, 889 },
455 "home": { 890 "home": {
456 - "home": "Principal",  
457 - "profile": "Perfil",  
458 - "logout": "Salir",  
459 - "menu": "Menu",  
460 - "avatar": "Avatar",  
461 - "open-user-menu": "Abrir menú de usuario" 891 + "home": "Principal",
  892 + "profile": "Perfil",
  893 + "logout": "Salir",
  894 + "menu": "Menu",
  895 + "avatar": "Avatar",
  896 + "open-user-menu": "Abrir menú de usuario"
462 }, 897 },
463 "import": { 898 "import": {
464 - "no-file": "Ningún archivo seleccionado",  
465 - "drop-file": "Arrastra un archivo JSON o clickea para seleccionar uno." 899 + "no-file": "Ningún archivo seleccionado",
  900 + "drop-file": "Arrastra un archivo JSON o clickea para seleccionar uno."
466 }, 901 },
467 "item": { 902 "item": {
468 - "selected": "Seleccionado" 903 + "selected": "Seleccionado"
469 }, 904 },
470 "js-func": { 905 "js-func": {
471 - "no-return-error": "La función debe retornar un valor!",  
472 - "return-type-mismatch": "La función debe retornar un valor de tipo: '{{type}}'!" 906 + "no-return-error": "La función debe retornar un valor!",
  907 + "return-type-mismatch": "La función debe retornar un valor de tipo: '{{type}}'!"
  908 + },
  909 + "key-val": { // TODO
  910 + "key": "Key",
  911 + "value": "Value",
  912 + "remove-entry": "Remove entry",
  913 + "add-entry": "Add entry",
  914 + "no-data": "No entries"
  915 + },
  916 + "layout": { // TODO
  917 + "layout": "Layout",
  918 + "manage": "Manage layouts",
  919 + "settings": "Layout settings",
  920 + "color": "Color",
  921 + "main": "Main",
  922 + "right": "Right",
  923 + "select": "Select target layout"
473 }, 924 },
474 "legend": { 925 "legend": {
475 - "position": "Posición de leyenda",  
476 - "show-max": "Mostrar máximo",  
477 - "show-min": "Mostrar mínimo",  
478 - "show-avg": "Mostrar promedio",  
479 - "show-total": "Mostrar total",  
480 - "settings": "Ajustes de leyenda.",  
481 - "min": "min",  
482 - "max": "max",  
483 - "avg": "prom",  
484 - "total": "total" 926 + "position": "Posición de leyenda",
  927 + "show-max": "Mostrar máximo",
  928 + "show-min": "Mostrar mínimo",
  929 + "show-avg": "Mostrar promedio",
  930 + "show-total": "Mostrar total",
  931 + "settings": "Ajustes de leyenda.",
  932 + "min": "min",
  933 + "max": "max",
  934 + "avg": "prom",
  935 + "total": "total"
485 }, 936 },
486 "login": { 937 "login": {
487 - "login": "Ingresar",  
488 - "request-password-reset": "Pedir restablecer contraseña",  
489 - "reset-password": "Restablecer contraseña",  
490 - "create-password": "Crear contraseña",  
491 - "passwords-mismatch-error": "Las contraseñas deben ser las mismas!",  
492 - "password-again": "Reingresa la contraseña",  
493 - "sign-in": "Iniciar sesión",  
494 - "username": "Usuario (email)",  
495 - "remember-me": "Recordar",  
496 - "forgot-password": "¿Olvidaste tu contraseña?",  
497 - "password-reset": "Restablecer Contraseña",  
498 - "new-password": "Nueva contraseña",  
499 - "new-password-again": "Repita la nueva contraseña",  
500 - "password-link-sent-message": "Se ha enviado el enlace de restablecimiento de contraseña con éxito!",  
501 - "email": "Email"  
502 - },  
503 - "plugin": {  
504 - "plugins": "Plugins",  
505 - "delete": "Eliminar plugin",  
506 - "activate": "Activar plugin",  
507 - "suspend": "Suspender plugin",  
508 - "active": "Activo",  
509 - "suspended": "Suspendido",  
510 - "name": "Nombre",  
511 - "name-required": "Nombre requerido.",  
512 - "description": "Descripción",  
513 - "add": "Agregar Plugin",  
514 - "delete-plugin-title": "¿Estás seguro que quieres eliminar el plugin '{{pluginName}}'?",  
515 - "delete-plugin-text": "Ten cuidado, luego de confirmar el plugin será eliminado y la información relacionada será irrecuperable.",  
516 - "delete-plugins-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 plugin} other {# plugins} }?",  
517 - "delete-plugins-action-title": "Eliminar { count, select, 1 {1 plugin} other {# plugins} }",  
518 - "delete-plugins-text": "Ten cuidado, luego de confirmar todos los plugins seleccionados serán eliminados y la información relacionada será irrecuperable.",  
519 - "add-plugin-text": "Agregar nuevo plugin",  
520 - "no-plugins-text": "Ningún plugin encontrado",  
521 - "plugin-details": "Detalles",  
522 - "api-token": "API token",  
523 - "api-token-required": "API token requerido.",  
524 - "type": "Tipo del plugin",  
525 - "type-required": "Tipo requerido.",  
526 - "configuration": "Ajustes del plugin",  
527 - "system": "Sistema",  
528 - "select-plugin": "plugin",  
529 - "plugin": "Plugin",  
530 - "no-plugins-matching": "No se encontraron plugins: '{{entity}}'",  
531 - "plugin-required": "Plugin requerido.",  
532 - "plugin-require-match": "Por favor, elija un plugin existente.",  
533 - "events": "Eventos",  
534 - "details": "Detalles",  
535 - "import": "Importar plugin",  
536 - "export": "Exportar plugin",  
537 - "export-failed-error": "Imposible exportar plugin: {{error}}",  
538 - "create-new-plugin": "Crear nuevo plugin",  
539 - "plugin-file": "Archivo",  
540 - "invalid-plugin-file-error": "Imposible de importar plugin: Estructura de datos inválida." 938 + "login": "Ingresar",
  939 + "request-password-reset": "Pedir restablecer contraseña",
  940 + "reset-password": "Restablecer contraseña",
  941 + "create-password": "Crear contraseña",
  942 + "passwords-mismatch-error": "Las contraseñas deben ser las mismas!",
  943 + "password-again": "Reingresa la contraseña",
  944 + "sign-in": "Iniciar sesión",
  945 + "username": "Usuario (email)",
  946 + "remember-me": "Recordar",
  947 + "forgot-password": "¿Olvidaste tu contraseña?",
  948 + "password-reset": "Restablecer Contraseña",
  949 + "new-password": "Nueva contraseña",
  950 + "new-password-again": "Repita la nueva contraseña",
  951 + "password-link-sent-message": "Se ha enviado el enlace de restablecimiento de contraseña con éxito!",
  952 + "email": "Email"
541 }, 953 },
542 "position": { 954 "position": {
543 - "top": "Arriba",  
544 - "bottom": "Abajo",  
545 - "left": "Izquierda",  
546 - "right": "Derecha" 955 + "top": "Arriba",
  956 + "bottom": "Abajo",
  957 + "left": "Izquierda",
  958 + "right": "Derecha"
547 }, 959 },
548 "profile": { 960 "profile": {
549 - "profile": "Perfil",  
550 - "change-password": "Cambiar contraseña",  
551 - "current-password": "Contraseña actual"  
552 - },  
553 - "rule": {  
554 - "rules": "Reglas",  
555 - "delete": "Eliminar regla",  
556 - "activate": "Activar regla",  
557 - "suspend": "Suspender regla",  
558 - "active": "Activada",  
559 - "suspended": "Suspendida",  
560 - "name": "Nombre",  
561 - "name-required": "Nombre requerido.",  
562 - "description": "Descripción",  
563 - "add": "Agregar Regla",  
564 - "delete-rule-title": "¿Estás seguro que quieres eliminar la regla '{{ruleName}}'?",  
565 - "delete-rule-text": "Ten cuidado, luego de confirmar la regla será eliminada y la información relacionada será irrecuperable.",  
566 - "delete-rules-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 regla} other {# reglas} }?",  
567 - "delete-rules-action-title": "Eliminar { count, select, 1 {1 regla} other {# reglas} }",  
568 - "delete-rules-text": "Ten cuidado, luego de confirmar todas las reglas seleccionadas serán borradas y la información relacionada será irrecuperable.",  
569 - "add-rule-text": "Agregar nueva regla",  
570 - "no-rules-text": "Ninguna regla encontrada",  
571 - "rule-details": "Detalles",  
572 - "filters": "Filtros",  
573 - "filter": "Filtro",  
574 - "add-filter-prompt": "Por favor, ingresa un filtro",  
575 - "remove-filter": "Eliminar filtro",  
576 - "add-filter": "Agregar filtro",  
577 - "filter-name": "Nombre",  
578 - "filter-type": "Tipo",  
579 - "edit-filter": "Editar filtro",  
580 - "view-filter": "Ver filtro",  
581 - "component-name": "Nombre",  
582 - "component-name-required": "Nombre requerido.",  
583 - "component-type": "Tipo",  
584 - "component-type-required": "Tipo requerido.",  
585 - "processor": "Procesador",  
586 - "no-processor-configured": "Ningún procesador encontrado",  
587 - "create-processor": "Crear procesador",  
588 - "processor-name": "Nombre",  
589 - "processor-type": "Tipo",  
590 - "plugin-action": "Acción del Plugin",  
591 - "action-name": "Nombre",  
592 - "action-type": "Tipo",  
593 - "create-action-prompt": "Por favor, crea una acción.",  
594 - "create-action": "Crear acción",  
595 - "details": "Detalles",  
596 - "events": "Eventos",  
597 - "system": "Sistema",  
598 - "import": "Importar regla",  
599 - "export": "Exportar regla",  
600 - "export-failed-error": "Imposible de exportar regla: {{error}}",  
601 - "create-new-rule": "Crear nueva regla",  
602 - "rule-file": "Archivo",  
603 - "invalid-rule-file-error": "Imposible de importar regla: Estructura de datos inválida."  
604 - },  
605 - "rule-plugin": {  
606 - "management": "Gestión de Reglas y Plugins" 961 + "profile": "Perfil",
  962 + "change-password": "Cambiar contraseña",
  963 + "current-password": "Contraseña actual"
  964 + },
  965 + "relation": { // TODO
  966 + "relations": "Relations",
  967 + "direction": "Direction",
  968 + "search-direction": {
  969 + "FROM": "From",
  970 + "TO": "To"
  971 + },
  972 + "direction-type": {
  973 + "FROM": "from",
  974 + "TO": "to"
  975 + },
  976 + "from-relations": "Outbound relations",
  977 + "to-relations": "Inbound relations",
  978 + "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
  979 + "type": "Type",
  980 + "to-entity-type": "To entity type",
  981 + "to-entity-name": "To entity name",
  982 + "from-entity-type": "From entity type",
  983 + "from-entity-name": "From entity name",
  984 + "to-entity": "To entity",
  985 + "from-entity": "From entity",
  986 + "delete": "Delete relation",
  987 + "relation-type": "Relation type",
  988 + "relation-type-required": "Relation type is required.",
  989 + "any-relation-type": "Any type",
  990 + "add": "Add relation",
  991 + "edit": "Edit relation",
  992 + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
  993 + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
  994 + "delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  995 + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.",
  996 + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
  997 + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
  998 + "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  999 + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.",
  1000 + "remove-relation-filter": "Remove relation filter",
  1001 + "add-relation-filter": "Add relation filter",
  1002 + "any-relation": "Any relation",
  1003 + "relation-filters": "Relation filters",
  1004 + "additional-info": "Additional info (JSON)",
  1005 + "invalid-additional-info": "Unable to parse additional info json."
  1006 + },
  1007 + "rulechain": { // TODO
  1008 + "rulechain": "Rule chain",
  1009 + "rulechains": "Rule chains",
  1010 + "root": "Root",
  1011 + "delete": "Delete rule chain",
  1012 + "name": "Name",
  1013 + "name-required": "Name is required.",
  1014 + "description": "Description",
  1015 + "add": "Add Rule Chain",
  1016 + "set-root": "Make rule chain root",
  1017 + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
  1018 + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
  1019 + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
  1020 + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
  1021 + "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
  1022 + "delete-rulechains-action-title": "Delete { count, select, 1 {1 rule chain} other {# rule chains} }",
  1023 + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
  1024 + "add-rulechain-text": "Add new rule chain",
  1025 + "no-rulechains-text": "No rule chains found",
  1026 + "rulechain-details": "Rule chain details",
  1027 + "details": "Details",
  1028 + "events": "Events",
  1029 + "system": "System",
  1030 + "import": "Import rule chain",
  1031 + "export": "Export rule chain",
  1032 + "export-failed-error": "Unable to export rule chain: {{error}}",
  1033 + "create-new-rulechain": "Create new rule chain",
  1034 + "rulechain-file": "Rule chain file",
  1035 + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
  1036 + "copyId": "Copy rule chain Id",
  1037 + "idCopiedMessage": "Rule chain Id has been copied to clipboard",
  1038 + "select-rulechain": "Select rule chain",
  1039 + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
  1040 + "rulechain-required": "Rule chain is required",
  1041 + "management": "Rules management",
  1042 + "debug-mode": "Debug mode"
  1043 + },
  1044 + "rulenode": { // TODO
  1045 + "details": "Details",
  1046 + "events": "Events",
  1047 + "search": "Search nodes",
  1048 + "open-node-library": "Open node library",
  1049 + "add": "Add rule node",
  1050 + "name": "Name",
  1051 + "name-required": "Name is required.",
  1052 + "type": "Type",
  1053 + "description": "Description",
  1054 + "delete": "Delete rule node",
  1055 + "select-all-objects": "Select all nodes and connections",
  1056 + "deselect-all-objects": "Deselect all nodes and connections",
  1057 + "delete-selected-objects": "Delete selected nodes and connections",
  1058 + "delete-selected": "Delete selected",
  1059 + "select-all": "Select all",
  1060 + "copy-selected": "Copy selected",
  1061 + "deselect-all": "Deselect all",
  1062 + "rulenode-details": "Rule node details",
  1063 + "debug-mode": "Debug mode",
  1064 + "configuration": "Configuration",
  1065 + "link": "Link",
  1066 + "link-details": "Rule node link details",
  1067 + "add-link": "Add link",
  1068 + "link-label": "Link label",
  1069 + "link-label-required": "Link label is required.",
  1070 + "custom-link-label": "Custom link label",
  1071 + "custom-link-label-required": "Custom link label is required.",
  1072 + "type-filter": "Filter",
  1073 + "type-filter-details": "Filter incoming messages with configured conditions",
  1074 + "type-enrichment": "Enrichment",
  1075 + "type-enrichment-details": "Add additional information into Message Metadata",
  1076 + "type-transformation": "Transformation",
  1077 + "type-transformation-details": "Change Message payload and Metadata",
  1078 + "type-action": "Action",
  1079 + "type-action-details": "Perform special action",
  1080 + "type-external": "External",
  1081 + "type-external-details": "Interacts with external system",
  1082 + "type-rule-chain": "Rule Chain",
  1083 + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
  1084 + "type-input": "Input",
  1085 + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
  1086 + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
  1087 + "ui-resources-load-error": "Failed to load configuration ui resources.",
  1088 + "invalid-target-rulechain": "Unable to resolve target rule chain!",
  1089 + "test-script-function": "Test script function",
  1090 + "message": "Message",
  1091 + "message-type": "Message type",
  1092 + "message-type-required": "Message type is required",
  1093 + "metadata": "Metadata",
  1094 + "metadata-required": "Metadata entries can't be empty.",
  1095 + "output": "Output",
  1096 + "test": "Test",
  1097 + "help": "Help"
607 }, 1098 },
608 "tenant": { 1099 "tenant": {
609 - "tenants": "Tenants",  
610 - "management": "Gestión de Tenant",  
611 - "add": "Agregar Tenant",  
612 - "admins": "Admins",  
613 - "manage-tenant-admins": "Gestionar administradores tenant",  
614 - "delete": "Eliminar tenant",  
615 - "add-tenant-text": "Agregar nuevo tenant",  
616 - "no-tenants-text": "Ningún tenant encontrado",  
617 - "tenant-details": "Detalles del Tenant",  
618 - "delete-tenant-title": "¿Estás seguro que quieres eliminar el tenant '{{tenantTitle}}'?",  
619 - "delete-tenant-text": "Ten cuidado, luego de confirmar el tenant será eliminado y la información relacionada será irrecuperable.",  
620 - "delete-tenants-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 tenant} other {# tenants} }?",  
621 - "delete-tenants-action-title": "Eliminar { count, select, 1 {1 tenant} other {# tenants} }",  
622 - "delete-tenants-text": "Ten cuidado, luego de confirmar los tenants seleccionados serán eliminados y la información relacionada será irrecuperable.",  
623 - "title": "Título",  
624 - "title-required": "Título requerido.",  
625 - "description": "Descripción" 1100 + "tenants": "Tenants",
  1101 + "management": "Gestión de Tenant",
  1102 + "add": "Agregar Tenant",
  1103 + "admins": "Admins",
  1104 + "manage-tenant-admins": "Gestionar administradores tenant",
  1105 + "delete": "Eliminar tenant",
  1106 + "add-tenant-text": "Agregar nuevo tenant",
  1107 + "no-tenants-text": "Ningún tenant encontrado",
  1108 + "tenant-details": "Detalles del Tenant",
  1109 + "delete-tenant-title": "¿Estás seguro que quieres eliminar el tenant '{{tenantTitle}}'?",
  1110 + "delete-tenant-text": "Ten cuidado, luego de confirmar el tenant será eliminado y la información relacionada será irrecuperable.",
  1111 + "delete-tenants-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 tenant} other {# tenants} }?",
  1112 + "delete-tenants-action-title": "Eliminar { count, select, 1 {1 tenant} other {# tenants} }",
  1113 + "delete-tenants-text": "Ten cuidado, luego de confirmar los tenants seleccionados serán eliminados y la información relacionada será irrecuperable.",
  1114 + "title": "Título",
  1115 + "title-required": "Título requerido.",
  1116 + "description": "Descripción"
626 }, 1117 },
627 "timeinterval": { 1118 "timeinterval": {
628 - "seconds-interval": "{ seconds, select, 1 {1 segundo} other {# segundos} }",  
629 - "minutes-interval": "{ minutes, select, 1 {1 minuto} other {# minutos} }",  
630 - "hours-interval": "{ hours, select, 1 {1 hora} other {# horas} }",  
631 - "days-interval": "{ days, select, 1 {1 día} other {# días} }",  
632 - "days": "Días",  
633 - "hours": "Horas",  
634 - "minutes": "Minutos",  
635 - "seconds": "Segundos",  
636 - "advanced": "Avanzado" 1119 + "seconds-interval": "{ seconds, select, 1 {1 segundo} other {# segundos} }",
  1120 + "minutes-interval": "{ minutes, select, 1 {1 minuto} other {# minutos} }",
  1121 + "hours-interval": "{ hours, select, 1 {1 hora} other {# horas} }",
  1122 + "days-interval": "{ days, select, 1 {1 día} other {# días} }",
  1123 + "days": "Días",
  1124 + "hours": "Horas",
  1125 + "minutes": "Minutos",
  1126 + "seconds": "Segundos",
  1127 + "advanced": "Avanzado"
637 }, 1128 },
638 "timewindow": { 1129 "timewindow": {
639 - "days": "{ days, select, 1 { día } other {# días } }",  
640 - "hours": "{ hours, select, 0 { horas } 1 {1 hora } other {# horas } }",  
641 - "minutes": "{ minutes, select, 0 { minutos } 1 {1 minuto } other {# minutos } }",  
642 - "seconds": "{ seconds, select, 0 { segundos } 1 {1 segundo } other {# segundos } }",  
643 - "realtime": "Tiempo-real",  
644 - "history": "Histórico",  
645 - "last-prefix": "último",  
646 - "period": "desde {{ startTime }} hasta {{ endTime }}",  
647 - "edit": "Editar ventana de tiempo",  
648 - "date-range": "Rango de fechas",  
649 - "last": "Últimos",  
650 - "time-period": "Período de tiempo" 1130 + "days": "{ days, select, 1 { día } other {# días } }",
  1131 + "hours": "{ hours, select, 0 { horas } 1 {1 hora } other {# horas } }",
  1132 + "minutes": "{ minutes, select, 0 { minutos } 1 {1 minuto } other {# minutos } }",
  1133 + "seconds": "{ seconds, select, 0 { segundos } 1 {1 segundo } other {# segundos } }",
  1134 + "realtime": "Tiempo-real",
  1135 + "history": "Histórico",
  1136 + "last-prefix": "último",
  1137 + "period": "desde {{ startTime }} hasta {{ endTime }}",
  1138 + "edit": "Editar ventana de tiempo",
  1139 + "date-range": "Rango de fechas",
  1140 + "last": "Últimos",
  1141 + "time-period": "Período de tiempo"
651 }, 1142 },
652 "user": { 1143 "user": {
653 - "users": "Usuarios",  
654 - "customer-users": "Usuarios del Cliente",  
655 - "tenant-admins": "Tenant Admins",  
656 - "sys-admin": "Administrador del Sistema",  
657 - "tenant-admin": "Administrador Tenant",  
658 - "customer": "Cliente",  
659 - "anonymous": "Anónimo",  
660 - "add": "Agregar usuario",  
661 - "delete": "Eliminar usuario",  
662 - "add-user-text": "Agregar nuevo usuario",  
663 - "no-users-text": "Ningún usuario encontrado",  
664 - "user-details": "Detalles del usuario",  
665 - "delete-user-title": "¿Estás seguro que quieres eliminar el usuario '{{userEmail}}'?",  
666 - "delete-user-text": "Ten cuidado, luego de confirmar el usuario seleccionado será eliminado y la información relacionada será irrecuperable.",  
667 - "delete-users-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 usuario} other {# usuarios} }?",  
668 - "delete-users-action-title": "Borrar { count, select, 1 {1 usuario} other {# usuarios} }",  
669 - "delete-users-text": "Ten cuidado, luego de confirmar los usuarios seleccionados serán eliminados y la información relacionada será irrecuperable.",  
670 - "activation-email-sent-message": "Mail de activación enviado con éxito!",  
671 - "resend-activation": "Reenviar activación",  
672 - "email": "Email",  
673 - "email-required": "Email requerido.",  
674 - "first-name": "Nombre",  
675 - "last-name": "Apellido",  
676 - "description": "Descripción",  
677 - "default-dashboard": "Panel por defecto",  
678 - "always-fullscreen": "Siempre en pantalla completa" 1144 + "users": "Usuarios",
  1145 + "customer-users": "Usuarios del Cliente",
  1146 + "tenant-admins": "Tenant Admins",
  1147 + "sys-admin": "Administrador del Sistema",
  1148 + "tenant-admin": "Administrador Tenant",
  1149 + "customer": "Cliente",
  1150 + "anonymous": "Anónimo",
  1151 + "add": "Agregar usuario",
  1152 + "delete": "Eliminar usuario",
  1153 + "add-user-text": "Agregar nuevo usuario",
  1154 + "no-users-text": "Ningún usuario encontrado",
  1155 + "user-details": "Detalles del usuario",
  1156 + "delete-user-title": "¿Estás seguro que quieres eliminar el usuario '{{userEmail}}'?",
  1157 + "delete-user-text": "Ten cuidado, luego de confirmar el usuario seleccionado será eliminado y la información relacionada será irrecuperable.",
  1158 + "delete-users-title": "¿Estás seguro que quieres eliminar { count, select, 1 {1 usuario} other {# usuarios} }?",
  1159 + "delete-users-action-title": "Borrar { count, select, 1 {1 usuario} other {# usuarios} }",
  1160 + "delete-users-text": "Ten cuidado, luego de confirmar los usuarios seleccionados serán eliminados y la información relacionada será irrecuperable.",
  1161 + "activation-email-sent-message": "Mail de activación enviado con éxito!",
  1162 + "resend-activation": "Reenviar activación",
  1163 + "email": "Email",
  1164 + "email-required": "Email requerido.",
  1165 + "first-name": "Nombre",
  1166 + "last-name": "Apellido",
  1167 + "description": "Descripción",
  1168 + "default-dashboard": "Panel por defecto",
  1169 + "always-fullscreen": "Siempre en pantalla completa"
679 }, 1170 },
680 "value": { 1171 "value": {
681 - "type": "Tipo de valor",  
682 - "string": "Cadena de texto",  
683 - "string-value": "Valor de cadena de texto",  
684 - "integer": "Nro entero",  
685 - "integer-value": "Valor de nro entero",  
686 - "invalid-integer-value": "Valor inválido",  
687 - "double": "Nro decimal",  
688 - "double-value": "Valor nro decimal",  
689 - "boolean": "Booleano",  
690 - "boolean-value": "Valor booleano",  
691 - "false": "Falso",  
692 - "true": "Verdadero" 1172 + "type": "Tipo de valor",
  1173 + "string": "Cadena de texto",
  1174 + "string-value": "Valor de cadena de texto",
  1175 + "integer": "Nro entero",
  1176 + "integer-value": "Valor de nro entero",
  1177 + "invalid-integer-value": "Valor inválido",
  1178 + "double": "Nro decimal",
  1179 + "double-value": "Valor nro decimal",
  1180 + "boolean": "Booleano",
  1181 + "boolean-value": "Valor booleano",
  1182 + "false": "Falso",
  1183 + "true": "Verdadero"
693 }, 1184 },
694 "widget": { 1185 "widget": {
695 - "widget-library": "Bibloteca de Widgets",  
696 - "widget-bundle": "Paquetes de Widgets",  
697 - "select-widgets-bundle": "Seleccionar paquete de widgets",  
698 - "management": "Gestión de Widgets",  
699 - "editor": "Editor de widgets",  
700 - "widget-type-not-found": "Problema al cargar la configuración del widget.<br>Probablemente asociado\n El tipo de widget fue eliminado.",  
701 - "widget-type-load-error": "Widget no pudo ser cargado debido a estos errores:",  
702 - "remove": "Eliminar widget",  
703 - "edit": "Editar widget",  
704 - "remove-widget-title": "¿Estás seguro que quieres eliminar el widget '{{widgetTitle}}'?",  
705 - "remove-widget-text": "Luego de confirmar el widget será eliminado y toda la información relacionada será irrecuperable..",  
706 - "timeseries": "Series de tiempo",  
707 - "latest-values": "Últimos valores",  
708 - "rpc": "Widget de control",  
709 - "static": "Widget estático",  
710 - "select-widget-type": "Seleccionar tipo de widget",  
711 - "missing-widget-title-error": "El titulo del widget debe ser especificado!",  
712 - "widget-saved": "Widget guardado",  
713 - "unable-to-save-widget-error": "Imposible guardar widget! Tiene errores!",  
714 - "save": "Guardar widget",  
715 - "saveAs": "Guardar widget como",  
716 - "save-widget-type-as": "Guardar tipo de widget como",  
717 - "save-widget-type-as-text": "Por favor, ingrese un nuevo titulo y/o seleccione un paquete de destino.",  
718 - "toggle-fullscreen": "Cambiar a pantalla completa",  
719 - "run": "Correr widget",  
720 - "title": "Titulo",  
721 - "title-required": "Titulo requerido.",  
722 - "type": "Tipo",  
723 - "resources": "Recursos",  
724 - "resource-url": "JavaScript/CSS URL",  
725 - "remove-resource": "Eliminar recurso",  
726 - "add-resource": "Agregar recurso",  
727 - "html": "HTML",  
728 - "tidy": "Tidy",  
729 - "css": "CSS",  
730 - "settings-schema": "Esquema de configuración",  
731 - "datakey-settings-schema": "Esquema de configuración de clave de datos",  
732 - "javascript": "Javascript",  
733 - "remove-widget-type-title": "¿Estás seguro que quieres eliminar el tipo del widget '{{widgetName}}'?",  
734 - "remove-widget-type-text": "Luego de confirmar el tipo será eliminado y la información relacionada será irrecuperable.",  
735 - "remove-widget-type": "Eliminar tipo de widget.",  
736 - "add-widget-type": "Agregar nuevo tipo de widget",  
737 - "widget-type-load-failed-error": "Error al cargar el tipo de widget!",  
738 - "widget-template-load-failed-error": "Error al cargar el template del widget!",  
739 - "add": "Agregar Widget",  
740 - "undo": "Deshacer cambios",  
741 - "export": "Exportar widget" 1186 + "widget-library": "Bibloteca de Widgets",
  1187 + "widget-bundle": "Paquetes de Widgets",
  1188 + "select-widgets-bundle": "Seleccionar paquete de widgets",
  1189 + "management": "Gestión de Widgets",
  1190 + "editor": "Editor de widgets",
  1191 + "widget-type-not-found": "Problema al cargar la configuración del widget.<br>Probablemente asociado\n El tipo de widget fue eliminado.",
  1192 + "widget-type-load-error": "Widget no pudo ser cargado debido a estos errores:",
  1193 + "remove": "Eliminar widget",
  1194 + "edit": "Editar widget",
  1195 + "remove-widget-title": "¿Estás seguro que quieres eliminar el widget '{{widgetTitle}}'?",
  1196 + "remove-widget-text": "Luego de confirmar el widget será eliminado y toda la información relacionada será irrecuperable..",
  1197 + "timeseries": "Series de tiempo",
  1198 + "latest-values": "Últimos valores",
  1199 + "rpc": "Widget de control",
  1200 + "static": "Widget estático",
  1201 + "select-widget-type": "Seleccionar tipo de widget",
  1202 + "missing-widget-title-error": "El titulo del widget debe ser especificado!",
  1203 + "widget-saved": "Widget guardado",
  1204 + "unable-to-save-widget-error": "Imposible guardar widget! Tiene errores!",
  1205 + "save": "Guardar widget",
  1206 + "saveAs": "Guardar widget como",
  1207 + "save-widget-type-as": "Guardar tipo de widget como",
  1208 + "save-widget-type-as-text": "Por favor, ingrese un nuevo titulo y/o seleccione un paquete de destino.",
  1209 + "toggle-fullscreen": "Cambiar a pantalla completa",
  1210 + "run": "Correr widget",
  1211 + "title": "Titulo",
  1212 + "title-required": "Titulo requerido.",
  1213 + "type": "Tipo",
  1214 + "resources": "Recursos",
  1215 + "resource-url": "JavaScript/CSS URL",
  1216 + "remove-resource": "Eliminar recurso",
  1217 + "add-resource": "Agregar recurso",
  1218 + "html": "HTML",
  1219 + "tidy": "Tidy",
  1220 + "css": "CSS",
  1221 + "settings-schema": "Esquema de configuración",
  1222 + "datakey-settings-schema": "Esquema de configuración de clave de datos",
  1223 + "javascript": "Javascript",
  1224 + "remove-widget-type-title": "¿Estás seguro que quieres eliminar el tipo del widget '{{widgetName}}'?",
  1225 + "remove-widget-type-text": "Luego de confirmar el tipo será eliminado y la información relacionada será irrecuperable.",
  1226 + "remove-widget-type": "Eliminar tipo de widget.",
  1227 + "add-widget-type": "Agregar nuevo tipo de widget",
  1228 + "widget-type-load-failed-error": "Error al cargar el tipo de widget!",
  1229 + "widget-template-load-failed-error": "Error al cargar el template del widget!",
  1230 + "add": "Agregar Widget",
  1231 + "undo": "Deshacer cambios",
  1232 + "export": "Exportar widget"
  1233 + },
  1234 + "widget-action": { // TODO
  1235 + "header-button": "Widget header button",
  1236 + "open-dashboard-state": "Navigate to new dashboard state",
  1237 + "update-dashboard-state": "Update current dashboard state",
  1238 + "open-dashboard": "Navigate to other dashboard",
  1239 + "custom": "Custom action",
  1240 + "target-dashboard-state": "Target dashboard state",
  1241 + "target-dashboard-state-required": "Target dashboard state is required",
  1242 + "set-entity-from-widget": "Set entity from widget",
  1243 + "target-dashboard": "Target dashboard",
  1244 + "open-right-layout": "Open right dashboard layout (mobile view)"
742 }, 1245 },
743 "widgets-bundle": { 1246 "widgets-bundle": {
744 - "current": "Paquete actual",  
745 - "widgets-bundles": "Paquete de Widgets",  
746 - "add": "Agregar paquete de widgets",  
747 - "delete": "Eliminar paquete de widgets",  
748 - "title": "Título",  
749 - "title-required": "Título requerido.",  
750 - "add-widgets-bundle-text": "Agregar nuevo paquete de widgets",  
751 - "no-widgets-bundles-text": "Ningún paquete de widgets encontrado",  
752 - "empty": "Paquete de widgets vacío.",  
753 - "details": "Detalles",  
754 - "widgets-bundle-details": "Detalles del paquete de Widgets",  
755 - "delete-widgets-bundle-title": "¿Estás seguro que desea eliminar el paquete de widgets '{{widgetsBundleTitle}}'?",  
756 - "delete-widgets-bundle-text": "Ten cuidado, luego de confirmar todos los paquetes seleccionados serán eliminados y su información relacionada será irrecuperable.",  
757 - "delete-widgets-bundles-title": "¿Estás seguro que deseas eliminar { count, select, 1 {1 paquete de widgets} other {# paquetes de widgets} }?",  
758 - "delete-widgets-bundles-action-title": "Eliminar { count, select, 1 {1 paquete de widgets} other {# paquetes de widgets} }",  
759 - "delete-widgets-bundles-text": "Ten cuidado, luego de confirmar todos los paquetes seleccionados serán eliminados y la información relacionada será irrecuperable.",  
760 - "no-widgets-bundles-matching": "Ningún paquete '{{widgetsBundle}}' encontrado.",  
761 - "widgets-bundle-required": "Paquete de widget requerido.",  
762 - "system": "Sistema",  
763 - "import": "Importar paquete de widgets",  
764 - "export": "Exportar paquete de widgets",  
765 - "export-failed-error": "Imposible exportar paquete de widgets: {{error}}",  
766 - "create-new-widgets-bundle": "Crear nuevo paquete de widgets",  
767 - "widgets-bundle-file": "Archivo de paquete de widgets",  
768 - "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida." 1247 + "current": "Paquete actual",
  1248 + "widgets-bundles": "Paquete de Widgets",
  1249 + "add": "Agregar paquete de widgets",
  1250 + "delete": "Eliminar paquete de widgets",
  1251 + "title": "Título",
  1252 + "title-required": "Título requerido.",
  1253 + "add-widgets-bundle-text": "Agregar nuevo paquete de widgets",
  1254 + "no-widgets-bundles-text": "Ningún paquete de widgets encontrado",
  1255 + "empty": "Paquete de widgets vacío.",
  1256 + "details": "Detalles",
  1257 + "widgets-bundle-details": "Detalles del paquete de Widgets",
  1258 + "delete-widgets-bundle-title": "¿Estás seguro que desea eliminar el paquete de widgets '{{widgetsBundleTitle}}'?",
  1259 + "delete-widgets-bundle-text": "Ten cuidado, luego de confirmar todos los paquetes seleccionados serán eliminados y su información relacionada será irrecuperable.",
  1260 + "delete-widgets-bundles-title": "¿Estás seguro que deseas eliminar { count, select, 1 {1 paquete de widgets} other {# paquetes de widgets} }?",
  1261 + "delete-widgets-bundles-action-title": "Eliminar { count, select, 1 {1 paquete de widgets} other {# paquetes de widgets} }",
  1262 + "delete-widgets-bundles-text": "Ten cuidado, luego de confirmar todos los paquetes seleccionados serán eliminados y la información relacionada será irrecuperable.",
  1263 + "no-widgets-bundles-matching": "Ningún paquete '{{widgetsBundle}}' encontrado.",
  1264 + "widgets-bundle-required": "Paquete de widget requerido.",
  1265 + "system": "Sistema",
  1266 + "import": "Importar paquete de widgets",
  1267 + "export": "Exportar paquete de widgets",
  1268 + "export-failed-error": "Imposible exportar paquete de widgets: {{error}}",
  1269 + "create-new-widgets-bundle": "Crear nuevo paquete de widgets",
  1270 + "widgets-bundle-file": "Archivo de paquete de widgets",
  1271 + "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida."
769 }, 1272 },
770 "widget-config": { 1273 "widget-config": {
771 - "data": "Datos",  
772 - "settings": "Ajustes",  
773 - "advanced": "Avanzado",  
774 - "title": "Titulo",  
775 - "general-settings": "Ajustes generales",  
776 - "display-title": "Mostrar titulo",  
777 - "drop-shadow": "Sombra",  
778 - "enable-fullscreen": "Habilitar pantalla completa",  
779 - "background-color": "Color de fondo",  
780 - "text-color": "Color del texto",  
781 - "padding": "Relleno",  
782 - "title-style": "Estilo de título",  
783 - "mobile-mode-settings": "Ajustes mobile.",  
784 - "order": "Orden",  
785 - "height": "Altura",  
786 - "units": "Caracter especial a mostrar en el siguiente valor",  
787 - "decimals": "Números de dígitos después de la coma",  
788 - "timewindow": "Ventana de tiempo",  
789 - "use-dashboard-timewindow": "Usar ventana de tiempo del Panel",  
790 - "display-legend": "Mostrar leyenda",  
791 - "datasources": "Set de datos",  
792 - "datasource-type": "Tipo",  
793 - "datasource-parameters": "Parámetros",  
794 - "remove-datasource": "Eliminar set de datos",  
795 - "add-datasource": "Agregar set de datos",  
796 - "target-device": "Dispositivo destino" 1274 + "data": "Datos",
  1275 + "settings": "Ajustes",
  1276 + "advanced": "Avanzado",
  1277 + "title": "Titulo",
  1278 + "general-settings": "Ajustes generales",
  1279 + "display-title": "Mostrar titulo",
  1280 + "drop-shadow": "Sombra",
  1281 + "enable-fullscreen": "Habilitar pantalla completa",
  1282 + "background-color": "Color de fondo",
  1283 + "text-color": "Color del texto",
  1284 + "padding": "Relleno",
  1285 + "title-style": "Estilo de título",
  1286 + "mobile-mode-settings": "Ajustes mobile.",
  1287 + "order": "Orden",
  1288 + "height": "Altura",
  1289 + "units": "Caracter especial a mostrar en el siguiente valor",
  1290 + "decimals": "Números de dígitos después de la coma",
  1291 + "timewindow": "Ventana de tiempo",
  1292 + "use-dashboard-timewindow": "Usar ventana de tiempo del Panel",
  1293 + "display-legend": "Mostrar leyenda",
  1294 + "datasources": "Set de datos",
  1295 + "datasource-type": "Tipo",
  1296 + "datasource-parameters": "Parámetros",
  1297 + "remove-datasource": "Eliminar set de datos",
  1298 + "add-datasource": "Agregar set de datos",
  1299 + "target-device": "Dispositivo destino"
797 }, 1300 },
798 "widget-type": { 1301 "widget-type": {
799 - "import": "Importar tipo de widget",  
800 - "export": "Exportar tipo de widget",  
801 - "export-failed-error": "Imposible exportar tipo de widget: {{error}}",  
802 - "create-new-widget-type": "Crear nuevo tipo de widget",  
803 - "widget-type-file": "Tipo de archivo del widget",  
804 - "invalid-widget-type-file-error": "Imposible de importar tipo de widget: Estructura de datos inválida." 1302 + "import": "Importar tipo de widget",
  1303 + "export": "Exportar tipo de widget",
  1304 + "export-failed-error": "Imposible exportar tipo de widget: {{error}}",
  1305 + "create-new-widget-type": "Crear nuevo tipo de widget",
  1306 + "widget-type-file": "Tipo de archivo del widget",
  1307 + "invalid-widget-type-file-error": "Imposible de importar tipo de widget: Estructura de datos inválida."
  1308 + },
  1309 + "icon": { // TODO
  1310 + "icon": "Icon",
  1311 + "select-icon": "Select icon",
  1312 + "material-icons": "Material icons",
  1313 + "show-all": "Show all icons"
  1314 + },
  1315 + "custom": { // TODO
  1316 + "widget-action": {
  1317 + "action-cell-button": "Action cell button",
  1318 + "row-click": "On row click",
  1319 + "marker-click": "On marker click",
  1320 + "tooltip-tag-action": "Tooltip tag action"
  1321 + }
805 }, 1322 },
806 "language": { 1323 "language": {
807 - "language": "Lenguaje",  
808 - "en_US": "Inglés",  
809 - "ko_KR": "Coreano",  
810 - "zh_CN": "Chino",  
811 - "ru_RU": "Ruso",  
812 - "es_ES": "Español" 1324 + "language": "Lenguaje",
  1325 + "en_US": "Inglés",
  1326 + "ko_KR": "Coreano",
  1327 + "zh_CN": "Chino",
  1328 + "ru_RU": "Ruso",
  1329 + "es_ES": "Español"
813 } 1330 }
814 - };  
815 - angular.extend(locales, {'es_ES': es_ES}); 1331 + };
  1332 + angular.extend(locales, { 'es_ES': es_ES });
816 } 1333 }
@@ -38,8 +38,11 @@ export default function addLocaleKorean(locales) { @@ -38,8 +38,11 @@ export default function addLocaleKorean(locales) {
38 "update": "업데이트", 38 "update": "업데이트",
39 "remove": "제거", 39 "remove": "제거",
40 "search": "검색", 40 "search": "검색",
  41 + "clear-search": "Clear search", // TODO
41 "assign": "할당", 42 "assign": "할당",
42 "unassign": "비할당", 43 "unassign": "비할당",
  44 + "share": "Share", // TODO
  45 + "make-private": "Make private", // TODO
43 "apply": "적용", 46 "apply": "적용",
44 "apply-changes": "변경사항 적용", 47 "apply-changes": "변경사항 적용",
45 "edit-mode": "수정 모드", 48 "edit-mode": "수정 모드",
@@ -57,8 +60,11 @@ export default function addLocaleKorean(locales) { @@ -57,8 +60,11 @@ export default function addLocaleKorean(locales) {
57 "undo": "취소", 60 "undo": "취소",
58 "copy": "복사", 61 "copy": "복사",
59 "paste": "붙여넣기", 62 "paste": "붙여넣기",
  63 + "copy-reference": "Copy reference", // TODO
  64 + "paste-reference": "Paste reference", // TODO
60 "import": "가져오기", 65 "import": "가져오기",
61 - "export": "내보내기" 66 + "export": "내보내기",
  67 + "share-via": "Share via {{provider}}" // TODO
62 }, 68 },
63 "aggregation": { 69 "aggregation": {
64 "aggregation": "집합", 70 "aggregation": "집합",
@@ -95,6 +101,160 @@ export default function addLocaleKorean(locales) { @@ -95,6 +101,160 @@ export default function addLocaleKorean(locales) {
95 "enable-tls": "TLS 사용", 101 "enable-tls": "TLS 사용",
96 "send-test-mail": "테스트 메일 보내기" 102 "send-test-mail": "테스트 메일 보내기"
97 }, 103 },
  104 +
  105 + "alarm": { // TODO
  106 + "alarm": "Alarm",
  107 + "alarms": "Alarms",
  108 + "select-alarm": "Select alarm",
  109 + "no-alarms-matching": "No alarms matching '{{entity}}' were found.",
  110 + "alarm-required": "Alarm is required",
  111 + "alarm-status": "Alarm status",
  112 + "search-status": {
  113 + "ANY": "Any",
  114 + "ACTIVE": "Active",
  115 + "CLEARED": "Cleared",
  116 + "ACK": "Acknowledged",
  117 + "UNACK": "Unacknowledged"
  118 + },
  119 + "display-status": {
  120 + "ACTIVE_UNACK": "Active Unacknowledged",
  121 + "ACTIVE_ACK": "Active Acknowledged",
  122 + "CLEARED_UNACK": "Cleared Unacknowledged",
  123 + "CLEARED_ACK": "Cleared Acknowledged"
  124 + },
  125 + "no-alarms-prompt": "No alarms found",
  126 + "created-time": "Created time",
  127 + "type": "Type",
  128 + "severity": "Severity",
  129 + "originator": "Originator",
  130 + "originator-type": "Originator type",
  131 + "details": "Details",
  132 + "status": "Status",
  133 + "alarm-details": "Alarm details",
  134 + "start-time": "Start time",
  135 + "end-time": "End time",
  136 + "ack-time": "Acknowledged time",
  137 + "clear-time": "Cleared time",
  138 + "severity-critical": "Critical",
  139 + "severity-major": "Major",
  140 + "severity-minor": "Minor",
  141 + "severity-warning": "Warning",
  142 + "severity-indeterminate": "Indeterminate",
  143 + "acknowledge": "Acknowledge",
  144 + "clear": "Clear",
  145 + "search": "Search alarms",
  146 + "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected",
  147 + "no-data": "No data to display",
  148 + "polling-interval": "Alarms polling interval (sec)",
  149 + "polling-interval-required": "Alarms polling interval is required.",
  150 + "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
  151 + "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }",
  152 + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?",
  153 + "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }",
  154 + "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?"
  155 + },
  156 + "alias": { // TODO
  157 + "add": "Add alias",
  158 + "edit": "Edit alias",
  159 + "name": "Alias name",
  160 + "name-required": "Alias name is required",
  161 + "duplicate-alias": "Alias with same name is already exists.",
  162 + "filter-type-single-entity": "Single entity",
  163 + "filter-type-entity-list": "Entity list",
  164 + "filter-type-entity-name": "Entity name",
  165 + "filter-type-state-entity": "Entity from dashboard state",
  166 + "filter-type-state-entity-description": "Entity taken from dashboard state parameters",
  167 + "filter-type-asset-type": "Asset type",
  168 + "filter-type-asset-type-description": "Assets of type '{{assetType}}'",
  169 + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
  170 + "filter-type-device-type": "Device type",
  171 + "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
  172 + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
  173 + "filter-type-relations-query": "Relations query",
  174 + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  175 + "filter-type-asset-search-query": "Asset search query",
  176 + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  177 + "filter-type-device-search-query": "Device search query",
  178 + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  179 + "entity-filter": "Entity filter",
  180 + "resolve-multiple": "Resolve as multiple entities",
  181 + "filter-type": "Filter type",
  182 + "filter-type-required": "Filter type is required.",
  183 + "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
  184 + "no-entity-filter-specified": "No entity filter specified",
  185 + "root-state-entity": "Use dashboard state entity as root",
  186 + "root-entity": "Root entity",
  187 + "state-entity-parameter-name": "State entity parameter name",
  188 + "default-state-entity": "Default state entity",
  189 + "default-entity-parameter-name": "By default",
  190 + "max-relation-level": "Max relation level",
  191 + "unlimited-level": "Unlimited level",
  192 + "state-entity": "Dashboard state entity",
  193 + "all-entities": "All entities",
  194 + "any-relation": "any"
  195 + },
  196 + "asset": { // TODO
  197 + "asset": "Asset",
  198 + "assets": "Assets",
  199 + "management": "Asset management",
  200 + "view-assets": "View Assets",
  201 + "add": "Add Asset",
  202 + "assign-to-customer": "Assign to customer",
  203 + "assign-asset-to-customer": "Assign Asset(s) To Customer",
  204 + "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
  205 + "no-assets-text": "No assets found",
  206 + "assign-to-customer-text": "Please select the customer to assign the asset(s)",
  207 + "public": "Public",
  208 + "assignedToCustomer": "Assigned to customer",
  209 + "make-public": "Make asset public",
  210 + "make-private": "Make asset private",
  211 + "unassign-from-customer": "Unassign from customer",
  212 + "delete": "Delete asset",
  213 + "asset-public": "Asset is public",
  214 + "asset-type": "Asset type",
  215 + "asset-type-required": "Asset type is required.",
  216 + "select-asset-type": "Select asset type",
  217 + "enter-asset-type": "Enter asset type",
  218 + "any-asset": "Any asset",
  219 + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
  220 + "asset-type-list-empty": "No asset types selected.",
  221 + "asset-types": "Asset types",
  222 + "name": "Name",
  223 + "name-required": "Name is required.",
  224 + "description": "Description",
  225 + "type": "Type",
  226 + "type-required": "Type is required.",
  227 + "details": "Details",
  228 + "events": "Events",
  229 + "add-asset-text": "Add new asset",
  230 + "asset-details": "Asset details",
  231 + "assign-assets": "Assign assets",
  232 + "assign-assets-text": "Assign { count, select, 1 {1 asset} other {# assets} } to customer",
  233 + "delete-assets": "Delete assets",
  234 + "unassign-assets": "Unassign assets",
  235 + "unassign-assets-action-title": "Unassign { count, select, 1 {1 asset} other {# assets} } from customer",
  236 + "assign-new-asset": "Assign new asset",
  237 + "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?",
  238 + "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.",
  239 + "delete-assets-title": "Are you sure you want to delete { count, select, 1 {1 asset} other {# assets} }?",
  240 + "delete-assets-action-title": "Delete { count, select, 1 {1 asset} other {# assets} }",
  241 + "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.",
  242 + "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?",
  243 + "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.",
  244 + "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?",
  245 + "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.",
  246 + "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?",
  247 + "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.",
  248 + "unassign-asset": "Unassign asset",
  249 + "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
  250 + "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
  251 + "copyId": "Copy asset Id",
  252 + "idCopiedMessage": "Asset Id has been copied to clipboard",
  253 + "select-asset": "Select asset",
  254 + "no-assets-matching": "No assets matching '{{entity}}' were found.",
  255 + "asset-required": "Asset is required",
  256 + "name-starts-with": "Asset name starts with"
  257 + },
98 "attribute": { 258 "attribute": {
99 "attributes": "속성", 259 "attributes": "속성",
100 "latest-telemetry": "최근 데이터", 260 "latest-telemetry": "최근 데이터",
@@ -104,9 +264,9 @@ export default function addLocaleKorean(locales) { @@ -104,9 +264,9 @@ export default function addLocaleKorean(locales) {
104 "scope-server": "서버 속성", 264 "scope-server": "서버 속성",
105 "scope-shared": "공유 속성", 265 "scope-shared": "공유 속성",
106 "add": "속성 추가", 266 "add": "속성 추가",
107 - "key": "Key", 267 + "key": "Key", // TODO
108 "key-required": "속성 key를 입력하세요.", 268 "key-required": "속성 key를 입력하세요.",
109 - "value": "Value", 269 + "value": "Value", // TODO
110 "value-required": "속성 value를 입력하세요.", 270 "value-required": "속성 value를 입력하세요.",
111 "delete-attributes-title": "{ count, select, 1 {속성} other {여러 속성들을} } 삭제하시겠습니까??", 271 "delete-attributes-title": "{ count, select, 1 {속성} other {여러 속성들을} } 삭제하시겠습니까??",
112 "delete-attributes-text": "모든 선택된 속성들이 제거 될 것이므로 주의하십시오.", 272 "delete-attributes-text": "모든 선택된 속성들이 제거 될 것이므로 주의하십시오.",
@@ -121,6 +281,38 @@ export default function addLocaleKorean(locales) { @@ -121,6 +281,38 @@ export default function addLocaleKorean(locales) {
121 "selected-attributes": "{ count, select, 1 {속성 1개} other {속성 #개} } 선택됨", 281 "selected-attributes": "{ count, select, 1 {속성 1개} other {속성 #개} } 선택됨",
122 "selected-telemetry": "{ count, select, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨" 282 "selected-telemetry": "{ count, select, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨"
123 }, 283 },
  284 + "audit-log": { // TODO
  285 + "audit": "Audit",
  286 + "audit-logs": "Audit Logs",
  287 + "timestamp": "Timestamp",
  288 + "entity-type": "Entity Type",
  289 + "entity-name": "Entity Name",
  290 + "user": "User",
  291 + "type": "Type",
  292 + "status": "Status",
  293 + "details": "Details",
  294 + "type-added": "Added",
  295 + "type-deleted": "Deleted",
  296 + "type-updated": "Updated",
  297 + "type-attributes-updated": "Attributes updated",
  298 + "type-attributes-deleted": "Attributes deleted",
  299 + "type-rpc-call": "RPC call",
  300 + "type-credentials-updated": "Credentials updated",
  301 + "type-assigned-to-customer": "Assigned to Customer",
  302 + "type-unassigned-from-customer": "Unassigned from Customer",
  303 + "type-activated": "Activated",
  304 + "type-suspended": "Suspended",
  305 + "type-credentials-read": "Credentials read",
  306 + "type-attributes-read": "Attributes read",
  307 + "status-success": "Success",
  308 + "status-failure": "Failure",
  309 + "audit-log-details": "Audit log details",
  310 + "no-audit-logs-prompt": "No logs found",
  311 + "action-data": "Action data",
  312 + "failure-details": "Failure details",
  313 + "search": "Search audit logs",
  314 + "clear-search": "Clear search"
  315 + },
124 "confirm-on-exit": { 316 "confirm-on-exit": {
125 "message": "변경 사항을 저장하지 않았습니다. 이 페이지를 나가시겠습니까?", 317 "message": "변경 사항을 저장하지 않았습니다. 이 페이지를 나가시겠습니까?",
126 "html-message": "변경 사항을 저장하지 않았습니다.<br/>이 페이지를 나가시겠습니까?", 318 "html-message": "변경 사항을 저장하지 않았습니다.<br/>이 페이지를 나가시겠습니까?",
@@ -145,6 +337,11 @@ export default function addLocaleKorean(locales) { @@ -145,6 +337,11 @@ export default function addLocaleKorean(locales) {
145 "enter-password": "비밀번호를 입력하세요.", 337 "enter-password": "비밀번호를 입력하세요.",
146 "enter-search": "검색어 입력" 338 "enter-search": "검색어 입력"
147 }, 339 },
  340 + "content-type": { // TODO
  341 + "json": "Json",
  342 + "text": "Text",
  343 + "binary": "Binary (Base64)"
  344 + },
148 "customer": { 345 "customer": {
149 "customers": "커스터머", 346 "customers": "커스터머",
150 "management": "커스터머 관리", 347 "management": "커스터머 관리",
@@ -156,6 +353,10 @@ export default function addLocaleKorean(locales) { @@ -156,6 +353,10 @@ export default function addLocaleKorean(locales) {
156 "manage-customer-users": "커스터머 사용자 관리", 353 "manage-customer-users": "커스터머 사용자 관리",
157 "manage-customer-devices": "커스터머 디바이스 관리", 354 "manage-customer-devices": "커스터머 디바이스 관리",
158 "manage-customer-dashboards": "커스터머 대시보드 관리", 355 "manage-customer-dashboards": "커스터머 대시보드 관리",
  356 + "manage-public-devices": "Manage public devices", // TODO
  357 + "manage-public-dashboards": "Manage public dashboards", // TODO
  358 + "manage-customer-assets": "Manage customer assets", // TODO
  359 + "manage-public-assets": "Manage public assets", // TODO
159 "add-customer-text": "커스터머 추가", 360 "add-customer-text": "커스터머 추가",
160 "no-customers-text": "커스터머가 없습니다.", 361 "no-customers-text": "커스터머가 없습니다.",
161 "customer-details": "커스터머 상세정보", 362 "customer-details": "커스터머 상세정보",
@@ -169,7 +370,17 @@ export default function addLocaleKorean(locales) { @@ -169,7 +370,17 @@ export default function addLocaleKorean(locales) {
169 "manage-dashboards": "대시보드 관리", 370 "manage-dashboards": "대시보드 관리",
170 "title": "타이틀", 371 "title": "타이틀",
171 "title-required": "타이틀을 입력하세요.", 372 "title-required": "타이틀을 입력하세요.",
172 - "description": "설명" 373 + "description": "설명",
  374 + "details": "Details",
  375 + "events": "Events",
  376 + "copyId": "Copy customer Id",
  377 + "idCopiedMessage": "Customer Id has been copied to clipboard",
  378 + "select-customer": "Select customer",
  379 + "no-customers-matching": "No customers matching '{{entity}}' were found.",
  380 + "customer-required": "Customer is required",
  381 + "select-default-customer": "Select default customer",
  382 + "default-customer": "Default customer",
  383 + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
173 }, 384 },
174 "datetime": { 385 "datetime": {
175 "date-from": "시작 날짜", 386 "date-from": "시작 날짜",
@@ -277,11 +488,15 @@ export default function addLocaleKorean(locales) { @@ -277,11 +488,15 @@ export default function addLocaleKorean(locales) {
277 "attributes": "Attributes", 488 "attributes": "Attributes",
278 "timeseries-required": "디바이스 timeseries 를 입력하세요.", 489 "timeseries-required": "디바이스 timeseries 를 입력하세요.",
279 "timeseries-or-attributes-required": "디바이스 timeseries/attributes 를 입력하세요.", 490 "timeseries-or-attributes-required": "디바이스 timeseries/attributes 를 입력하세요.",
  491 + "maximum-timeseries-or-attributes": "Maximum { count, select, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", // TODO
  492 + "alarm-fields-required": "Alarm fields are required.", // TODO
280 "function-types": "함수 유형", 493 "function-types": "함수 유형",
281 - "function-types-required": "함수 유형을 입력하세요." 494 + "function-types-required": "함수 유형을 입력하세요.",
  495 + "maximum-function-types": "Maximum { count, select, 1 {1 function type is allowed.} other {# function types are allowed} }" // TODO
282 }, 496 },
283 "datasource": { 497 "datasource": {
284 "type": "데이터소스 유형", 498 "type": "데이터소스 유형",
  499 + "name": "Name", // TODO
285 "add-datasource-prompt": "데이터소스를 추가하세요." 500 "add-datasource-prompt": "데이터소스를 추가하세요."
286 }, 501 },
287 "details": { 502 "details": {
@@ -375,11 +590,101 @@ export default function addLocaleKorean(locales) { @@ -375,11 +590,101 @@ export default function addLocaleKorean(locales) {
375 "unhandled-error-code": "처리되지 않은 오류 코드: {{errorCode}}", 590 "unhandled-error-code": "처리되지 않은 오류 코드: {{errorCode}}",
376 "unknown-error": "알 수 없는 오류" 591 "unknown-error": "알 수 없는 오류"
377 }, 592 },
  593 + "entity": { // TODO
  594 + "entity": "Entity",
  595 + "entities": "Entities",
  596 + "aliases": "Entity aliases",
  597 + "entity-alias": "Entity alias",
  598 + "unable-delete-entity-alias-title": "Unable to delete entity alias",
  599 + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
  600 + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
  601 + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
  602 + "configure-alias": "Configure '{{alias}}' alias",
  603 + "alias": "Alias",
  604 + "alias-required": "Entity alias is required.",
  605 + "remove-alias": "Remove entity alias",
  606 + "add-alias": "Add entity alias",
  607 + "entity-list": "Entity list",
  608 + "entity-type": "Entity type",
  609 + "entity-types": "Entity types",
  610 + "entity-type-list": "Entity type list",
  611 + "any-entity": "Any entity",
  612 + "enter-entity-type": "Enter entity type",
  613 + "no-entities-matching": "No entities matching '{{entity}}' were found.",
  614 + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
  615 + "name-starts-with": "Name starts with",
  616 + "use-entity-name-filter": "Use filter",
  617 + "entity-list-empty": "No entities selected.",
  618 + "entity-type-list-empty": "No entity types selected.",
  619 + "entity-name-filter-required": "Entity name filter is required.",
  620 + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
  621 + "all-subtypes": "All",
  622 + "select-entities": "Select entities",
  623 + "no-aliases-found": "No aliases found.",
  624 + "no-alias-matching": "'{{alias}}' not found.",
  625 + "create-new-alias": "Create a new one!",
  626 + "key": "Key",
  627 + "key-name": "Key name",
  628 + "no-keys-found": "No keys found.",
  629 + "no-key-matching": "'{{key}}' not found.",
  630 + "create-new-key": "Create a new one!",
  631 + "type": "Type",
  632 + "type-required": "Entity type is required.",
  633 + "type-device": "Device",
  634 + "type-devices": "Devices",
  635 + "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
  636 + "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
  637 + "type-asset": "Asset",
  638 + "type-assets": "Assets",
  639 + "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
  640 + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
  641 + "type-rule": "Rule",
  642 + "type-rules": "Rules",
  643 + "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
  644 + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
  645 + "type-plugin": "Plugin",
  646 + "type-plugins": "Plugins",
  647 + "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
  648 + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
  649 + "type-tenant": "Tenant",
  650 + "type-tenants": "Tenants",
  651 + "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
  652 + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
  653 + "type-customer": "Customer",
  654 + "type-customers": "Customers",
  655 + "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
  656 + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
  657 + "type-user": "User",
  658 + "type-users": "Users",
  659 + "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
  660 + "user-name-starts-with": "Users whose names start with '{{prefix}}'",
  661 + "type-dashboard": "Dashboard",
  662 + "type-dashboards": "Dashboards",
  663 + "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
  664 + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
  665 + "type-alarm": "Alarm",
  666 + "type-alarms": "Alarms",
  667 + "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
  668 + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  669 + "type-rulechain": "Rule chain",
  670 + "type-rulechains": "Rule chains",
  671 + "list-of-rulechains": "{ count, select, 1 {One rule chain} other {List of # rule chains} }",
  672 + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
  673 + "type-current-customer": "Current Customer",
  674 + "search": "Search entities",
  675 + "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
  676 + "entity-name": "Entity name",
  677 + "details": "Entity details",
  678 + "no-entities-prompt": "No entities found",
  679 + "no-data": "No data to display"
  680 + },
378 "event": { 681 "event": {
379 "event-type": "이벤트 타입", 682 "event-type": "이벤트 타입",
380 "type-error": "에러", 683 "type-error": "에러",
381 "type-lc-event": "주기적 이벤트", 684 "type-lc-event": "주기적 이벤트",
382 "type-stats": "통계", 685 "type-stats": "통계",
  686 + "type-debug-rule-node": "Debug", // TODO
  687 + "type-debug-rule-chain": "Debug", // TODO
383 "no-events-prompt": "이벤트 없음", 688 "no-events-prompt": "이벤트 없음",
384 "error": "에러", 689 "error": "에러",
385 "alarm": "알람", 690 "alarm": "알람",
@@ -387,6 +692,14 @@ export default function addLocaleKorean(locales) { @@ -387,6 +692,14 @@ export default function addLocaleKorean(locales) {
387 "server": "서버", 692 "server": "서버",
388 "body": "Body", 693 "body": "Body",
389 "method": "Method", 694 "method": "Method",
  695 + "type": "Type", // TODO
  696 + "entity": "Entity", // TODO
  697 + "message-id": "Message Id", // TODO
  698 + "message-type": "Message Type", // TODO
  699 + "data-type": "Data Type", // TODO
  700 + "relation-type": "Relation Type", // TODO
  701 + "metadata": "Metadata", // TODO
  702 + "data": "Data", // TODO
390 "event": "이벤트", 703 "event": "이벤트",
391 "status": "상태", 704 "status": "상태",
392 "success": "성공", 705 "success": "성공",
@@ -394,6 +707,163 @@ export default function addLocaleKorean(locales) { @@ -394,6 +707,163 @@ export default function addLocaleKorean(locales) {
394 "messages-processed": "처리된 메시지", 707 "messages-processed": "처리된 메시지",
395 "errors-occurred": "오류가 발생했습니다" 708 "errors-occurred": "오류가 발생했습니다"
396 }, 709 },
  710 + "extension": { // TODO
  711 + "extensions": "Extensions",
  712 + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
  713 + "type": "Type",
  714 + "key": "Key",
  715 + "value": "Value",
  716 + "id": "Id",
  717 + "extension-id": "Extension id",
  718 + "extension-type": "Extension type",
  719 + "transformer-json": "JSON *",
  720 + "unique-id-required": "Current extension id already exists.",
  721 + "delete": "Delete extension",
  722 + "add": "Add extension",
  723 + "edit": "Edit extension",
  724 + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
  725 + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
  726 + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
  727 + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
  728 + "converters": "Converters",
  729 + "converter-id": "Converter id",
  730 + "configuration": "Configuration",
  731 + "converter-configurations": "Converter configurations",
  732 + "token": "Security token",
  733 + "add-converter": "Add converter",
  734 + "add-config": "Add converter configuration",
  735 + "device-name-expression": "Device name expression",
  736 + "device-type-expression": "Device type expression",
  737 + "custom": "Custom",
  738 + "to-double": "To Double",
  739 + "transformer": "Transformer",
  740 + "json-required": "Transformer json is required.",
  741 + "json-parse": "Unable to parse transformer json.",
  742 + "attributes": "Attributes",
  743 + "add-attribute": "Add attribute",
  744 + "add-map": "Add mapping element",
  745 + "timeseries": "Timeseries",
  746 + "add-timeseries": "Add timeseries",
  747 + "field-required": "Field is required",
  748 + "brokers": "Brokers",
  749 + "add-broker": "Add broker",
  750 + "host": "Host",
  751 + "port": "Port",
  752 + "port-range": "Port should be in a range from 1 to 65535.",
  753 + "ssl": "Ssl",
  754 + "credentials": "Credentials",
  755 + "username": "Username",
  756 + "password": "Password",
  757 + "retry-interval": "Retry interval in milliseconds",
  758 + "anonymous": "Anonymous",
  759 + "basic": "Basic",
  760 + "pem": "PEM",
  761 + "ca-cert": "CA certificate file *",
  762 + "private-key": "Private key file *",
  763 + "cert": "Certificate file *",
  764 + "no-file": "No file selected.",
  765 + "drop-file": "Drop a file or click to select a file to upload.",
  766 + "mapping": "Mapping",
  767 + "topic-filter": "Topic filter",
  768 + "converter-type": "Converter type",
  769 + "converter-json": "Json",
  770 + "json-name-expression": "Device name json expression",
  771 + "topic-name-expression": "Device name topic expression",
  772 + "json-type-expression": "Device type json expression",
  773 + "topic-type-expression": "Device type topic expression",
  774 + "attribute-key-expression": "Attribute key expression",
  775 + "attr-json-key-expression": "Attribute key json expression",
  776 + "attr-topic-key-expression": "Attribute key topic expression",
  777 + "request-id-expression": "Request id expression",
  778 + "request-id-json-expression": "Request id json expression",
  779 + "request-id-topic-expression": "Request id topic expression",
  780 + "response-topic-expression": "Response topic expression",
  781 + "value-expression": "Value expression",
  782 + "topic": "Topic",
  783 + "timeout": "Timeout in milliseconds",
  784 + "converter-json-required": "Converter json is required.",
  785 + "converter-json-parse": "Unable to parse converter json.",
  786 + "filter-expression": "Filter expression",
  787 + "connect-requests": "Connect requests",
  788 + "add-connect-request": "Add connect request",
  789 + "disconnect-requests": "Disconnect requests",
  790 + "add-disconnect-request": "Add disconnect request",
  791 + "attribute-requests": "Attribute requests",
  792 + "add-attribute-request": "Add attribute request",
  793 + "attribute-updates": "Attribute updates",
  794 + "add-attribute-update": "Add attribute update",
  795 + "server-side-rpc": "Server side RPC",
  796 + "add-server-side-rpc-request": "Add server-side RPC request",
  797 + "device-name-filter": "Device name filter",
  798 + "attribute-filter": "Attribute filter",
  799 + "method-filter": "Method filter",
  800 + "request-topic-expression": "Request topic expression",
  801 + "response-timeout": "Response timeout in milliseconds",
  802 + "topic-expression": "Topic expression",
  803 + "client-scope": "Client scope",
  804 + "add-device": "Add device",
  805 + "opc-server": "Servers",
  806 + "opc-add-server": "Add server",
  807 + "opc-add-server-prompt": "Please add server",
  808 + "opc-application-name": "Application name",
  809 + "opc-application-uri": "Application uri",
  810 + "opc-scan-period-in-seconds": "Scan period in seconds",
  811 + "opc-security": "Security",
  812 + "opc-identity": "Identity",
  813 + "opc-keystore": "Keystore",
  814 + "opc-type": "Type",
  815 + "opc-keystore-type": "Type",
  816 + "opc-keystore-location": "Location *",
  817 + "opc-keystore-password": "Password",
  818 + "opc-keystore-alias": "Alias",
  819 + "opc-keystore-key-password": "Key password",
  820 + "opc-device-node-pattern": "Device node pattern",
  821 + "opc-device-name-pattern": "Device name pattern",
  822 + "modbus-server": "Servers/slaves",
  823 + "modbus-add-server": "Add server/slave",
  824 + "modbus-add-server-prompt": "Please add server/slave",
  825 + "modbus-transport": "Transport",
  826 + "modbus-port-name": "Serial port name",
  827 + "modbus-encoding": "Encoding",
  828 + "modbus-parity": "Parity",
  829 + "modbus-baudrate": "Baud rate",
  830 + "modbus-databits": "Data bits",
  831 + "modbus-stopbits": "Stop bits",
  832 + "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
  833 + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
  834 + "modbus-unit-id": "Unit ID",
  835 + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
  836 + "modbus-device-name": "Device name",
  837 + "modbus-poll-period": "Poll period (ms)",
  838 + "modbus-attributes-poll-period": "Attributes poll period (ms)",
  839 + "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
  840 + "modbus-poll-period-range": "Poll period should be positive value.",
  841 + "modbus-tag": "Tag",
  842 + "modbus-function": "Function",
  843 + "modbus-register-address": "Register address",
  844 + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
  845 + "modbus-register-bit-index": "Bit index",
  846 + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
  847 + "modbus-register-count": "Register count",
  848 + "modbus-register-count-range": "Register count should be a positive value.",
  849 + "modbus-byte-order": "Byte order",
  850 +
  851 + "sync": {
  852 + "status": "Status",
  853 + "sync": "Sync",
  854 + "not-sync": "Not sync",
  855 + "last-sync-time": "Last sync time",
  856 + "not-available": "Not available"
  857 + },
  858 +
  859 + "export-extensions-configuration": "Export extensions configuration",
  860 + "import-extensions-configuration": "Import extensions configuration",
  861 + "import-extensions": "Import extensions",
  862 + "import-extension": "Import extension",
  863 + "export-extension": "Export extension",
  864 + "file": "Extensions file",
  865 + "invalid-file-error": "Invalid extension file"
  866 + },
397 "fullscreen": { 867 "fullscreen": {
398 "expand": "전체화면으로 확장", 868 "expand": "전체화면으로 확장",
399 "exit": "전체화면 종료", 869 "exit": "전체화면 종료",
@@ -436,7 +906,24 @@ export default function addLocaleKorean(locales) { @@ -436,7 +906,24 @@ export default function addLocaleKorean(locales) {
436 }, 906 },
437 "js-func": { 907 "js-func": {
438 "no-return-error": "함수는 값을 반환해야 합니다!", 908 "no-return-error": "함수는 값을 반환해야 합니다!",
439 - "return-type-mismatch": "함수는 '{{type}}' 유형의 값을 반환해야 합니다!" 909 + "return-type-mismatch": "함수는 '{{type}}' 유형의 값을 반환해야 합니다!",
  910 + "tidy": "Tidy" // TODO
  911 + },
  912 + "key-val": { // TODO
  913 + "key": "Key",
  914 + "value": "Value",
  915 + "remove-entry": "Remove entry",
  916 + "add-entry": "Add entry",
  917 + "no-data": "No entries"
  918 + },
  919 + "layout": { // TODO
  920 + "layout": "Layout",
  921 + "manage": "Manage layouts",
  922 + "settings": "Layout settings",
  923 + "color": "Color",
  924 + "main": "Main",
  925 + "right": "Right",
  926 + "select": "Select target layout"
440 }, 927 },
441 "legend": { 928 "legend": {
442 "position": "범례 위치", 929 "position": "범례 위치",
@@ -467,45 +954,6 @@ export default function addLocaleKorean(locales) { @@ -467,45 +954,6 @@ export default function addLocaleKorean(locales) {
467 "password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!", 954 "password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!",
468 "email": "이메일" 955 "email": "이메일"
469 }, 956 },
470 - "plugin": {  
471 - "plugins": "플러그인",  
472 - "delete": "플러그인 삭제",  
473 - "activate": "플러그인 활성화",  
474 - "suspend": "플러그인 비활성화",  
475 - "active": "활성화",  
476 - "suspended": "비활성화",  
477 - "name": "이름",  
478 - "name-required": "이름을 입력하세요.",  
479 - "description": "설명",  
480 - "add": "플러그인 추가",  
481 - "delete-plugin-title": "'{{pluginName}}' 플러그인을 삭제하시겠습니까?",  
482 - "delete-plugin-text": "플러그인과 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.",  
483 - "delete-plugins-title": "{ count, select, 1 {플러그인 1개} other {플러그인 #개} }를 삭제하시겠습니까?",  
484 - "delete-plugins-action-title": "{ count, select, 1 {플러그인 1개} other {플러그인 #개} } 삭제",  
485 - "delete-plugins-text": "선택된 플러그인이 삭제되고 관련된 모든 데이터가 없어지므로 주의하십시오.",  
486 - "add-plugin-text": "새로운 플러그인 추가",  
487 - "no-plugins-text": "플러그인이 없습니다.",  
488 - "plugin-details": "플러그인 상세정보",  
489 - "api-token": "API 토큰",  
490 - "api-token-required": "API 토큰을 입력하세요.",  
491 - "type": "플러그인 종류",  
492 - "type-required": "플러그인 종류를 선택해주세요.",  
493 - "configuration": "플러그인 구성",  
494 - "system": "시스템",  
495 - "select-plugin": "플러그인 선택",  
496 - "plugin": "플러그인",  
497 - "no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.",  
498 - "plugin-required": "플러그인을 입력하세요.",  
499 - "plugin-require-match": "기존의 플러그인을 선택해주세요.",  
500 - "events": "이벤트",  
501 - "details": "상세",  
502 - "import": "플러그인 가져오기",  
503 - "export": "플러그인 내보내기",  
504 - "export-failed-error": "플러그인을 내보내기 할 수 없습니다.: {{error}}",  
505 - "create-new-plugin": "새로운 플러그인 생성",  
506 - "plugin-file": "플러그인 파일",  
507 - "invalid-plugin-file-error": "플러그인을 가져오기 할 수 없습니다.: 잘못된 플러그인 데이터 구조입니다."  
508 - },  
509 "position": { 957 "position": {
510 "top": "상단", 958 "top": "상단",
511 "bottom": "하단", 959 "bottom": "하단",
@@ -517,60 +965,139 @@ export default function addLocaleKorean(locales) { @@ -517,60 +965,139 @@ export default function addLocaleKorean(locales) {
517 "change-password": "비밀번호 변경", 965 "change-password": "비밀번호 변경",
518 "current-password": "현재 비밀번호" 966 "current-password": "현재 비밀번호"
519 }, 967 },
520 - "rule": {  
521 - "rules": "규칙",  
522 - "delete": "규칙 삭제",  
523 - "activate": "규칙 활성화",  
524 - "suspend": "규칙 비활성화",  
525 - "active": "활성화",  
526 - "suspended": "비활성화",  
527 - "name": "이름",  
528 - "name-required": "이름을 입력하세요.",  
529 - "description": "설명",  
530 - "add": "규칙 추가",  
531 - "delete-rule-title": "'{{ruleName}}' 규칙을 삭제하시겠습니까?",  
532 - "delete-rule-text": "규칙과 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.",  
533 - "delete-rules-title": "{ count, select, 1 {규칙 1개} other {규칙 #개} }를 삭제하시겠습니까?",  
534 - "delete-rules-action-title": "{ count, select, 1 {규칙 1개} other {규칙 #개} } 삭제",  
535 - "delete-rules-text": "선택된 규칙이 삭제되고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.",  
536 - "add-rule-text": "규칙 추가",  
537 - "no-rules-text": "규칙이 없습니다.",  
538 - "rule-details": "규칙 상세정보",  
539 - "filters": "필터",  
540 - "filter": "필터",  
541 - "add-filter-prompt": "필터를 추가해 주세요.",  
542 - "remove-filter": "필터 삭제",  
543 - "add-filter": "필터 추가",  
544 - "filter-name": "필터 이름",  
545 - "filter-type": "필터 종류",  
546 - "edit-filter": "필터 수정",  
547 - "view-filter": "필터 보기",  
548 - "component-name": "이름",  
549 - "component-name-required": "이름을 입력하세요.",  
550 - "component-type": "종류",  
551 - "component-type-required": "타입을 입력하세요.",  
552 - "processor": "프로세서",  
553 - "no-processor-configured": "프로세서가 구성되지 않았습니다.",  
554 - "create-processor": "프로세서 생성",  
555 - "processor-name": "프로세서 이름",  
556 - "processor-type": "프로세서 종류",  
557 - "plugin-action": "플러그인 액션",  
558 - "action-name": "액션 이름",  
559 - "action-type": "액션 종류",  
560 - "create-action-prompt": "액션을 만들어 주세요",  
561 - "create-action": "액션 생성",  
562 - "details": "상세",  
563 - "events": "이벤트",  
564 - "system": "시스템",  
565 - "import": "규칙 가져오기",  
566 - "export": "규칙 내보내기",  
567 - "export-failed-error": "규칙을 내보내기 할 수 없습니다.: {{error}}",  
568 - "create-new-rule": "새로운 규칙 생성",  
569 - "rule-file": "규칙 파일",  
570 - "invalid-rule-file-error": "규칙을 가져오기 할 수 없습니다.: 잘못된 데이터 구조입니다." 968 + "relation": { // TODO
  969 + "relations": "Relations",
  970 + "direction": "Direction",
  971 + "search-direction": {
  972 + "FROM": "From",
  973 + "TO": "To"
  974 + },
  975 + "direction-type": {
  976 + "FROM": "from",
  977 + "TO": "to"
  978 + },
  979 + "from-relations": "Outbound relations",
  980 + "to-relations": "Inbound relations",
  981 + "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
  982 + "type": "Type",
  983 + "to-entity-type": "To entity type",
  984 + "to-entity-name": "To entity name",
  985 + "from-entity-type": "From entity type",
  986 + "from-entity-name": "From entity name",
  987 + "to-entity": "To entity",
  988 + "from-entity": "From entity",
  989 + "delete": "Delete relation",
  990 + "relation-type": "Relation type",
  991 + "relation-type-required": "Relation type is required.",
  992 + "any-relation-type": "Any type",
  993 + "add": "Add relation",
  994 + "edit": "Edit relation",
  995 + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
  996 + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
  997 + "delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  998 + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.",
  999 + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
  1000 + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
  1001 + "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  1002 + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.",
  1003 + "remove-relation-filter": "Remove relation filter",
  1004 + "add-relation-filter": "Add relation filter",
  1005 + "any-relation": "Any relation",
  1006 + "relation-filters": "Relation filters",
  1007 + "additional-info": "Additional info (JSON)",
  1008 + "invalid-additional-info": "Unable to parse additional info json."
  1009 + },
  1010 + "rulechain": { // TODO
  1011 + "rulechain": "Rule chain",
  1012 + "rulechains": "Rule chains",
  1013 + "root": "Root",
  1014 + "delete": "Delete rule chain",
  1015 + "name": "Name",
  1016 + "name-required": "Name is required.",
  1017 + "description": "Description",
  1018 + "add": "Add Rule Chain",
  1019 + "set-root": "Make rule chain root",
  1020 + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
  1021 + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
  1022 + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
  1023 + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
  1024 + "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
  1025 + "delete-rulechains-action-title": "Delete { count, select, 1 {1 rule chain} other {# rule chains} }",
  1026 + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
  1027 + "add-rulechain-text": "Add new rule chain",
  1028 + "no-rulechains-text": "No rule chains found",
  1029 + "rulechain-details": "Rule chain details",
  1030 + "details": "Details",
  1031 + "events": "Events",
  1032 + "system": "System",
  1033 + "import": "Import rule chain",
  1034 + "export": "Export rule chain",
  1035 + "export-failed-error": "Unable to export rule chain: {{error}}",
  1036 + "create-new-rulechain": "Create new rule chain",
  1037 + "rulechain-file": "Rule chain file",
  1038 + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
  1039 + "copyId": "Copy rule chain Id",
  1040 + "idCopiedMessage": "Rule chain Id has been copied to clipboard",
  1041 + "select-rulechain": "Select rule chain",
  1042 + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
  1043 + "rulechain-required": "Rule chain is required",
  1044 + "management": "Rules management",
  1045 + "debug-mode": "Debug mode"
571 }, 1046 },
572 - "rule-plugin": {  
573 - "management": "규칙 및 플러그인 관리" 1047 + "rulenode": { // TODO
  1048 + "details": "Details",
  1049 + "events": "Events",
  1050 + "search": "Search nodes",
  1051 + "open-node-library": "Open node library",
  1052 + "add": "Add rule node",
  1053 + "name": "Name",
  1054 + "name-required": "Name is required.",
  1055 + "type": "Type",
  1056 + "description": "Description",
  1057 + "delete": "Delete rule node",
  1058 + "select-all-objects": "Select all nodes and connections",
  1059 + "deselect-all-objects": "Deselect all nodes and connections",
  1060 + "delete-selected-objects": "Delete selected nodes and connections",
  1061 + "delete-selected": "Delete selected",
  1062 + "select-all": "Select all",
  1063 + "copy-selected": "Copy selected",
  1064 + "deselect-all": "Deselect all",
  1065 + "rulenode-details": "Rule node details",
  1066 + "debug-mode": "Debug mode",
  1067 + "configuration": "Configuration",
  1068 + "link": "Link",
  1069 + "link-details": "Rule node link details",
  1070 + "add-link": "Add link",
  1071 + "link-label": "Link label",
  1072 + "link-label-required": "Link label is required.",
  1073 + "custom-link-label": "Custom link label",
  1074 + "custom-link-label-required": "Custom link label is required.",
  1075 + "type-filter": "Filter",
  1076 + "type-filter-details": "Filter incoming messages with configured conditions",
  1077 + "type-enrichment": "Enrichment",
  1078 + "type-enrichment-details": "Add additional information into Message Metadata",
  1079 + "type-transformation": "Transformation",
  1080 + "type-transformation-details": "Change Message payload and Metadata",
  1081 + "type-action": "Action",
  1082 + "type-action-details": "Perform special action",
  1083 + "type-external": "External",
  1084 + "type-external-details": "Interacts with external system",
  1085 + "type-rule-chain": "Rule Chain",
  1086 + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
  1087 + "type-input": "Input",
  1088 + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
  1089 + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
  1090 + "ui-resources-load-error": "Failed to load configuration ui resources.",
  1091 + "invalid-target-rulechain": "Unable to resolve target rule chain!",
  1092 + "test-script-function": "Test script function",
  1093 + "message": "Message",
  1094 + "message-type": "Message type",
  1095 + "message-type-required": "Message type is required",
  1096 + "metadata": "Metadata",
  1097 + "metadata-required": "Metadata entries can't be empty.",
  1098 + "output": "Output",
  1099 + "test": "Test",
  1100 + "help": "Help"
574 }, 1101 },
575 "tenant": { 1102 "tenant": {
576 "tenants": "테넌트", 1103 "tenants": "테넌트",
@@ -589,7 +1116,14 @@ export default function addLocaleKorean(locales) { @@ -589,7 +1116,14 @@ export default function addLocaleKorean(locales) {
589 "delete-tenants-text": "선택된 테넌트가 삭제되고 관련된 모든 정보를 복구할 수 없으므로 주의하십시오.", 1116 "delete-tenants-text": "선택된 테넌트가 삭제되고 관련된 모든 정보를 복구할 수 없으므로 주의하십시오.",
590 "title": "타이틀", 1117 "title": "타이틀",
591 "title-required": "타이틀을 입력하세요.", 1118 "title-required": "타이틀을 입력하세요.",
592 - "description": "설명" 1119 + "description": "설명",
  1120 + "details": "Details", // TODO
  1121 + "events": "Events", // TODO
  1122 + "copyId": "Copy tenant Id", // TODO
  1123 + "idCopiedMessage": "Tenant Id has been copied to clipboard", // TODO
  1124 + "select-tenant": "Select tenant", // TODO
  1125 + "no-tenants-matching": "No tenants matching '{{entity}}' were found.",
  1126 + "tenant-required": "Tenant is required" // TODO
593 }, 1127 },
594 "timeinterval": { 1128 "timeinterval": {
595 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }", 1129 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
@@ -642,7 +1176,18 @@ export default function addLocaleKorean(locales) { @@ -642,7 +1176,18 @@ export default function addLocaleKorean(locales) {
642 "last-name": "성", 1176 "last-name": "성",
643 "description": "설명", 1177 "description": "설명",
644 "default-dashboard": "기본 대시보드", 1178 "default-dashboard": "기본 대시보드",
645 - "always-fullscreen": "항상 전체화면" 1179 + "always-fullscreen": "항상 전체화면",
  1180 + "select-user": "Select user", // TODO
  1181 + "no-users-matching": "No users matching '{{entity}}' were found.", // TODO
  1182 + "user-required": "User is required", // TODO
  1183 + "activation-method": "Activation method", // TODO
  1184 + "display-activation-link": "Display activation link", // TODO
  1185 + "send-activation-mail": "Send activation mail", // TODO
  1186 + "activation-link": "User activation link", // TODO
  1187 + "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :", // TODO
  1188 + "copy-activation-link": "Copy activation link", // TODO
  1189 + "activation-link-copied-message": "User activation link has been copied to clipboard", // TODO
  1190 + "details": "Details" // TODO
646 }, 1191 },
647 "value": { 1192 "value": {
648 "type": "Value type", 1193 "type": "Value type",
@@ -707,6 +1252,18 @@ export default function addLocaleKorean(locales) { @@ -707,6 +1252,18 @@ export default function addLocaleKorean(locales) {
707 "undo": "위젯 변경사항 취소", 1252 "undo": "위젯 변경사항 취소",
708 "export": "위젯 내보내기" 1253 "export": "위젯 내보내기"
709 }, 1254 },
  1255 + "widget-action": { // TODO
  1256 + "header-button": "Widget header button",
  1257 + "open-dashboard-state": "Navigate to new dashboard state",
  1258 + "update-dashboard-state": "Update current dashboard state",
  1259 + "open-dashboard": "Navigate to other dashboard",
  1260 + "custom": "Custom action",
  1261 + "target-dashboard-state": "Target dashboard state",
  1262 + "target-dashboard-state-required": "Target dashboard state is required",
  1263 + "set-entity-from-widget": "Set entity from widget",
  1264 + "target-dashboard": "Target dashboard",
  1265 + "open-right-layout": "Open right dashboard layout (mobile view)"
  1266 + },
710 "widgets-bundle": { 1267 "widgets-bundle": {
711 "current": "현재 번들", 1268 "current": "현재 번들",
712 "widgets-bundles": "위젯 번들", 1269 "widgets-bundles": "위젯 번들",
@@ -770,6 +1327,20 @@ export default function addLocaleKorean(locales) { @@ -770,6 +1327,20 @@ export default function addLocaleKorean(locales) {
770 "widget-type-file": "위젯 타입 파일", 1327 "widget-type-file": "위젯 타입 파일",
771 "invalid-widget-type-file-error": "위젯 타입을 가져오기 할 수 없습니다.: 잘못된 위젯 타입 데이터 구조입니다." 1328 "invalid-widget-type-file-error": "위젯 타입을 가져오기 할 수 없습니다.: 잘못된 위젯 타입 데이터 구조입니다."
772 }, 1329 },
  1330 + "icon": { // TODO
  1331 + "icon": "Icon",
  1332 + "select-icon": "Select icon",
  1333 + "material-icons": "Material icons",
  1334 + "show-all": "Show all icons"
  1335 + },
  1336 + "custom": {
  1337 + "widget-action": {
  1338 + "action-cell-button": "Action cell button",
  1339 + "row-click": "On row click",
  1340 + "marker-click": "On marker click",
  1341 + "tooltip-tag-action": "Tooltip tag action"
  1342 + }
  1343 + },
773 "language": { 1344 "language": {
774 "language": "언어", 1345 "language": "언어",
775 "en_US": "영어", 1346 "en_US": "영어",
@@ -779,5 +1350,5 @@ export default function addLocaleKorean(locales) { @@ -779,5 +1350,5 @@ export default function addLocaleKorean(locales) {
779 "es_ES": "스페인어" 1350 "es_ES": "스페인어"
780 } 1351 }
781 }; 1352 };
782 - angular.extend(locales, {'ko_KR': ko_KR}); 1353 + angular.extend(locales, { 'ko_KR': ko_KR });
783 } 1354 }