Commit ee8f7405fc7129411af2036cf49612074de9d18b

Authored by Igor Kulikov
2 parents 3d9156b5 75c0c5b3

Merge with master. Version set to 2.0.2.

Showing 60 changed files with 3279 additions and 1247 deletions
@@ -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 }
@@ -98,6 +98,159 @@ export default function addLocaleRussian(locales) { @@ -98,6 +98,159 @@ export default function addLocaleRussian(locales) {
98 "enable-tls": "Включить TLS", 98 "enable-tls": "Включить TLS",
99 "send-test-mail": "Отправить пробное письмо" 99 "send-test-mail": "Отправить пробное письмо"
100 }, 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"
  253 + },
101 "attribute": { 254 "attribute": {
102 "attributes": "Атрибуты", 255 "attributes": "Атрибуты",
103 "latest-telemetry": "Последняя телеметрия", 256 "latest-telemetry": "Последняя телеметрия",
@@ -124,6 +277,38 @@ export default function addLocaleRussian(locales) { @@ -124,6 +277,38 @@ export default function addLocaleRussian(locales) {
124 "selected-attributes": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, one {1 атрибут} few {# атрибута} other {# атрибутов} }", 277 "selected-attributes": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, one {1 атрибут} few {# атрибута} other {# атрибутов} }",
125 "selected-telemetry": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, 1 {1 параметр} few {# параметра} other {# параметров} } телеметрии" 278 "selected-telemetry": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, 1 {1 параметр} few {# параметра} other {# параметров} } телеметрии"
126 }, 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"
  311 + },
127 "confirm-on-exit": { 312 "confirm-on-exit": {
128 "message": "У вас есть несохраненные изменения. Вы точно хотите покинуть эту страницу?", 313 "message": "У вас есть несохраненные изменения. Вы точно хотите покинуть эту страницу?",
129 "html-message": "У вас есть несохраненные изменения.<br/>Вы точно хотите покинуть эту страницу?", 314 "html-message": "У вас есть несохраненные изменения.<br/>Вы точно хотите покинуть эту страницу?",
@@ -148,6 +333,11 @@ export default function addLocaleRussian(locales) { @@ -148,6 +333,11 @@ export default function addLocaleRussian(locales) {
148 "enter-password": "Введите пароль", 333 "enter-password": "Введите пароль",
149 "enter-search": "Введите условие поиска" 334 "enter-search": "Введите условие поиска"
150 }, 335 },
  336 + "content-type": { // TODO
  337 + "json": "Json",
  338 + "text": "Text",
  339 + "binary": "Binary (Base64)"
  340 + },
151 "customer": { 341 "customer": {
152 "customers": "Клиенты", 342 "customers": "Клиенты",
153 "management": "Управление клиентами", 343 "management": "Управление клиентами",
@@ -172,11 +362,22 @@ export default function addLocaleRussian(locales) { @@ -172,11 +362,22 @@ export default function addLocaleRussian(locales) {
172 "delete-customers-action-title": "Удалить { count, plural, one {1 клиента} other {# клиентов} } }", 362 "delete-customers-action-title": "Удалить { count, plural, one {1 клиента} other {# клиентов} } }",
173 "delete-customers-text": "Внимание, после подтверждения клиенты и вся связанная с ними информация будут безвозвратно утеряны.", 363 "delete-customers-text": "Внимание, после подтверждения клиенты и вся связанная с ними информация будут безвозвратно утеряны.",
174 "manage-users": "Управление пользователями", 364 "manage-users": "Управление пользователями",
  365 + "manage-assets": "Manage assets", // TODO
175 "manage-devices": "Управление устройствами", 366 "manage-devices": "Управление устройствами",
176 "manage-dashboards": "Управление дашбордами", 367 "manage-dashboards": "Управление дашбордами",
177 "title": "Имя", 368 "title": "Имя",
178 "title-required": "Название обязательно.", 369 "title-required": "Название обязательно.",
179 - "description": "Описание" 370 + "description": "Описание",
  371 + "details": "Details", // TODO
  372 + "events": "Events", // TODO
  373 + "copyId": "Copy customer Id", // TODO
  374 + "idCopiedMessage": "Customer Id has been copied to clipboard", // TODO
  375 + "select-customer": "Select customer", // TODO
  376 + "no-customers-matching": "No customers matching '{{entity}}' were found.", // TODO
  377 + "customer-required": "Customer is required", // TODO
  378 + "select-default-customer": "Select default customer", // TODO
  379 + "default-customer": "Default customer", // TODO
  380 + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" // TODO
180 }, 381 },
181 "datetime": { 382 "datetime": {
182 "date-from": "Дата с", 383 "date-from": "Дата с",
@@ -282,19 +483,41 @@ export default function addLocaleRussian(locales) { @@ -282,19 +483,41 @@ export default function addLocaleRussian(locales) {
282 "configuration-error": "Ошибка конфигурирования", 483 "configuration-error": "Ошибка конфигурирования",
283 "alias-resolution-error-title": "Ошибка конфигурирования псевдонимов дашборда", 484 "alias-resolution-error-title": "Ошибка конфигурирования псевдонимов дашборда",
284 "invalid-aliases-config": "Не удалось найти устройства, соответствующие фильтру псевдонимов.<br/>" + 485 "invalid-aliases-config": "Не удалось найти устройства, соответствующие фильтру псевдонимов.<br/>" +
285 - "Пожалуйста, свяжитесь с администратором для устранения этой проблемы.", 486 + "Пожалуйста, свяжитесь с администратором для устранения этой проблемы.",
286 "select-devices": "Выберите устройства", 487 "select-devices": "Выберите устройства",
287 "assignedToCustomer": "Прикреплен к клиенту", 488 "assignedToCustomer": "Прикреплен к клиенту",
288 "public": "Общедоступный", 489 "public": "Общедоступный",
289 "public-link": "Общедоступная ссылка", 490 "public-link": "Общедоступная ссылка",
290 "copy-public-link": "Скопировать общедоступную ссылку", 491 "copy-public-link": "Скопировать общедоступную ссылку",
291 - "public-link-copied-message": "Общедоступная ссылка на дашборд скопирована в буфер обмена" 492 + "public-link-copied-message": "Общедоступная ссылка на дашборд скопирована в буфер обмена",
  493 + "manage-states": "Manage dashboard states", // TODO
  494 + "states": "Dashboard states", // TODO
  495 + "search-states": "Search dashboard states", // TODO
  496 + "selected-states": "{ count, select, 1 {1 dashboard state} other {# dashboard states} } selected", // TODO
  497 + "edit-state": "Edit dashboard state", // TODO
  498 + "delete-state": "Delete dashboard state", // TODO
  499 + "add-state": "Add dashboard state", // TODO
  500 + "state": "Dashboard state", // TODO
  501 + "state-name": "Name", // TODO
  502 + "state-name-required": "Dashboard state name is required.", // TODO
  503 + "state-id": "State Id", // TODO
  504 + "state-id-required": "Dashboard state id is required.", // TODO
  505 + "state-id-exists": "Dashboard state with the same id is already exists.", // TODO
  506 + "is-root-state": "Root state", // TODO
  507 + "delete-state-title": "Delete dashboard state", // TODO
  508 + "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", // TODO
  509 + "show-details": "Show details", // TODO
  510 + "hide-details": "Hide details", // TODO
  511 + "select-state": "Select target state", // TODO
  512 + "state-controller": "State controller" // TODO
292 }, 513 },
293 "datakey": { 514 "datakey": {
294 "settings": "Настройки", 515 "settings": "Настройки",
295 "advanced": "Дополнительно", 516 "advanced": "Дополнительно",
296 "label": "Метка", 517 "label": "Метка",
297 "color": "Цвет", 518 "color": "Цвет",
  519 + "units": "Special symbol to show next to value", // TODO
  520 + "decimals": "Number of digits after floating point", // TODO
298 "data-generation-func": "Функция генерации данных", 521 "data-generation-func": "Функция генерации данных",
299 "use-data-post-processing-func": "Использовать функцию пост-обработки данных", 522 "use-data-post-processing-func": "Использовать функцию пост-обработки данных",
300 "configuration": "Конфигурация ключа данных", 523 "configuration": "Конфигурация ключа данных",
@@ -302,8 +525,11 @@ export default function addLocaleRussian(locales) { @@ -302,8 +525,11 @@ export default function addLocaleRussian(locales) {
302 "attributes": "Атрибуты", 525 "attributes": "Атрибуты",
303 "timeseries-required": "Выборка по времени обязательна.", 526 "timeseries-required": "Выборка по времени обязательна.",
304 "timeseries-or-attributes-required": "Выборка по времени/атрибуты обязательны.", 527 "timeseries-or-attributes-required": "Выборка по времени/атрибуты обязательны.",
  528 + "maximum-timeseries-or-attributes": "Maximum { count, select, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", // TODO
  529 + "alarm-fields-required": "Alarm fields are required.", // TODO
305 "function-types": "Тип функции", 530 "function-types": "Тип функции",
306 - "function-types-required": "Тип функции обязателен." 531 + "function-types-required": "Тип функции обязателен.",
  532 + "maximum-function-types": "Maximum { count, select, 1 {1 function type is allowed.} other {# function types are allowed} }" // TODO
307 }, 533 },
308 "datasource": { 534 "datasource": {
309 "type": "Тип источника данных", 535 "type": "Тип источника данных",
@@ -408,11 +634,101 @@ export default function addLocaleRussian(locales) { @@ -408,11 +634,101 @@ export default function addLocaleRussian(locales) {
408 "unhandled-error-code": "Код необработанной ошибки: {{errorCode}}", 634 "unhandled-error-code": "Код необработанной ошибки: {{errorCode}}",
409 "unknown-error": "Неизвестная ошибка" 635 "unknown-error": "Неизвестная ошибка"
410 }, 636 },
  637 + "entity": { // TODO
  638 + "entity": "Entity",
  639 + "entities": "Entities",
  640 + "aliases": "Entity aliases",
  641 + "entity-alias": "Entity alias",
  642 + "unable-delete-entity-alias-title": "Unable to delete entity alias",
  643 + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
  644 + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
  645 + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
  646 + "configure-alias": "Configure '{{alias}}' alias",
  647 + "alias": "Alias",
  648 + "alias-required": "Entity alias is required.",
  649 + "remove-alias": "Remove entity alias",
  650 + "add-alias": "Add entity alias",
  651 + "entity-list": "Entity list",
  652 + "entity-type": "Entity type",
  653 + "entity-types": "Entity types",
  654 + "entity-type-list": "Entity type list",
  655 + "any-entity": "Any entity",
  656 + "enter-entity-type": "Enter entity type",
  657 + "no-entities-matching": "No entities matching '{{entity}}' were found.",
  658 + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
  659 + "name-starts-with": "Name starts with",
  660 + "use-entity-name-filter": "Use filter",
  661 + "entity-list-empty": "No entities selected.",
  662 + "entity-type-list-empty": "No entity types selected.",
  663 + "entity-name-filter-required": "Entity name filter is required.",
  664 + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
  665 + "all-subtypes": "All",
  666 + "select-entities": "Select entities",
  667 + "no-aliases-found": "No aliases found.",
  668 + "no-alias-matching": "'{{alias}}' not found.",
  669 + "create-new-alias": "Create a new one!",
  670 + "key": "Key",
  671 + "key-name": "Key name",
  672 + "no-keys-found": "No keys found.",
  673 + "no-key-matching": "'{{key}}' not found.",
  674 + "create-new-key": "Create a new one!",
  675 + "type": "Type",
  676 + "type-required": "Entity type is required.",
  677 + "type-device": "Device",
  678 + "type-devices": "Devices",
  679 + "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
  680 + "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
  681 + "type-asset": "Asset",
  682 + "type-assets": "Assets",
  683 + "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
  684 + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
  685 + "type-rule": "Rule",
  686 + "type-rules": "Rules",
  687 + "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
  688 + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
  689 + "type-plugin": "Plugin",
  690 + "type-plugins": "Plugins",
  691 + "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
  692 + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
  693 + "type-tenant": "Tenant",
  694 + "type-tenants": "Tenants",
  695 + "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
  696 + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
  697 + "type-customer": "Customer",
  698 + "type-customers": "Customers",
  699 + "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
  700 + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
  701 + "type-user": "User",
  702 + "type-users": "Users",
  703 + "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
  704 + "user-name-starts-with": "Users whose names start with '{{prefix}}'",
  705 + "type-dashboard": "Dashboard",
  706 + "type-dashboards": "Dashboards",
  707 + "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
  708 + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
  709 + "type-alarm": "Alarm",
  710 + "type-alarms": "Alarms",
  711 + "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
  712 + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  713 + "type-rulechain": "Rule chain",
  714 + "type-rulechains": "Rule chains",
  715 + "list-of-rulechains": "{ count, select, 1 {One rule chain} other {List of # rule chains} }",
  716 + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
  717 + "type-current-customer": "Current Customer",
  718 + "search": "Search entities",
  719 + "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
  720 + "entity-name": "Entity name",
  721 + "details": "Entity details",
  722 + "no-entities-prompt": "No entities found",
  723 + "no-data": "No data to display"
  724 + },
411 "event": { 725 "event": {
412 "event-type": "Тип события", 726 "event-type": "Тип события",
413 "type-error": "Ошибка", 727 "type-error": "Ошибка",
414 "type-lc-event": "Событие жизненного цикла", 728 "type-lc-event": "Событие жизненного цикла",
415 "type-stats": "Статистика", 729 "type-stats": "Статистика",
  730 + "type-debug-rule-node": "Debug", // TODO
  731 + "type-debug-rule-chain": "Debug", // TODO
416 "no-events-prompt": "События не найдены", 732 "no-events-prompt": "События не найдены",
417 "error": "Ошибка", 733 "error": "Ошибка",
418 "alarm": "Аварийное оповещение", 734 "alarm": "Аварийное оповещение",
@@ -420,6 +736,14 @@ export default function addLocaleRussian(locales) { @@ -420,6 +736,14 @@ export default function addLocaleRussian(locales) {
420 "server": "Сервер", 736 "server": "Сервер",
421 "body": "Тело", 737 "body": "Тело",
422 "method": "Метод", 738 "method": "Метод",
  739 + "type": "Type", // TODO
  740 + "entity": "Entity", // TODO
  741 + "message-id": "Message Id", // TODO
  742 + "message-type": "Message Type", // TODO
  743 + "data-type": "Data Type", // TODO
  744 + "relation-type": "Relation Type", // TODO
  745 + "metadata": "Metadata", // TODO
  746 + "data": "Data", // TODO
423 "event": "Событие", 747 "event": "Событие",
424 "status": "Статус", 748 "status": "Статус",
425 "success": "Успех", 749 "success": "Успех",
@@ -427,6 +751,163 @@ export default function addLocaleRussian(locales) { @@ -427,6 +751,163 @@ export default function addLocaleRussian(locales) {
427 "messages-processed": "Сообщения обработаны", 751 "messages-processed": "Сообщения обработаны",
428 "errors-occurred": "Возникли ошибки" 752 "errors-occurred": "Возникли ошибки"
429 }, 753 },
  754 + "extension": { // TODO
  755 + "extensions": "Extensions",
  756 + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
  757 + "type": "Type",
  758 + "key": "Key",
  759 + "value": "Value",
  760 + "id": "Id",
  761 + "extension-id": "Extension id",
  762 + "extension-type": "Extension type",
  763 + "transformer-json": "JSON *",
  764 + "unique-id-required": "Current extension id already exists.",
  765 + "delete": "Delete extension",
  766 + "add": "Add extension",
  767 + "edit": "Edit extension",
  768 + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
  769 + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
  770 + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
  771 + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
  772 + "converters": "Converters",
  773 + "converter-id": "Converter id",
  774 + "configuration": "Configuration",
  775 + "converter-configurations": "Converter configurations",
  776 + "token": "Security token",
  777 + "add-converter": "Add converter",
  778 + "add-config": "Add converter configuration",
  779 + "device-name-expression": "Device name expression",
  780 + "device-type-expression": "Device type expression",
  781 + "custom": "Custom",
  782 + "to-double": "To Double",
  783 + "transformer": "Transformer",
  784 + "json-required": "Transformer json is required.",
  785 + "json-parse": "Unable to parse transformer json.",
  786 + "attributes": "Attributes",
  787 + "add-attribute": "Add attribute",
  788 + "add-map": "Add mapping element",
  789 + "timeseries": "Timeseries",
  790 + "add-timeseries": "Add timeseries",
  791 + "field-required": "Field is required",
  792 + "brokers": "Brokers",
  793 + "add-broker": "Add broker",
  794 + "host": "Host",
  795 + "port": "Port",
  796 + "port-range": "Port should be in a range from 1 to 65535.",
  797 + "ssl": "Ssl",
  798 + "credentials": "Credentials",
  799 + "username": "Username",
  800 + "password": "Password",
  801 + "retry-interval": "Retry interval in milliseconds",
  802 + "anonymous": "Anonymous",
  803 + "basic": "Basic",
  804 + "pem": "PEM",
  805 + "ca-cert": "CA certificate file *",
  806 + "private-key": "Private key file *",
  807 + "cert": "Certificate file *",
  808 + "no-file": "No file selected.",
  809 + "drop-file": "Drop a file or click to select a file to upload.",
  810 + "mapping": "Mapping",
  811 + "topic-filter": "Topic filter",
  812 + "converter-type": "Converter type",
  813 + "converter-json": "Json",
  814 + "json-name-expression": "Device name json expression",
  815 + "topic-name-expression": "Device name topic expression",
  816 + "json-type-expression": "Device type json expression",
  817 + "topic-type-expression": "Device type topic expression",
  818 + "attribute-key-expression": "Attribute key expression",
  819 + "attr-json-key-expression": "Attribute key json expression",
  820 + "attr-topic-key-expression": "Attribute key topic expression",
  821 + "request-id-expression": "Request id expression",
  822 + "request-id-json-expression": "Request id json expression",
  823 + "request-id-topic-expression": "Request id topic expression",
  824 + "response-topic-expression": "Response topic expression",
  825 + "value-expression": "Value expression",
  826 + "topic": "Topic",
  827 + "timeout": "Timeout in milliseconds",
  828 + "converter-json-required": "Converter json is required.",
  829 + "converter-json-parse": "Unable to parse converter json.",
  830 + "filter-expression": "Filter expression",
  831 + "connect-requests": "Connect requests",
  832 + "add-connect-request": "Add connect request",
  833 + "disconnect-requests": "Disconnect requests",
  834 + "add-disconnect-request": "Add disconnect request",
  835 + "attribute-requests": "Attribute requests",
  836 + "add-attribute-request": "Add attribute request",
  837 + "attribute-updates": "Attribute updates",
  838 + "add-attribute-update": "Add attribute update",
  839 + "server-side-rpc": "Server side RPC",
  840 + "add-server-side-rpc-request": "Add server-side RPC request",
  841 + "device-name-filter": "Device name filter",
  842 + "attribute-filter": "Attribute filter",
  843 + "method-filter": "Method filter",
  844 + "request-topic-expression": "Request topic expression",
  845 + "response-timeout": "Response timeout in milliseconds",
  846 + "topic-expression": "Topic expression",
  847 + "client-scope": "Client scope",
  848 + "add-device": "Add device",
  849 + "opc-server": "Servers",
  850 + "opc-add-server": "Add server",
  851 + "opc-add-server-prompt": "Please add server",
  852 + "opc-application-name": "Application name",
  853 + "opc-application-uri": "Application uri",
  854 + "opc-scan-period-in-seconds": "Scan period in seconds",
  855 + "opc-security": "Security",
  856 + "opc-identity": "Identity",
  857 + "opc-keystore": "Keystore",
  858 + "opc-type": "Type",
  859 + "opc-keystore-type": "Type",
  860 + "opc-keystore-location": "Location *",
  861 + "opc-keystore-password": "Password",
  862 + "opc-keystore-alias": "Alias",
  863 + "opc-keystore-key-password": "Key password",
  864 + "opc-device-node-pattern": "Device node pattern",
  865 + "opc-device-name-pattern": "Device name pattern",
  866 + "modbus-server": "Servers/slaves",
  867 + "modbus-add-server": "Add server/slave",
  868 + "modbus-add-server-prompt": "Please add server/slave",
  869 + "modbus-transport": "Transport",
  870 + "modbus-port-name": "Serial port name",
  871 + "modbus-encoding": "Encoding",
  872 + "modbus-parity": "Parity",
  873 + "modbus-baudrate": "Baud rate",
  874 + "modbus-databits": "Data bits",
  875 + "modbus-stopbits": "Stop bits",
  876 + "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
  877 + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
  878 + "modbus-unit-id": "Unit ID",
  879 + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
  880 + "modbus-device-name": "Device name",
  881 + "modbus-poll-period": "Poll period (ms)",
  882 + "modbus-attributes-poll-period": "Attributes poll period (ms)",
  883 + "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
  884 + "modbus-poll-period-range": "Poll period should be positive value.",
  885 + "modbus-tag": "Tag",
  886 + "modbus-function": "Function",
  887 + "modbus-register-address": "Register address",
  888 + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
  889 + "modbus-register-bit-index": "Bit index",
  890 + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
  891 + "modbus-register-count": "Register count",
  892 + "modbus-register-count-range": "Register count should be a positive value.",
  893 + "modbus-byte-order": "Byte order",
  894 +
  895 + "sync": {
  896 + "status": "Status",
  897 + "sync": "Sync",
  898 + "not-sync": "Not sync",
  899 + "last-sync-time": "Last sync time",
  900 + "not-available": "Not available"
  901 + },
  902 +
  903 + "export-extensions-configuration": "Export extensions configuration",
  904 + "import-extensions-configuration": "Import extensions configuration",
  905 + "import-extensions": "Import extensions",
  906 + "import-extension": "Import extension",
  907 + "export-extension": "Export extension",
  908 + "file": "Extensions file",
  909 + "invalid-file-error": "Invalid extension file"
  910 + },
430 "fullscreen": { 911 "fullscreen": {
431 "expand": "Во весь экран", 912 "expand": "Во весь экран",
432 "exit": "Выйти из полноэкранного режима", 913 "exit": "Выйти из полноэкранного режима",
@@ -469,7 +950,24 @@ export default function addLocaleRussian(locales) { @@ -469,7 +950,24 @@ export default function addLocaleRussian(locales) {
469 }, 950 },
470 "js-func": { 951 "js-func": {
471 "no-return-error": "Функция должна возвращать значение!", 952 "no-return-error": "Функция должна возвращать значение!",
472 - "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!" 953 + "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!",
  954 + "tidy": "Tidy" // TODO
  955 + },
  956 + "key-val": { // TODO
  957 + "key": "Key",
  958 + "value": "Value",
  959 + "remove-entry": "Remove entry",
  960 + "add-entry": "Add entry",
  961 + "no-data": "No entries"
  962 + },
  963 + "layout": { // TODO
  964 + "layout": "Layout",
  965 + "manage": "Manage layouts",
  966 + "settings": "Layout settings",
  967 + "color": "Color",
  968 + "main": "Main",
  969 + "right": "Right",
  970 + "select": "Select target layout"
473 }, 971 },
474 "legend": { 972 "legend": {
475 "position": "Расположение легенды", 973 "position": "Расположение легенды",
@@ -500,45 +998,6 @@ export default function addLocaleRussian(locales) { @@ -500,45 +998,6 @@ export default function addLocaleRussian(locales) {
500 "password-link-sent-message": "Ссылка для сброса пароля была успешно отправлена!", 998 "password-link-sent-message": "Ссылка для сброса пароля была успешно отправлена!",
501 "email": "Эл. адрес" 999 "email": "Эл. адрес"
502 }, 1000 },
503 - "plugin": {  
504 - "plugins": "Плагины",  
505 - "delete": "Удалить плагин",  
506 - "activate": "Активировать плагин",  
507 - "suspend": "Приостановить плагин",  
508 - "active": "Активный",  
509 - "suspended": "Приостановлен",  
510 - "name": "Название",  
511 - "name-required": "Название обязательно.",  
512 - "description": "Описание",  
513 - "add": "Добавить плагин",  
514 - "delete-plugin-title": "Вы точно хотите удалить плагин '{{pluginName}}'?",  
515 - "delete-plugin-text": "Внимание, после подтверждения плагин и все связанные с ним данные будут безвозвратно утеряны.",  
516 - "delete-plugins-title": "Вы точно хотите удалить { count, plural, one {1 плагин} few {# плагина} other {# плагинов} }?",  
517 - "delete-plugins-action-title": "Удалить { count, plural, one {1 плагин} few {# плагина} other {# плагинов} } }",  
518 - "delete-plugins-text": "Внимание, после подтверждения выбранные плагины и все связанные с ними данные будут безвозвратно утеряны.",  
519 - "add-plugin-text": "Добавить новый плагин",  
520 - "no-plugins-text": "Плагины не найдены",  
521 - "plugin-details": "Подробности о плагине",  
522 - "api-token": "API токен",  
523 - "api-token-required": "API токен обязателен.",  
524 - "type": "Тип плагина",  
525 - "type-required": "Тип плагина обязателен.",  
526 - "configuration": "Настройки плагина",  
527 - "system": "Системный",  
528 - "select-plugin": "Выберите плагин",  
529 - "plugin": "Плагин",  
530 - "no-plugins-matching": "Плагин '{{entity}}' не найден.",  
531 - "plugin-required": "Плагин обязателен.",  
532 - "plugin-require-match": "Пожалуйста, выберите существующий плагин.",  
533 - "events": "События",  
534 - "details": "Подробности",  
535 - "import": "Импортировать плагин",  
536 - "export": "Экспортировать плагин",  
537 - "export-failed-error": "Не удалось экспортировать плагин: {{error}}",  
538 - "create-new-plugin": "Создать новый плагин",  
539 - "plugin-file": "Файл плагина",  
540 - "invalid-plugin-file-error": "Не удалось импортировать плагин: неизвестная схема данных плагина."  
541 - },  
542 "position": { 1001 "position": {
543 "top": "Верх", 1002 "top": "Верх",
544 "bottom": "Низ", 1003 "bottom": "Низ",
@@ -550,60 +1009,139 @@ export default function addLocaleRussian(locales) { @@ -550,60 +1009,139 @@ export default function addLocaleRussian(locales) {
550 "change-password": "Изменить пароль", 1009 "change-password": "Изменить пароль",
551 "current-password": "Текущий пароль" 1010 "current-password": "Текущий пароль"
552 }, 1011 },
553 - "rule": {  
554 - "rules": "Правила",  
555 - "delete": "Удалить правило",  
556 - "activate": "Активировать правило",  
557 - "suspend": "Приостановить правило",  
558 - "active": "Активное",  
559 - "suspended": "Приостановлены",  
560 - "name": "Название",  
561 - "name-required": "Название обязательно.",  
562 - "description": "Описание",  
563 - "add": "Добавить правило",  
564 - "delete-rule-title": "Вы точно хотите удалить правило '{{ruleName}}'?",  
565 - "delete-rule-text": "Внимание, после подтверждения правило и все связанные с ним данные будут безвозвратно утеряны.",  
566 - "delete-rules-title": "Вы точно хотите удалить { count, plural, one {1 правило} few {# правила} other {# правил} }?",  
567 - "delete-rules-action-title": "Удалить { count, plural, one {1 правило} few {# правила} other {# правил} }",  
568 - "delete-rules-text": "Внимание, после подтверждения выбранные правила и все связанные с ними данные будут безвозвратно утеряны.",  
569 - "add-rule-text": "Добавить новое правило",  
570 - "no-rules-text": "Правила не найдены",  
571 - "rule-details": "Подробности о правиле",  
572 - "filters": "Фильтры",  
573 - "filter": "Фильтр",  
574 - "add-filter-prompt": "Пожалуйста, добавьте фильтр",  
575 - "remove-filter": "Удалить фильтр",  
576 - "add-filter": "Добавить фильтр",  
577 - "filter-name": "Название фильтра",  
578 - "filter-type": "Тип фильтра",  
579 - "edit-filter": "Редактировать фильтр",  
580 - "view-filter": "Просмотреть фильтр",  
581 - "component-name": "Название",  
582 - "component-name-required": "Название обязательно.",  
583 - "component-type": "Тип",  
584 - "component-type-required": "Тип обязателен.",  
585 - "processor": "Обработчик",  
586 - "no-processor-configured": "Обработчики не сконфигурированы",  
587 - "create-processor": "Создать обработчик",  
588 - "processor-name": "Название обработчика",  
589 - "processor-type": "Тип обработчика",  
590 - "plugin-action": "Действие плагина",  
591 - "action-name": "Название действия",  
592 - "action-type": "Тип действия",  
593 - "create-action-prompt": "Пожалуйста, создайте действие",  
594 - "create-action": "Создать действие",  
595 - "details": "Подробности",  
596 - "events": "События",  
597 - "system": "Системное",  
598 - "import": "Импортировать правило",  
599 - "export": "Экспортировать правило",  
600 - "export-failed-error": "Не удалось экспортировать правило: {{error}}",  
601 - "create-new-rule": "Создать новое правило",  
602 - "rule-file": "Файл правила",  
603 - "invalid-rule-file-error": "Не удалось импортировать правило: неизвестная схема данных правила." 1012 + "relation": { // TODO
  1013 + "relations": "Relations",
  1014 + "direction": "Direction",
  1015 + "search-direction": {
  1016 + "FROM": "From",
  1017 + "TO": "To"
  1018 + },
  1019 + "direction-type": {
  1020 + "FROM": "from",
  1021 + "TO": "to"
  1022 + },
  1023 + "from-relations": "Outbound relations",
  1024 + "to-relations": "Inbound relations",
  1025 + "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
  1026 + "type": "Type",
  1027 + "to-entity-type": "To entity type",
  1028 + "to-entity-name": "To entity name",
  1029 + "from-entity-type": "From entity type",
  1030 + "from-entity-name": "From entity name",
  1031 + "to-entity": "To entity",
  1032 + "from-entity": "From entity",
  1033 + "delete": "Delete relation",
  1034 + "relation-type": "Relation type",
  1035 + "relation-type-required": "Relation type is required.",
  1036 + "any-relation-type": "Any type",
  1037 + "add": "Add relation",
  1038 + "edit": "Edit relation",
  1039 + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
  1040 + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
  1041 + "delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  1042 + "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.",
  1043 + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
  1044 + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
  1045 + "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  1046 + "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.",
  1047 + "remove-relation-filter": "Remove relation filter",
  1048 + "add-relation-filter": "Add relation filter",
  1049 + "any-relation": "Any relation",
  1050 + "relation-filters": "Relation filters",
  1051 + "additional-info": "Additional info (JSON)",
  1052 + "invalid-additional-info": "Unable to parse additional info json."
  1053 + },
  1054 + "rulechain": { // TODO
  1055 + "rulechain": "Rule chain",
  1056 + "rulechains": "Rule chains",
  1057 + "root": "Root",
  1058 + "delete": "Delete rule chain",
  1059 + "name": "Name",
  1060 + "name-required": "Name is required.",
  1061 + "description": "Description",
  1062 + "add": "Add Rule Chain",
  1063 + "set-root": "Make rule chain root",
  1064 + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
  1065 + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
  1066 + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
  1067 + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
  1068 + "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
  1069 + "delete-rulechains-action-title": "Delete { count, select, 1 {1 rule chain} other {# rule chains} }",
  1070 + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
  1071 + "add-rulechain-text": "Add new rule chain",
  1072 + "no-rulechains-text": "No rule chains found",
  1073 + "rulechain-details": "Rule chain details",
  1074 + "details": "Details",
  1075 + "events": "Events",
  1076 + "system": "System",
  1077 + "import": "Import rule chain",
  1078 + "export": "Export rule chain",
  1079 + "export-failed-error": "Unable to export rule chain: {{error}}",
  1080 + "create-new-rulechain": "Create new rule chain",
  1081 + "rulechain-file": "Rule chain file",
  1082 + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
  1083 + "copyId": "Copy rule chain Id",
  1084 + "idCopiedMessage": "Rule chain Id has been copied to clipboard",
  1085 + "select-rulechain": "Select rule chain",
  1086 + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
  1087 + "rulechain-required": "Rule chain is required",
  1088 + "management": "Rules management",
  1089 + "debug-mode": "Debug mode"
604 }, 1090 },
605 - "rule-plugin": {  
606 - "management": "Управление плагинами и правилами" 1091 + "rulenode": { // TODO
  1092 + "details": "Details",
  1093 + "events": "Events",
  1094 + "search": "Search nodes",
  1095 + "open-node-library": "Open node library",
  1096 + "add": "Add rule node",
  1097 + "name": "Name",
  1098 + "name-required": "Name is required.",
  1099 + "type": "Type",
  1100 + "description": "Description",
  1101 + "delete": "Delete rule node",
  1102 + "select-all-objects": "Select all nodes and connections",
  1103 + "deselect-all-objects": "Deselect all nodes and connections",
  1104 + "delete-selected-objects": "Delete selected nodes and connections",
  1105 + "delete-selected": "Delete selected",
  1106 + "select-all": "Select all",
  1107 + "copy-selected": "Copy selected",
  1108 + "deselect-all": "Deselect all",
  1109 + "rulenode-details": "Rule node details",
  1110 + "debug-mode": "Debug mode",
  1111 + "configuration": "Configuration",
  1112 + "link": "Link",
  1113 + "link-details": "Rule node link details",
  1114 + "add-link": "Add link",
  1115 + "link-label": "Link label",
  1116 + "link-label-required": "Link label is required.",
  1117 + "custom-link-label": "Custom link label",
  1118 + "custom-link-label-required": "Custom link label is required.",
  1119 + "type-filter": "Filter",
  1120 + "type-filter-details": "Filter incoming messages with configured conditions",
  1121 + "type-enrichment": "Enrichment",
  1122 + "type-enrichment-details": "Add additional information into Message Metadata",
  1123 + "type-transformation": "Transformation",
  1124 + "type-transformation-details": "Change Message payload and Metadata",
  1125 + "type-action": "Action",
  1126 + "type-action-details": "Perform special action",
  1127 + "type-external": "External",
  1128 + "type-external-details": "Interacts with external system",
  1129 + "type-rule-chain": "Rule Chain",
  1130 + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
  1131 + "type-input": "Input",
  1132 + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
  1133 + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
  1134 + "ui-resources-load-error": "Failed to load configuration ui resources.",
  1135 + "invalid-target-rulechain": "Unable to resolve target rule chain!",
  1136 + "test-script-function": "Test script function",
  1137 + "message": "Message",
  1138 + "message-type": "Message type",
  1139 + "message-type-required": "Message type is required",
  1140 + "metadata": "Metadata",
  1141 + "metadata-required": "Metadata entries can't be empty.",
  1142 + "output": "Output",
  1143 + "test": "Test",
  1144 + "help": "Help"
607 }, 1145 },
608 "tenant": { 1146 "tenant": {
609 "tenants": "Владельцы", 1147 "tenants": "Владельцы",
@@ -740,6 +1278,18 @@ export default function addLocaleRussian(locales) { @@ -740,6 +1278,18 @@ export default function addLocaleRussian(locales) {
740 "undo": "Откатить изменения в виджете", 1278 "undo": "Откатить изменения в виджете",
741 "export": "Экспортировать виджет" 1279 "export": "Экспортировать виджет"
742 }, 1280 },
  1281 + "widget-action": { // TODO
  1282 + "header-button": "Widget header button",
  1283 + "open-dashboard-state": "Navigate to new dashboard state",
  1284 + "update-dashboard-state": "Update current dashboard state",
  1285 + "open-dashboard": "Navigate to other dashboard",
  1286 + "custom": "Custom action",
  1287 + "target-dashboard-state": "Target dashboard state",
  1288 + "target-dashboard-state-required": "Target dashboard state is required",
  1289 + "set-entity-from-widget": "Set entity from widget",
  1290 + "target-dashboard": "Target dashboard",
  1291 + "open-right-layout": "Open right dashboard layout (mobile view)"
  1292 + },
743 "widgets-bundle": { 1293 "widgets-bundle": {
744 "current": "Текущий набор", 1294 "current": "Текущий набор",
745 "widgets-bundles": "Наборы виджетов", 1295 "widgets-bundles": "Наборы виджетов",
@@ -803,6 +1353,20 @@ export default function addLocaleRussian(locales) { @@ -803,6 +1353,20 @@ export default function addLocaleRussian(locales) {
803 "widget-type-file": "Файл типа виджета", 1353 "widget-type-file": "Файл типа виджета",
804 "invalid-widget-type-file-error": "Не удалось импортировать виджет: неизвестная схема данных типа виджета." 1354 "invalid-widget-type-file-error": "Не удалось импортировать виджет: неизвестная схема данных типа виджета."
805 }, 1355 },
  1356 + "icon": { // TODO
  1357 + "icon": "Icon",
  1358 + "select-icon": "Select icon",
  1359 + "material-icons": "Material icons",
  1360 + "show-all": "Show all icons"
  1361 + },
  1362 + "custom": { // TODO
  1363 + "widget-action": {
  1364 + "action-cell-button": "Action cell button",
  1365 + "row-click": "On row click",
  1366 + "marker-click": "On marker click",
  1367 + "tooltip-tag-action": "Tooltip tag action"
  1368 + }
  1369 + },
806 "language": { 1370 "language": {
807 "language": "Язык", 1371 "language": "Язык",
808 "en_US": "Английский", 1372 "en_US": "Английский",
@@ -813,5 +1377,5 @@ export default function addLocaleRussian(locales) { @@ -813,5 +1377,5 @@ export default function addLocaleRussian(locales) {
813 1377
814 } 1378 }
815 }; 1379 };
816 - angular.extend(locales, {'ru_RU': ru_RU}); 1380 + angular.extend(locales, { 'ru_RU': ru_RU });
817 } 1381 }
@@ -336,6 +336,11 @@ export default function addLocaleChinese(locales) { @@ -336,6 +336,11 @@ export default function addLocaleChinese(locales) {
336 "enter-password": "输入密码", 336 "enter-password": "输入密码",
337 "enter-search": "输入检索条件" 337 "enter-search": "输入检索条件"
338 }, 338 },
  339 + "content-type": { // TODO
  340 + "json": "Json",
  341 + "text": "Text",
  342 + "binary": "Binary (Base64)"
  343 + },
339 "customer": { 344 "customer": {
340 "customer": "客户", 345 "customer": "客户",
341 "customers": "客户", 346 "customers": "客户",
@@ -754,6 +759,163 @@ export default function addLocaleChinese(locales) { @@ -754,6 +759,163 @@ export default function addLocaleChinese(locales) {
754 "messages-processed": "消息处理", 759 "messages-processed": "消息处理",
755 "errors-occurred": "错误发生" 760 "errors-occurred": "错误发生"
756 }, 761 },
  762 + "extension": { // TODO
  763 + "extensions": "Extensions",
  764 + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
  765 + "type": "Type",
  766 + "key": "Key",
  767 + "value": "Value",
  768 + "id": "Id",
  769 + "extension-id": "Extension id",
  770 + "extension-type": "Extension type",
  771 + "transformer-json": "JSON *",
  772 + "unique-id-required": "Current extension id already exists.",
  773 + "delete": "Delete extension",
  774 + "add": "Add extension",
  775 + "edit": "Edit extension",
  776 + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
  777 + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
  778 + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
  779 + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
  780 + "converters": "Converters",
  781 + "converter-id": "Converter id",
  782 + "configuration": "Configuration",
  783 + "converter-configurations": "Converter configurations",
  784 + "token": "Security token",
  785 + "add-converter": "Add converter",
  786 + "add-config": "Add converter configuration",
  787 + "device-name-expression": "Device name expression",
  788 + "device-type-expression": "Device type expression",
  789 + "custom": "Custom",
  790 + "to-double": "To Double",
  791 + "transformer": "Transformer",
  792 + "json-required": "Transformer json is required.",
  793 + "json-parse": "Unable to parse transformer json.",
  794 + "attributes": "Attributes",
  795 + "add-attribute": "Add attribute",
  796 + "add-map": "Add mapping element",
  797 + "timeseries": "Timeseries",
  798 + "add-timeseries": "Add timeseries",
  799 + "field-required": "Field is required",
  800 + "brokers": "Brokers",
  801 + "add-broker": "Add broker",
  802 + "host": "Host",
  803 + "port": "Port",
  804 + "port-range": "Port should be in a range from 1 to 65535.",
  805 + "ssl": "Ssl",
  806 + "credentials": "Credentials",
  807 + "username": "Username",
  808 + "password": "Password",
  809 + "retry-interval": "Retry interval in milliseconds",
  810 + "anonymous": "Anonymous",
  811 + "basic": "Basic",
  812 + "pem": "PEM",
  813 + "ca-cert": "CA certificate file *",
  814 + "private-key": "Private key file *",
  815 + "cert": "Certificate file *",
  816 + "no-file": "No file selected.",
  817 + "drop-file": "Drop a file or click to select a file to upload.",
  818 + "mapping": "Mapping",
  819 + "topic-filter": "Topic filter",
  820 + "converter-type": "Converter type",
  821 + "converter-json": "Json",
  822 + "json-name-expression": "Device name json expression",
  823 + "topic-name-expression": "Device name topic expression",
  824 + "json-type-expression": "Device type json expression",
  825 + "topic-type-expression": "Device type topic expression",
  826 + "attribute-key-expression": "Attribute key expression",
  827 + "attr-json-key-expression": "Attribute key json expression",
  828 + "attr-topic-key-expression": "Attribute key topic expression",
  829 + "request-id-expression": "Request id expression",
  830 + "request-id-json-expression": "Request id json expression",
  831 + "request-id-topic-expression": "Request id topic expression",
  832 + "response-topic-expression": "Response topic expression",
  833 + "value-expression": "Value expression",
  834 + "topic": "Topic",
  835 + "timeout": "Timeout in milliseconds",
  836 + "converter-json-required": "Converter json is required.",
  837 + "converter-json-parse": "Unable to parse converter json.",
  838 + "filter-expression": "Filter expression",
  839 + "connect-requests": "Connect requests",
  840 + "add-connect-request": "Add connect request",
  841 + "disconnect-requests": "Disconnect requests",
  842 + "add-disconnect-request": "Add disconnect request",
  843 + "attribute-requests": "Attribute requests",
  844 + "add-attribute-request": "Add attribute request",
  845 + "attribute-updates": "Attribute updates",
  846 + "add-attribute-update": "Add attribute update",
  847 + "server-side-rpc": "Server side RPC",
  848 + "add-server-side-rpc-request": "Add server-side RPC request",
  849 + "device-name-filter": "Device name filter",
  850 + "attribute-filter": "Attribute filter",
  851 + "method-filter": "Method filter",
  852 + "request-topic-expression": "Request topic expression",
  853 + "response-timeout": "Response timeout in milliseconds",
  854 + "topic-expression": "Topic expression",
  855 + "client-scope": "Client scope",
  856 + "add-device": "Add device",
  857 + "opc-server": "Servers",
  858 + "opc-add-server": "Add server",
  859 + "opc-add-server-prompt": "Please add server",
  860 + "opc-application-name": "Application name",
  861 + "opc-application-uri": "Application uri",
  862 + "opc-scan-period-in-seconds": "Scan period in seconds",
  863 + "opc-security": "Security",
  864 + "opc-identity": "Identity",
  865 + "opc-keystore": "Keystore",
  866 + "opc-type": "Type",
  867 + "opc-keystore-type": "Type",
  868 + "opc-keystore-location": "Location *",
  869 + "opc-keystore-password": "Password",
  870 + "opc-keystore-alias": "Alias",
  871 + "opc-keystore-key-password": "Key password",
  872 + "opc-device-node-pattern": "Device node pattern",
  873 + "opc-device-name-pattern": "Device name pattern",
  874 + "modbus-server": "Servers/slaves",
  875 + "modbus-add-server": "Add server/slave",
  876 + "modbus-add-server-prompt": "Please add server/slave",
  877 + "modbus-transport": "Transport",
  878 + "modbus-port-name": "Serial port name",
  879 + "modbus-encoding": "Encoding",
  880 + "modbus-parity": "Parity",
  881 + "modbus-baudrate": "Baud rate",
  882 + "modbus-databits": "Data bits",
  883 + "modbus-stopbits": "Stop bits",
  884 + "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
  885 + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
  886 + "modbus-unit-id": "Unit ID",
  887 + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
  888 + "modbus-device-name": "Device name",
  889 + "modbus-poll-period": "Poll period (ms)",
  890 + "modbus-attributes-poll-period": "Attributes poll period (ms)",
  891 + "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
  892 + "modbus-poll-period-range": "Poll period should be positive value.",
  893 + "modbus-tag": "Tag",
  894 + "modbus-function": "Function",
  895 + "modbus-register-address": "Register address",
  896 + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
  897 + "modbus-register-bit-index": "Bit index",
  898 + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
  899 + "modbus-register-count": "Register count",
  900 + "modbus-register-count-range": "Register count should be a positive value.",
  901 + "modbus-byte-order": "Byte order",
  902 +
  903 + "sync": {
  904 + "status": "Status",
  905 + "sync": "Sync",
  906 + "not-sync": "Not sync",
  907 + "last-sync-time": "Last sync time",
  908 + "not-available": "Not available"
  909 + },
  910 +
  911 + "export-extensions-configuration": "Export extensions configuration",
  912 + "import-extensions-configuration": "Import extensions configuration",
  913 + "import-extensions": "Import extensions",
  914 + "import-extension": "Import extension",
  915 + "export-extension": "Export extension",
  916 + "file": "Extensions file",
  917 + "invalid-file-error": "Invalid extension file"
  918 + },
757 "fullscreen": { 919 "fullscreen": {
758 "expand": "展开到全屏", 920 "expand": "展开到全屏",
759 "exit": "退出全屏", 921 "exit": "退出全屏",
@@ -798,6 +960,13 @@ export default function addLocaleChinese(locales) { @@ -798,6 +960,13 @@ export default function addLocaleChinese(locales) {
798 "no-return-error": "函数必须返回值!", 960 "no-return-error": "函数必须返回值!",
799 "return-type-mismatch": "函数必须返回 '{{type}}' 类型的值!" 961 "return-type-mismatch": "函数必须返回 '{{type}}' 类型的值!"
800 }, 962 },
  963 + "key-val": { // TODO
  964 + "key": "Key",
  965 + "value": "Value",
  966 + "remove-entry": "Remove entry",
  967 + "add-entry": "Add entry",
  968 + "no-data": "No entries"
  969 + },
801 "layout": { 970 "layout": {
802 "layout": "布局", 971 "layout": "布局",
803 "manage": "布局管理", 972 "manage": "布局管理",
@@ -836,47 +1005,6 @@ export default function addLocaleChinese(locales) { @@ -836,47 +1005,6 @@ export default function addLocaleChinese(locales) {
836 "password-link-sent-message": "密码重置链接已成功发送!", 1005 "password-link-sent-message": "密码重置链接已成功发送!",
837 "email": "电子邮件" 1006 "email": "电子邮件"
838 }, 1007 },
839 - "plugin": {  
840 - "plugins": "插件",  
841 - "delete": "删除插件",  
842 - "activate": "激活插件",  
843 - "suspend": "暂停插件",  
844 - "active": "激活",  
845 - "suspended": "暂停",  
846 - "name": "名称",  
847 - "name-required": "名称必填。",  
848 - "description": "描述",  
849 - "add": "添加插件",  
850 - "delete-plugin-title": "你确定要删除插件 '{{pluginName}}' 吗?",  
851 - "delete-plugin-text": "小心!确认后,插件和所有相关数据将不可恢复。",  
852 - "delete-plugins-title": "你确定你要删除 { count, select, 1 {1 插件} other {# 插件} } 吗?",  
853 - "delete-plugins-action-title": "删除 { count, select, 1 {1 插件} other {# 插件} }",  
854 - "delete-plugins-text": "小心!确认后,所有选定的插件将被删除,所有相关数据将不可恢复。",  
855 - "add-plugin-text": "添加新的插件",  
856 - "no-plugins-text": "没有找到插件",  
857 - "plugin-details": "插件详细信息",  
858 - "api-token": "API令牌",  
859 - "api-token-required": "API令牌必填。",  
860 - "type": "插件类型",  
861 - "type-required": "插件类型必填。",  
862 - "configuration": "插件配置",  
863 - "system": "系统",  
864 - "select-plugin": "选择插件",  
865 - "plugin": "插件",  
866 - "no-plugins-matching": "没有找到匹配'{{entity}}'的插件。",  
867 - "plugin-required": "插件必填。",  
868 - "plugin-require-match": "请选择一个现有的插件。",  
869 - "events": "事件",  
870 - "details": "详情",  
871 - "import": "导入插件",  
872 - "export": "导出插件",  
873 - "export-failed-error": "无法导出插件:{{error}}",  
874 - "create-new-plugin": "创建新的插件",  
875 - "plugin-file": "插件文件",  
876 - "invalid-plugin-file-error": "无法导入插件:插件数据结构无效。",  
877 - "copyId": "复制插件ID",  
878 - "idCopiedMessage": "插件ID已经复制到粘贴板"  
879 - },  
880 "position": { 1008 "position": {
881 "top": "顶部", 1009 "top": "顶部",
882 "bottom": "底部", 1010 "bottom": "底部",
@@ -930,66 +1058,97 @@ export default function addLocaleChinese(locales) { @@ -930,66 +1058,97 @@ export default function addLocaleChinese(locales) {
930 "additional-info": "附加信息 (JSON)", 1058 "additional-info": "附加信息 (JSON)",
931 "invalid-additional-info": "无法解析附加信息json。" 1059 "invalid-additional-info": "无法解析附加信息json。"
932 }, 1060 },
933 - "rule": {  
934 - "rule": "规则",  
935 - "rules": "规则",  
936 - "delete": "删除规则",  
937 - "activate": "激活规则",  
938 - "suspend": "暂停规则",  
939 - "active": "激活",  
940 - "suspended": "暂停",  
941 - "name": "名称",  
942 - "name-required": "名称必填。",  
943 - "description": "描述",  
944 - "add": "添加规则",  
945 - "delete-rule-title": "您确定要删除规则'{{ruleName}}'吗?",  
946 - "delete-rule-text": "小心!确认后,规则和所有相关数据将不可恢复。",  
947 - "delete-rules-title": "你确定要删除 {count, select, 1 {1 规则} other {# 规则}} 吗?",  
948 - "delete-rules-action-title": "删除 { count, select, 1 {1 规则} other {# 规则} }",  
949 - "delete-rules-text": "小心!确认后,所有选定的规则将被删除,所有相关数据将不可恢复。",  
950 - "add-rule-text": "添加新规则",  
951 - "no-rules-text": "没有找到规则",  
952 - "rule-details": "规则详情",  
953 - "filters": "过滤器",  
954 - "filter": "过滤器",  
955 - "add-filter-prompt": "请添加过滤器",  
956 - "remove-filter": "删除过滤器",  
957 - "add-filter": "添加过滤器",  
958 - "filter-name": "过滤器名称",  
959 - "filter-type": "过滤器类型",  
960 - "edit-filter": "编辑过滤器",  
961 - "view-filter": "查看过滤器",  
962 - "component-name": "名称",  
963 - "component-name-required": "名称必填。",  
964 - "component-type": "类型",  
965 - "component-type-required": "类型必填。",  
966 - "processor": "处理器",  
967 - "no-processor-configured": "未配置处理器",  
968 - "create-processor": "创建处理器",  
969 - "processor-name": "处理器名称",  
970 - "processor-type": "处理器类型",  
971 - "plugin-action": "插件动作",  
972 - "action-name": "动作名称",  
973 - "action-type": "动作类型",  
974 - "create-action-prompt": "请创建动作",  
975 - "create-action": "创建动作",  
976 - "details": "详情",  
977 - "events": "事件",  
978 - "system": "系统",  
979 - "import": "导入规则",  
980 - "export": "导出规则",  
981 - "export-failed-error": "无法导出规则:{{error}}",  
982 - "create-new-rule": "创建新规则",  
983 - "rule-file": "规则文件",  
984 - "invalid-rule-file-error": "无法导入规则:规则数据结构无效。",  
985 - "copyId": "Copy rule Id",  
986 - "idCopiedMessage": "规则ID已经复制到粘贴板",  
987 - "select-rule": "选择规则",  
988 - "no-rules-matching": "没有找到符合 '{{entity}}' 的规则。",  
989 - "rule-required": "规则必填" 1061 + "rulechain": { // TODO
  1062 + "rulechain": "Rule chain",
  1063 + "rulechains": "Rule chains",
  1064 + "root": "Root",
  1065 + "delete": "Delete rule chain",
  1066 + "name": "Name",
  1067 + "name-required": "Name is required.",
  1068 + "description": "Description",
  1069 + "add": "Add Rule Chain",
  1070 + "set-root": "Make rule chain root",
  1071 + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
  1072 + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
  1073 + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
  1074 + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
  1075 + "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
  1076 + "delete-rulechains-action-title": "Delete { count, select, 1 {1 rule chain} other {# rule chains} }",
  1077 + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
  1078 + "add-rulechain-text": "Add new rule chain",
  1079 + "no-rulechains-text": "No rule chains found",
  1080 + "rulechain-details": "Rule chain details",
  1081 + "details": "Details",
  1082 + "events": "Events",
  1083 + "system": "System",
  1084 + "import": "Import rule chain",
  1085 + "export": "Export rule chain",
  1086 + "export-failed-error": "Unable to export rule chain: {{error}}",
  1087 + "create-new-rulechain": "Create new rule chain",
  1088 + "rulechain-file": "Rule chain file",
  1089 + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
  1090 + "copyId": "Copy rule chain Id",
  1091 + "idCopiedMessage": "Rule chain Id has been copied to clipboard",
  1092 + "select-rulechain": "Select rule chain",
  1093 + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
  1094 + "rulechain-required": "Rule chain is required",
  1095 + "management": "Rules management",
  1096 + "debug-mode": "Debug mode"
990 }, 1097 },
991 - "rule-plugin": {  
992 - "management": "规则和插件管理" 1098 + "rulenode": { // TODO
  1099 + "details": "Details",
  1100 + "events": "Events",
  1101 + "search": "Search nodes",
  1102 + "open-node-library": "Open node library",
  1103 + "add": "Add rule node",
  1104 + "name": "Name",
  1105 + "name-required": "Name is required.",
  1106 + "type": "Type",
  1107 + "description": "Description",
  1108 + "delete": "Delete rule node",
  1109 + "select-all-objects": "Select all nodes and connections",
  1110 + "deselect-all-objects": "Deselect all nodes and connections",
  1111 + "delete-selected-objects": "Delete selected nodes and connections",
  1112 + "delete-selected": "Delete selected",
  1113 + "select-all": "Select all",
  1114 + "copy-selected": "Copy selected",
  1115 + "deselect-all": "Deselect all",
  1116 + "rulenode-details": "Rule node details",
  1117 + "debug-mode": "Debug mode",
  1118 + "configuration": "Configuration",
  1119 + "link": "Link",
  1120 + "link-details": "Rule node link details",
  1121 + "add-link": "Add link",
  1122 + "link-label": "Link label",
  1123 + "link-label-required": "Link label is required.",
  1124 + "custom-link-label": "Custom link label",
  1125 + "custom-link-label-required": "Custom link label is required.",
  1126 + "type-filter": "Filter",
  1127 + "type-filter-details": "Filter incoming messages with configured conditions",
  1128 + "type-enrichment": "Enrichment",
  1129 + "type-enrichment-details": "Add additional information into Message Metadata",
  1130 + "type-transformation": "Transformation",
  1131 + "type-transformation-details": "Change Message payload and Metadata",
  1132 + "type-action": "Action",
  1133 + "type-action-details": "Perform special action",
  1134 + "type-external": "External",
  1135 + "type-external-details": "Interacts with external system",
  1136 + "type-rule-chain": "Rule Chain",
  1137 + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
  1138 + "type-input": "Input",
  1139 + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
  1140 + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
  1141 + "ui-resources-load-error": "Failed to load configuration ui resources.",
  1142 + "invalid-target-rulechain": "Unable to resolve target rule chain!",
  1143 + "test-script-function": "Test script function",
  1144 + "message": "Message",
  1145 + "message-type": "Message type",
  1146 + "message-type-required": "Message type is required",
  1147 + "metadata": "Metadata",
  1148 + "metadata-required": "Metadata entries can't be empty.",
  1149 + "output": "Output",
  1150 + "test": "Test",
  1151 + "help": "Help"
993 }, 1152 },
994 "tenant": { 1153 "tenant": {
995 "tenant": "租户", 1154 "tenant": "租户",
@@ -1081,7 +1240,8 @@ export default function addLocaleChinese(locales) { @@ -1081,7 +1240,8 @@ export default function addLocaleChinese(locales) {
1081 "activation-link": "用户激活链接", 1240 "activation-link": "用户激活链接",
1082 "activation-link-text": "使用该链接 <a href='{{activationLink}}' target='_blank'>激活</a> 激活用户:", 1241 "activation-link-text": "使用该链接 <a href='{{activationLink}}' target='_blank'>激活</a> 激活用户:",
1083 "copy-activation-link": "复制用户激活链接", 1242 "copy-activation-link": "复制用户激活链接",
1084 - "activation-link-copied-message": "用户激活链接已经复制到粘贴板" 1243 + "activation-link-copied-message": "用户激活链接已经复制到粘贴板",
  1244 + "details": "Details" // TODO
1085 }, 1245 },
1086 "value": { 1246 "value": {
1087 "type": "值类型", 1247 "type": "值类型",
@@ -1248,6 +1408,14 @@ export default function addLocaleChinese(locales) { @@ -1248,6 +1408,14 @@ export default function addLocaleChinese(locales) {
1248 "material-icons": "素材图标", 1408 "material-icons": "素材图标",
1249 "show-all": "显示所有图标" 1409 "show-all": "显示所有图标"
1250 }, 1410 },
  1411 + "custom": { // TODO
  1412 + "widget-action": {
  1413 + "action-cell-button": "Action cell button",
  1414 + "row-click": "On row click",
  1415 + "marker-click": "On marker click",
  1416 + "tooltip-tag-action": "Tooltip tag action"
  1417 + }
  1418 + },
1251 "language": { 1419 "language": {
1252 "language": "语言", 1420 "language": "语言",
1253 "en_US": "英语", 1421 "en_US": "英语",
@@ -512,7 +512,7 @@ export default angular.module('thingsboard.locale', []) @@ -512,7 +512,7 @@ export default angular.module('thingsboard.locale', [])
512 "configuration-error": "Configuration error", 512 "configuration-error": "Configuration error",
513 "alias-resolution-error-title": "Dashboard aliases configuration error", 513 "alias-resolution-error-title": "Dashboard aliases configuration error",
514 "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.<br/>" + 514 "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.<br/>" +
515 - "Please contact your administrator in order to resolve this issue.", 515 + "Please contact your administrator in order to resolve this issue.",
516 "select-devices": "Select devices", 516 "select-devices": "Select devices",
517 "assignedToCustomer": "Assigned to customer", 517 "assignedToCustomer": "Assigned to customer",
518 "assignedToCustomers": "Assigned to customers", 518 "assignedToCustomers": "Assigned to customers",
@@ -897,13 +897,13 @@ export default angular.module('thingsboard.locale', []) @@ -897,13 +897,13 @@ export default angular.module('thingsboard.locale', [])
897 "opc-identity": "Identity", 897 "opc-identity": "Identity",
898 "opc-keystore": "Keystore", 898 "opc-keystore": "Keystore",
899 "opc-type": "Type", 899 "opc-type": "Type",
900 - "opc-keystore-type":"Type",  
901 - "opc-keystore-location":"Location *",  
902 - "opc-keystore-password":"Password",  
903 - "opc-keystore-alias":"Alias",  
904 - "opc-keystore-key-password":"Key password",  
905 - "opc-device-node-pattern":"Device node pattern",  
906 - "opc-device-name-pattern":"Device name pattern", 900 + "opc-keystore-type": "Type",
  901 + "opc-keystore-location": "Location *",
  902 + "opc-keystore-password": "Password",
  903 + "opc-keystore-alias": "Alias",
  904 + "opc-keystore-key-password": "Key password",
  905 + "opc-device-node-pattern": "Device node pattern",
  906 + "opc-device-name-pattern": "Device name pattern",
907 "modbus-server": "Servers/slaves", 907 "modbus-server": "Servers/slaves",
908 "modbus-add-server": "Add server/slave", 908 "modbus-add-server": "Add server/slave",
909 "modbus-add-server-prompt": "Please add server/slave", 909 "modbus-add-server-prompt": "Please add server/slave",
@@ -918,7 +918,7 @@ export default angular.module('thingsboard.locale', []) @@ -918,7 +918,7 @@ export default angular.module('thingsboard.locale', [])
918 "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", 918 "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
919 "modbus-unit-id": "Unit ID", 919 "modbus-unit-id": "Unit ID",
920 "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", 920 "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
921 - "modbus-device-name":"Device name", 921 + "modbus-device-name": "Device name",
922 "modbus-poll-period": "Poll period (ms)", 922 "modbus-poll-period": "Poll period (ms)",
923 "modbus-attributes-poll-period": "Attributes poll period (ms)", 923 "modbus-attributes-poll-period": "Attributes poll period (ms)",
924 "modbus-timeseries-poll-period": "Timeseries poll period (ms)", 924 "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
@@ -941,8 +941,8 @@ export default angular.module('thingsboard.locale', []) @@ -941,8 +941,8 @@ export default angular.module('thingsboard.locale', [])
941 "not-available": "Not available" 941 "not-available": "Not available"
942 }, 942 },
943 943
944 - "export-extensions-configuration":"Export extensions configuration",  
945 - "import-extensions-configuration":"Import extensions configuration", 944 + "export-extensions-configuration": "Export extensions configuration",
  945 + "import-extensions-configuration": "Import extensions configuration",
946 "import-extensions": "Import extensions", 946 "import-extensions": "Import extensions",
947 "import-extension": "Import extension", 947 "import-extension": "Import extension",
948 "export-extension": "Export extension", 948 "export-extension": "Export extension",
@@ -1445,14 +1445,6 @@ export default angular.module('thingsboard.locale', []) @@ -1445,14 +1445,6 @@ export default angular.module('thingsboard.locale', [])
1445 "material-icons": "Material icons", 1445 "material-icons": "Material icons",
1446 "show-all": "Show all icons" 1446 "show-all": "Show all icons"
1447 }, 1447 },
1448 - "language": {  
1449 - "language": "Language",  
1450 - "en_US": "English",  
1451 - "ko_KR": "Korean",  
1452 - "zh_CN": "Chinese",  
1453 - "ru_RU": "Russian",  
1454 - "es_ES": "Spanish"  
1455 - },  
1456 "custom": { 1448 "custom": {
1457 "widget-action": { 1449 "widget-action": {
1458 "action-cell-button": "Action cell button", 1450 "action-cell-button": "Action cell button",
@@ -1460,6 +1452,14 @@ export default angular.module('thingsboard.locale', []) @@ -1460,6 +1452,14 @@ export default angular.module('thingsboard.locale', [])
1460 "marker-click": "On marker click", 1452 "marker-click": "On marker click",
1461 "tooltip-tag-action": "Tooltip tag action" 1453 "tooltip-tag-action": "Tooltip tag action"
1462 } 1454 }
  1455 + },
  1456 + "language": {
  1457 + "language": "Language",
  1458 + "en_US": "English",
  1459 + "ko_KR": "Korean",
  1460 + "zh_CN": "Chinese",
  1461 + "ru_RU": "Russian",
  1462 + "es_ES": "Spanish"
1463 } 1463 }
1464 } 1464 }
1465 } 1465 }