Commit ee8f7405fc7129411af2036cf49612074de9d18b

Authored by Igor Kulikov
2 parents 3d9156b5 75c0c5b3

Merge with master. Version set to 2.0.2.

Showing 57 changed files with 2329 additions and 1029 deletions

Too many changes to show.

To preserve performance only 57 of 60 files are displayed.

... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ...
... ... @@ -294,7 +294,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
294 294 private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) {
295 295 GetAttributesRequest request = (GetAttributesRequest) src.getPayload();
296 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 299 Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
300 300 @Override
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.actors.rpc.RpcManagerActor;
32 32 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
33 33 import org.thingsboard.server.actors.session.SessionManagerActor;
34 34 import org.thingsboard.server.actors.stats.StatsActor;
  35 +import org.thingsboard.server.common.data.Device;
35 36 import org.thingsboard.server.common.data.id.DeviceId;
36 37 import org.thingsboard.server.common.data.id.EntityId;
37 38 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -48,6 +49,7 @@ import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
48 49 import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
49 50 import org.thingsboard.server.service.cluster.discovery.ServerInstance;
50 51 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  52 +import org.thingsboard.server.service.state.DeviceStateService;
51 53 import scala.concurrent.Await;
52 54 import scala.concurrent.Future;
53 55 import scala.concurrent.duration.Duration;
... ... @@ -81,6 +83,9 @@ public class DefaultActorService implements ActorService {
81 83 @Autowired
82 84 private DiscoveryService discoveryService;
83 85
  86 + @Autowired
  87 + private DeviceStateService deviceStateService;
  88 +
84 89 private ActorSystem system;
85 90
86 91 private ActorRef appActor;
... ... @@ -199,7 +204,7 @@ public class DefaultActorService implements ActorService {
199 204 public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) {
200 205 ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort());
201 206 log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
202   - if(log.isDebugEnabled()){
  207 + if (log.isDebugEnabled()) {
203 208 log.info("MSG: ", msg);
204 209 }
205 210 switch (msg.getMessageType()) {
... ... @@ -236,6 +241,9 @@ public class DefaultActorService implements ActorService {
236 241 case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
237 242 actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray());
238 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 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 33
34 34 @Override
35 35 public void init(ActorContext context) {
36   - if (systemContext.isTenantComponentsInitEnabled()) {
37   - super.init(context);
38   - }
  36 + super.init(context);
39 37 }
40 38
41 39 @Override
... ...
... ... @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Lazy;
36 36 import org.springframework.stereotype.Service;
37 37 import org.springframework.util.Assert;
38 38 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  39 +import org.thingsboard.server.service.state.DeviceStateService;
39 40 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
40 41 import org.thingsboard.server.utils.MiscUtils;
41 42
... ... @@ -74,6 +75,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
74 75 @Lazy
75 76 private TelemetrySubscriptionService tsSubService;
76 77
  78 + @Autowired
  79 + @Lazy
  80 + private DeviceStateService deviceStateService;
  81 +
77 82 private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>();
78 83
79 84 private CuratorFramework client;
... ... @@ -203,6 +208,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
203 208 switch (pathChildrenCacheEvent.getType()) {
204 209 case CHILD_ADDED:
205 210 tsSubService.onClusterUpdate();
  211 + deviceStateService.onClusterUpdate();
206 212 listeners.forEach(listener -> listener.onServerAdded(instance));
207 213 break;
208 214 case CHILD_UPDATED:
... ... @@ -210,6 +216,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
210 216 break;
211 217 case CHILD_REMOVED:
212 218 tsSubService.onClusterUpdate();
  219 + deviceStateService.onClusterUpdate();
213 220 listeners.forEach(listener -> listener.onServerRemoved(instance));
214 221 break;
215 222 default:
... ...
... ... @@ -211,7 +211,13 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
211 211
212 212 private void loadCql(Path cql) throws Exception {
213 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 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.context.annotation.Profile;
21 21 import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.data.SearchTextBased;
22 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 26 import org.thingsboard.server.common.data.page.TextPageLink;
25 27 import org.thingsboard.server.common.data.rule.RuleChain;
26 28 import org.thingsboard.server.dao.rule.RuleChainService;
27 29 import org.thingsboard.server.dao.tenant.TenantService;
28 30
29   -import java.util.List;
30   -import java.util.UUID;
31   -
32 31 @Service
33 32 @Profile("install")
34 33 @Slf4j
... ... @@ -59,8 +58,8 @@ public class DefaultDataUpdateService implements DataUpdateService {
59 58 new PaginatedUpdater<String, Tenant>() {
60 59
61 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 65 @Override
... ... @@ -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 80 private static final int DEFAULT_LIMIT = 100;
82 81
... ... @@ -84,20 +83,18 @@ public class DefaultDataUpdateService implements DataUpdateService {
84 83 TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
85 84 boolean hasNext = true;
86 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 88 updateEntity(entity);
90 89 }
91   - hasNext = entities.size() == pageLink.getLimit();
  90 + hasNext = entities.hasNext();
92 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 99 protected abstract void updateEntity(D entity);
103 100
... ...
... ... @@ -70,8 +70,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
70 70 log.info("Updating schema ...");
71 71 Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
72 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 75 log.info("Schema updated.");
77 76 break;
... ... @@ -87,8 +86,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
87 86
88 87 log.info("Updating schema ...");
89 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 90 log.info("Schema updated.");
93 91
94 92 log.info("Restoring dashboards ...");
... ... @@ -105,8 +103,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
105 103 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
106 104 log.info("Updating schema ...");
107 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 107 log.info("Schema updated.");
111 108 }
112 109 break;
... ... @@ -114,4 +111,10 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
114 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 23 import com.google.common.util.concurrent.ListenableFuture;
24 24 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
25 25 import com.google.common.util.concurrent.MoreExecutors;
  26 +import com.google.protobuf.InvalidProtocolBufferException;
26 27 import lombok.Getter;
27 28 import lombok.extern.slf4j.Slf4j;
28 29 import org.springframework.beans.factory.annotation.Autowired;
29 30 import org.springframework.beans.factory.annotation.Value;
30 31 import org.springframework.stereotype.Service;
  32 +import org.thingsboard.rule.engine.api.RpcError;
31 33 import org.thingsboard.server.actors.service.ActorService;
32 34 import org.thingsboard.server.common.data.DataConstants;
33 35 import org.thingsboard.server.common.data.Device;
... ... @@ -36,13 +38,19 @@ import org.thingsboard.server.common.data.id.DeviceId;
36 38 import org.thingsboard.server.common.data.id.TenantId;
37 39 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
38 40 import org.thingsboard.server.common.data.page.TextPageLink;
  41 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
39 42 import org.thingsboard.server.common.msg.TbMsg;
40 43 import org.thingsboard.server.common.msg.TbMsgDataType;
41 44 import org.thingsboard.server.common.msg.TbMsgMetaData;
  45 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
42 46 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
43 47 import org.thingsboard.server.dao.attributes.AttributesService;
44 48 import org.thingsboard.server.dao.device.DeviceService;
45 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 54 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
47 55
48 56 import javax.annotation.Nullable;
... ... @@ -54,6 +62,7 @@ import java.util.HashSet;
54 62 import java.util.List;
55 63 import java.util.Optional;
56 64 import java.util.Set;
  65 +import java.util.UUID;
57 66 import java.util.concurrent.ConcurrentHashMap;
58 67 import java.util.concurrent.ConcurrentMap;
59 68 import java.util.concurrent.ExecutionException;
... ... @@ -98,6 +107,12 @@ public class DefaultDeviceStateService implements DeviceStateService {
98 107 @Autowired
99 108 private TelemetrySubscriptionService tsSubService;
100 109
  110 + @Autowired
  111 + private ClusterRoutingService routingService;
  112 +
  113 + @Autowired
  114 + private ClusterRpcService clusterRpcService;
  115 +
101 116 @Value("${state.defaultInactivityTimeoutInSec}")
102 117 @Getter
103 118 private long defaultInactivityTimeoutInSec;
... ... @@ -172,12 +187,57 @@ public class DefaultDeviceStateService implements DeviceStateService {
172 187 }
173 188
174 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 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 247 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
188 248 List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
189 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 254 try {
193 255 Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
... ... @@ -209,7 +271,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
209 271 DeviceStateData stateData = deviceStates.get(deviceId);
210 272 DeviceState state = stateData.getState();
211 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 275 state.setLastInactivityAlarmTime(ts);
214 276 pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
215 277 saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts);
... ... @@ -219,7 +281,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
219 281 }
220 282
221 283 private void onDeviceConnectSync(DeviceId deviceId) {
222   - DeviceStateData stateData = deviceStates.get(deviceId);
  284 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
223 285 if (stateData != null) {
224 286 long ts = System.currentTimeMillis();
225 287 stateData.getState().setLastConnectTime(ts);
... ... @@ -229,7 +291,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
229 291 }
230 292
231 293 private void onDeviceDisconnectSync(DeviceId deviceId) {
232   - DeviceStateData stateData = deviceStates.get(deviceId);
  294 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
233 295 if (stateData != null) {
234 296 long ts = System.currentTimeMillis();
235 297 stateData.getState().setLastDisconnectTime(ts);
... ... @@ -239,7 +301,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
239 301 }
240 302
241 303 private void onDeviceActivitySync(DeviceId deviceId) {
242   - DeviceStateData stateData = deviceStates.get(deviceId);
  304 + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
243 305 if (stateData != null) {
244 306 DeviceState state = stateData.getState();
245 307 long ts = System.currentTimeMillis();
... ... @@ -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 333 private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
255 334 if (inactivityTimeout == 0L) {
256 335 return;
... ... @@ -269,37 +348,65 @@ public class DefaultDeviceStateService implements DeviceStateService {
269 348 }
270 349
271 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 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 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 17
18 18 import org.thingsboard.server.common.data.Device;
19 19 import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
20 21
21 22 import java.util.Optional;
22 23
... ... @@ -39,6 +40,7 @@ public interface DeviceStateService {
39 40
40 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 362
363 363 DonAsynchron.withCallback(tsService.findAll(entityId, queries),
364 364 missedUpdates -> {
365   - if (!missedUpdates.isEmpty()) {
  365 + if (missedUpdates != null && !missedUpdates.isEmpty()) {
366 366 tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
367 367 }
368 368 },
... ...
... ... @@ -57,6 +57,8 @@ enum MessageType {
57 57 CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE = 10;
58 58 CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE = 11;
59 59 CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12;
  60 +
  61 + CLUSTER_DEVICE_STATE_SERVICE_MESSAGE = 13;
60 62 }
61 63
62 64 // Messages related to CLUSTER_TELEMETRY_MESSAGE
... ... @@ -128,3 +130,13 @@ message FromDeviceRPCResponseProto {
128 130 string response = 3;
129 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 +}
\ No newline at end of file
... ...
... ... @@ -64,6 +64,7 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
64 64 mockMvc.perform(
65 65 asyncDispatch(doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", attrMap, new String[]{}).andReturn()))
66 66 .andExpect(status().isOk());
  67 + Thread.sleep(2000);
67 68 doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
68 69 }
69 70
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -98,6 +98,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
98 98 public boolean updateAssignedCustomer(Customer customer) {
99 99 ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
100 100 if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  101 + this.assignedCustomers.remove(customerInfo);
101 102 this.assignedCustomers.add(customerInfo);
102 103 return true;
103 104 } else {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -15,11 +15,13 @@
15 15 */
16 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 19 import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
20 20
21 21 public interface SessionMsgProcessor {
22 22
23 23 void process(SessionAwareMsg msg);
24 24
  25 + void onDeviceAdded(Device device);
  26 +
25 27 }
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>dao</artifactId>
... ...
... ... @@ -67,9 +67,9 @@ public abstract class AbstractCassandraCluster {
67 67 private long initTimeout;
68 68 @Value("${cassandra.init_retry_interval_ms}")
69 69 private long initRetryInterval;
70   - @Value("${cassandra.max_requests_per_connection_local:128}")
  70 + @Value("${cassandra.max_requests_per_connection_local:32768}")
71 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 73 private int max_requests_remote;
74 74
75 75 @Autowired
... ...
1   -VERSION=2.0.1
  1 +VERSION=2.0.2
2 2 PROJECT=thingsboard
3 3 APP=cassandra-setup
4 4
... ...
1   -VERSION=2.0.1
  1 +VERSION=2.0.2
2 2 PROJECT=thingsboard
3 3 APP=cassandra
4 4
... ...
... ... @@ -18,7 +18,7 @@ version: '2'
18 18
19 19 services:
20 20 tb:
21   - image: "thingsboard/application:2.0.1"
  21 + image: "thingsboard/application:2.0.2"
22 22 ports:
23 23 - "8080:8080"
24 24 - "1883:1883"
... ...
... ... @@ -22,7 +22,7 @@ spec:
22 22 containers:
23 23 - name: cassandra-setup
24 24 imagePullPolicy: Always
25   - image: thingsboard/cassandra-setup:2.0.1
  25 + image: thingsboard/cassandra-setup:2.0.2
26 26 env:
27 27 - name: ADD_DEMO_DATA
28 28 value: "true"
... ...
... ... @@ -54,7 +54,7 @@ spec:
54 54 topologyKey: "kubernetes.io/hostname"
55 55 containers:
56 56 - name: cassandra
57   - image: thingsboard/cassandra:2.0.1
  57 + image: thingsboard/cassandra:2.0.2
58 58 imagePullPolicy: Always
59 59 ports:
60 60 - containerPort: 7000
... ...
... ... @@ -84,7 +84,7 @@ spec:
84 84 containers:
85 85 - name: tb
86 86 imagePullPolicy: Always
87   - image: thingsboard/application:2.0.1
  87 + image: thingsboard/application:2.0.2
88 88 ports:
89 89 - containerPort: 8080
90 90 name: ui
... ...
... ... @@ -87,7 +87,7 @@ spec:
87 87 containers:
88 88 - name: zk
89 89 imagePullPolicy: Always
90   - image: thingsboard/zk:2.0.1
  90 + image: thingsboard/zk:2.0.2
91 91 ports:
92 92 - containerPort: 2181
93 93 name: client
... ...
1   -VERSION=2.0.1
  1 +VERSION=2.0.2
2 2 PROJECT=thingsboard
3 3 APP=application
4 4
... ...
1   -VERSION=2.0.1
  1 +VERSION=2.0.2
2 2 PROJECT=thingsboard
3 3 APP=zk
4 4
... ...
... ... @@ -19,12 +19,12 @@
19 19 <modelVersion>4.0.0</modelVersion>
20 20 <parent>
21 21 <groupId>org.thingsboard</groupId>
22   - <version>2.0.1</version>
  22 + <version>2.0.2</version>
23 23 <artifactId>thingsboard</artifactId>
24 24 </parent>
25 25 <groupId>org.thingsboard</groupId>
26 26 <artifactId>netty-mqtt</artifactId>
27   - <version>2.0.1</version>
  27 + <version>2.0.2</version>
28 28 <packaging>jar</packaging>
29 29
30 30 <name>Netty MQTT Client</name>
... ...
... ... @@ -98,33 +98,25 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
98 98 }
99 99
100 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 105 continue;
105 106 }
106 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 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 120 message.payload().release();
129 121 }
130 122
... ... @@ -133,7 +125,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
133 125 case CONNECTION_ACCEPTED:
134 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 129 channel.write(e.getValue().getSubscribeMessage());
138 130 e.getValue().setSent(true);
139 131 });
... ... @@ -148,6 +140,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
148 140 }
149 141 });
150 142 channel.flush();
  143 + if (this.client.isReconnect()) {
  144 + this.client.onSuccessfulReconnect();
  145 + }
151 146 break;
152 147
153 148 case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD:
... ... @@ -163,19 +158,19 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
163 158 }
164 159
165 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 162 if (pendingSubscription == null) {
168 163 return;
169 164 }
170 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 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 175 if (!pendingSubscription.getFuture().isDone()) {
181 176 pendingSubscription.getFuture().setSuccess(null);
... ... @@ -215,13 +210,13 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
215 210 }
216 211
217 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 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 220 this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId());
226 221 }
227 222
... ...
... ... @@ -92,7 +92,7 @@ public interface MqttClient {
92 92
93 93 /**
94 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 97 * @param topic The topic filter to subscribe to
98 98 * @param handler The handler to invoke when we receive a message
... ... @@ -102,7 +102,7 @@ public interface MqttClient {
102 102
103 103 /**
104 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 107 * @param topic The topic filter to subscribe to
108 108 * @param handler The handler to invoke when we receive a message
... ... @@ -112,7 +112,7 @@ public interface MqttClient {
112 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 116 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
117 117 *
118 118 * @param topic The topic to unsubscribe for
... ... @@ -122,7 +122,7 @@ public interface MqttClient {
122 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 126 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
127 127 *
128 128 * @param topic The topic to unsubscribe for
... ... @@ -172,24 +172,18 @@ public interface MqttClient {
172 172 */
173 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 177 * Construct the MqttClientImpl with additional config.
184 178 * This config can also be changed using the {@link #getClientConfig()} function
185 179 *
186 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 188 * Send disconnect and close channel
195 189 *
... ...
... ... @@ -15,6 +15,8 @@
15 15 */
16 16 package org.thingsboard.mqtt;
17 17
  18 +import io.netty.channel.ChannelId;
  19 +
18 20 /**
19 21 * Created by Valerii Sosliuk on 12/30/2017.
20 22 */
... ... @@ -25,5 +27,11 @@ public interface MqttClientCallback {
25 27 *
26 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 40 @SuppressWarnings({"WeakerAccess", "unused"})
41 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 45 private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>();
46 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 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 51 private final AtomicInteger nextMessageId = new AtomicInteger(1);
52 52
53 53 private final MqttClientConfig clientConfig;
54 54
  55 + private final MqttHandler defaultHandler;
  56 +
55 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 63 private String host;
61 64 private int port;
62 65 private MqttClientCallback callback;
... ... @@ -65,8 +68,9 @@ final class MqttClientImpl implements MqttClient {
65 68 /**
66 69 * Construct the MqttClientImpl with default config
67 70 */
68   - public MqttClientImpl() {
  71 + public MqttClientImpl(MqttHandler defaultHandler) {
69 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 79 *
76 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 83 this.clientConfig = clientConfig;
  84 + this.defaultHandler = defaultHandler;
80 85 }
81 86
82 87 /**
... ... @@ -100,12 +105,15 @@ final class MqttClientImpl implements MqttClient {
100 105 */
101 106 @Override
102 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 112 if (this.eventLoop == null) {
104 113 this.eventLoop = new NioEventLoopGroup();
105 114 }
106 115 this.host = host;
107 116 this.port = port;
108   -
109 117 Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next());
110 118 Bootstrap bootstrap = new Bootstrap();
111 119 bootstrap.group(this.eventLoop);
... ... @@ -113,22 +121,47 @@ final class MqttClientImpl implements MqttClient {
113 121 bootstrap.remoteAddress(host, port);
114 122 bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext()));
115 123 ChannelFuture future = bootstrap.connect();
  124 +
116 125 future.addListener((ChannelFutureListener) f -> {
117 126 if (f.isSuccess()) {
118 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 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 162 @Override
127 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 167 @Override
... ... @@ -183,12 +216,12 @@ final class MqttClientImpl implements MqttClient {
183 216 */
184 217 @Override
185 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 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 226 * @param topic The topic filter to subscribe to
194 227 * @param handler The handler to invoke when we receive a message
... ... @@ -201,7 +234,7 @@ final class MqttClientImpl implements MqttClient {
201 234
202 235 /**
203 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 239 * @param topic The topic filter to subscribe to
207 240 * @param handler The handler to invoke when we receive a message
... ... @@ -210,11 +243,11 @@ final class MqttClientImpl implements MqttClient {
210 243 */
211 244 @Override
212 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 251 * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)}
219 252 *
220 253 * @param topic The topic to unsubscribe for
... ... @@ -224,8 +257,8 @@ final class MqttClientImpl implements MqttClient {
224 257 @Override
225 258 public Future<Void> off(String topic, MqttHandler handler) {
226 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 263 this.handlerToSubscribtion.removeAll(handler);
231 264 this.checkSubscribtions(topic, future);
... ... @@ -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 270 * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)}
238 271 *
239 272 * @param topic The topic to unsubscribe for
... ... @@ -242,12 +275,12 @@ final class MqttClientImpl implements MqttClient {
242 275 @Override
243 276 public Future<Void> off(String topic) {
244 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 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 285 this.checkSubscribtions(topic, future);
253 286 return future;
... ... @@ -310,7 +343,7 @@ final class MqttClientImpl implements MqttClient {
310 343 ChannelFuture channelFuture = this.sendAndFlushPacket(message);
311 344
312 345 if (channelFuture != null) {
313   - pendingPublish.setSent(channelFuture != null);
  346 + pendingPublish.setSent(true);
314 347 if (channelFuture.cause() != null) {
315 348 future.setFailure(channelFuture.cause());
316 349 return future;
... ... @@ -352,6 +385,15 @@ final class MqttClientImpl implements MqttClient {
352 385
353 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 397 ChannelFuture sendAndFlushPacket(Object message) {
356 398 if (this.channel == null) {
357 399 return null;
... ... @@ -359,11 +401,7 @@ final class MqttClientImpl implements MqttClient {
359 401 if (this.channel.isActive()) {
360 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 407 private MqttMessageIdVariableHeader getNewMessageId() {
... ... @@ -371,18 +409,18 @@ final class MqttClientImpl implements MqttClient {
371 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 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 424 return this.channel.newSucceededFuture();
387 425 }
388 426
... ... @@ -393,27 +431,27 @@ final class MqttClientImpl implements MqttClient {
393 431 MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription));
394 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 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 442 return future;
405 443 }
406 444
407 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 447 MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
410 448 MqttMessageIdVariableHeader variableHeader = getNewMessageId();
411 449 MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic));
412 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 456 this.sendAndFlushPacket(message);
419 457 } else {
... ... @@ -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 467 return subscriptions;
430 468 }
431 469
... ... @@ -433,15 +471,15 @@ final class MqttClientImpl implements MqttClient {
433 471 return pendingSubscribeTopics;
434 472 }
435 473
436   - HashMultimap<MqttHandler, MqttSubscribtion> getHandlerToSubscribtion() {
  474 + HashMultimap<MqttHandler, MqttSubscription> getHandlerToSubscribtion() {
437 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 483 return pendingServerUnsubscribes;
446 484 }
447 485
... ... @@ -481,4 +519,9 @@ final class MqttClientImpl implements MqttClient {
481 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 23 import java.util.Set;
24 24 import java.util.function.Consumer;
25 25
26   -final class MqttPendingSubscribtion {
  26 +final class MqttPendingSubscription {
27 27
28 28 private final Promise<Void> future;
29 29 private final String topic;
... ... @@ -34,7 +34,7 @@ final class MqttPendingSubscribtion {
34 34
35 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 38 this.future = future;
39 39 this.topic = topic;
40 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 21
22 22 import java.util.function.Consumer;
23 23
24   -final class MqttPendingUnsubscribtion {
  24 +final class MqttPendingUnsubscription {
25 25
26 26 private final Promise<Void> future;
27 27 private final String topic;
28 28
29 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 32 this.future = future;
33 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 17
18 18 import java.util.regex.Pattern;
19 19
20   -final class MqttSubscribtion {
  20 +final class MqttSubscription {
21 21
22 22 private final String topic;
23 23 private final Pattern topicRegex;
... ... @@ -27,7 +27,7 @@ final class MqttSubscribtion {
27 27
28 28 private boolean called;
29 29
30   - MqttSubscribtion(String topic, MqttHandler handler, boolean once) {
  30 + MqttSubscription(String topic, MqttHandler handler, boolean once) {
31 31 if(topic == null){
32 32 throw new NullPointerException("topic");
33 33 }
... ... @@ -65,7 +65,7 @@ final class MqttSubscribtion {
65 65 if (this == o) return true;
66 66 if (o == null || getClass() != o.getClass()) return false;
67 67
68   - MqttSubscribtion that = (MqttSubscribtion) o;
  68 + MqttSubscription that = (MqttSubscription) o;
69 69
70 70 return once == that.once && topic.equals(that.topic) && handler.equals(that.handler);
71 71 }
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <groupId>org.thingsboard</groupId>
22 22 <artifactId>thingsboard</artifactId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <packaging>pom</packaging>
25 25
26 26 <name>Thingsboard</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rule-engine</artifactId>
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>2.0.1</version>
  25 + <version>2.0.2</version>
26 26 <artifactId>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>2.0.1</version>
  25 + <version>2.0.2</version>
26 26 <artifactId>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -111,8 +111,9 @@ public class TbMqttNode implements TbNode {
111 111 if (!StringUtils.isEmpty(this.config.getClientId())) {
112 112 config.setClientId(this.config.getClientId());
113 113 }
  114 + config.setCleanSession(this.config.isCleanSession());
114 115 this.config.getCredentials().configure(config);
115   - MqttClient client = MqttClient.create(config);
  116 + MqttClient client = MqttClient.create(config, null);
116 117 client.setEventLoop(this.eventLoopGroup);
117 118 Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort());
118 119 MqttConnectResult result;
... ...
... ... @@ -30,6 +30,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
30 30 private int connectTimeoutSec;
31 31 private String clientId;
32 32
  33 + private boolean cleanSession;
33 34 private boolean ssl;
34 35 private MqttClientCredentials credentials;
35 36
... ... @@ -40,6 +41,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
40 41 configuration.setHost("localhost");
41 42 configuration.setPort(1883);
42 43 configuration.setConnectTimeoutSec(10);
  44 + configuration.setCleanSession(true);
43 45 configuration.setSsl(false);
44 46 configuration.setCredentials(new AnonymousCredentials());
45 47 return configuration;
... ...
... ... @@ -16,7 +16,10 @@
16 16 package org.thingsboard.rule.engine.rpc;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.fasterxml.jackson.databind.JsonNode;
19 21 import com.google.gson.Gson;
  22 +import com.google.gson.JsonElement;
20 23 import com.google.gson.JsonObject;
21 24 import com.google.gson.JsonParser;
22 25 import lombok.extern.slf4j.Slf4j;
... ... @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
35 38 import org.thingsboard.server.common.data.plugin.ComponentType;
36 39 import org.thingsboard.server.common.msg.TbMsg;
37 40
  41 +import java.io.IOException;
38 42 import java.util.Random;
39 43 import java.util.UUID;
40 44 import java.util.concurrent.TimeUnit;
... ... @@ -86,10 +90,18 @@ public class TbSendRPCRequestNode implements TbNode {
86 90 tmp = msg.getMetaData().getValue("expirationTime");
87 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 101 RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder()
90 102 .oneway(oneway)
91 103 .method(json.get("method").getAsString())
92   - .body(gson.toJson(json.get("params")))
  104 + .body(params)
93 105 .deviceId(new DeviceId(msg.getOriginator().getId()))
94 106 .requestId(requestId)
95 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 4 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -92,6 +92,7 @@ public class GatewaySessionCtx {
92 92 device.setType(deviceType);
93 93 device = deviceService.saveDevice(device);
94 94 relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created"));
  95 + processor.onDeviceAdded(device);
95 96 }
96 97 GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device);
97 98 devices.put(deviceName, ctx);
... ... @@ -154,6 +155,7 @@ public class GatewaySessionCtx {
154 155 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
155 156 processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(),
156 157 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
  158 + ack(mqttMsg);
157 159 } else {
158 160 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
159 161 }
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard</groupId>
... ...
1 1 {
2 2 "name": "thingsboard",
3 3 "private": true,
4   - "version": "2.0.1",
  4 + "version": "2.0.2",
5 5 "description": "Thingsboard UI",
6 6 "licenses": [
7 7 {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.0.1</version>
  23 + <version>2.0.2</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard</groupId>
... ...
... ... @@ -13,804 +13,1321 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   - export default function addLocaleSpanish(locales) {
  16 +export default function addLocaleSpanish(locales) {
17 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 593 "dialog": {
404   - "close": "Cerrar cuadro de diálogo"
  594 + "close": "Cerrar cuadro de diálogo"
405 595 },
406 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 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 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 871 "function": {
437   - "function": "Función"
  872 + "function": "Función"
438 873 },
439 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 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 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 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 902 "item": {
468   - "selected": "Seleccionado"
  903 + "selected": "Seleccionado"
469 904 },
470 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 }
\ No newline at end of file
... ...
... ... @@ -38,8 +38,11 @@ export default function addLocaleKorean(locales) {
38 38 "update": "업데이트",
39 39 "remove": "제거",
40 40 "search": "검색",
  41 + "clear-search": "Clear search", // TODO
41 42 "assign": "할당",
42 43 "unassign": "비할당",
  44 + "share": "Share", // TODO
  45 + "make-private": "Make private", // TODO
43 46 "apply": "적용",
44 47 "apply-changes": "변경사항 적용",
45 48 "edit-mode": "수정 모드",
... ... @@ -57,8 +60,11 @@ export default function addLocaleKorean(locales) {
57 60 "undo": "취소",
58 61 "copy": "복사",
59 62 "paste": "붙여넣기",
  63 + "copy-reference": "Copy reference", // TODO
  64 + "paste-reference": "Paste reference", // TODO
60 65 "import": "가져오기",
61   - "export": "내보내기"
  66 + "export": "내보내기",
  67 + "share-via": "Share via {{provider}}" // TODO
62 68 },
63 69 "aggregation": {
64 70 "aggregation": "집합",
... ... @@ -95,6 +101,160 @@ export default function addLocaleKorean(locales) {
95 101 "enable-tls": "TLS 사용",
96 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 258 "attribute": {
99 259 "attributes": "속성",
100 260 "latest-telemetry": "최근 데이터",
... ... @@ -104,9 +264,9 @@ export default function addLocaleKorean(locales) {
104 264 "scope-server": "서버 속성",
105 265 "scope-shared": "공유 속성",
106 266 "add": "속성 추가",
107   - "key": "Key",
  267 + "key": "Key", // TODO
108 268 "key-required": "속성 key를 입력하세요.",
109   - "value": "Value",
  269 + "value": "Value", // TODO
110 270 "value-required": "속성 value를 입력하세요.",
111 271 "delete-attributes-title": "{ count, select, 1 {속성} other {여러 속성들을} } 삭제하시겠습니까??",
112 272 "delete-attributes-text": "모든 선택된 속성들이 제거 될 것이므로 주의하십시오.",
... ... @@ -121,6 +281,38 @@ export default function addLocaleKorean(locales) {
121 281 "selected-attributes": "{ count, select, 1 {속성 1개} other {속성 #개} } 선택됨",
122 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 316 "confirm-on-exit": {
125 317 "message": "변경 사항을 저장하지 않았습니다. 이 페이지를 나가시겠습니까?",
126 318 "html-message": "변경 사항을 저장하지 않았습니다.<br/>이 페이지를 나가시겠습니까?",
... ... @@ -145,6 +337,11 @@ export default function addLocaleKorean(locales) {
145 337 "enter-password": "비밀번호를 입력하세요.",
146 338 "enter-search": "검색어 입력"
147 339 },
  340 + "content-type": { // TODO
  341 + "json": "Json",
  342 + "text": "Text",
  343 + "binary": "Binary (Base64)"
  344 + },
148 345 "customer": {
149 346 "customers": "커스터머",
150 347 "management": "커스터머 관리",
... ... @@ -156,6 +353,10 @@ export default function addLocaleKorean(locales) {
156 353 "manage-customer-users": "커스터머 사용자 관리",
157 354 "manage-customer-devices": "커스터머 디바이스 관리",
158 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 360 "add-customer-text": "커스터머 추가",
160 361 "no-customers-text": "커스터머가 없습니다.",
161 362 "customer-details": "커스터머 상세정보",
... ... @@ -169,7 +370,17 @@ export default function addLocaleKorean(locales) {
169 370 "manage-dashboards": "대시보드 관리",
170 371 "title": "타이틀",
171 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 385 "datetime": {
175 386 "date-from": "시작 날짜",
... ... @@ -277,11 +488,15 @@ export default function addLocaleKorean(locales) {
277 488 "attributes": "Attributes",
278 489 "timeseries-required": "디바이스 timeseries 를 입력하세요.",
279 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 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 497 "datasource": {
284 498 "type": "데이터소스 유형",
  499 + "name": "Name", // TODO
285 500 "add-datasource-prompt": "데이터소스를 추가하세요."
286 501 },
287 502 "details": {
... ... @@ -375,11 +590,101 @@ export default function addLocaleKorean(locales) {
375 590 "unhandled-error-code": "처리되지 않은 오류 코드: {{errorCode}}",
376 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 681 "event": {
379 682 "event-type": "이벤트 타입",
380 683 "type-error": "에러",
381 684 "type-lc-event": "주기적 이벤트",
382 685 "type-stats": "통계",
  686 + "type-debug-rule-node": "Debug", // TODO
  687 + "type-debug-rule-chain": "Debug", // TODO
383 688 "no-events-prompt": "이벤트 없음",
384 689 "error": "에러",
385 690 "alarm": "알람",
... ... @@ -387,6 +692,14 @@ export default function addLocaleKorean(locales) {
387 692 "server": "서버",
388 693 "body": "Body",
389 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 703 "event": "이벤트",
391 704 "status": "상태",
392 705 "success": "성공",
... ... @@ -394,6 +707,163 @@ export default function addLocaleKorean(locales) {
394 707 "messages-processed": "처리된 메시지",
395 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 867 "fullscreen": {
398 868 "expand": "전체화면으로 확장",
399 869 "exit": "전체화면 종료",
... ... @@ -436,7 +906,24 @@ export default function addLocaleKorean(locales) {
436 906 },
437 907 "js-func": {
438 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 928 "legend": {
442 929 "position": "범례 위치",
... ... @@ -467,45 +954,6 @@ export default function addLocaleKorean(locales) {
467 954 "password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!",
468 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 957 "position": {
510 958 "top": "상단",
511 959 "bottom": "하단",
... ... @@ -517,60 +965,139 @@ export default function addLocaleKorean(locales) {
517 965 "change-password": "비밀번호 변경",
518 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 1102 "tenant": {
576 1103 "tenants": "테넌트",
... ... @@ -589,7 +1116,14 @@ export default function addLocaleKorean(locales) {
589 1116 "delete-tenants-text": "선택된 테넌트가 삭제되고 관련된 모든 정보를 복구할 수 없으므로 주의하십시오.",
590 1117 "title": "타이틀",
591 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 1128 "timeinterval": {
595 1129 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
... ... @@ -642,7 +1176,18 @@ export default function addLocaleKorean(locales) {
642 1176 "last-name": "성",
643 1177 "description": "설명",
644 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 1192 "value": {
648 1193 "type": "Value type",
... ... @@ -707,6 +1252,18 @@ export default function addLocaleKorean(locales) {
707 1252 "undo": "위젯 변경사항 취소",
708 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 1267 "widgets-bundle": {
711 1268 "current": "현재 번들",
712 1269 "widgets-bundles": "위젯 번들",
... ... @@ -770,6 +1327,20 @@ export default function addLocaleKorean(locales) {
770 1327 "widget-type-file": "위젯 타입 파일",
771 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 1344 "language": {
774 1345 "language": "언어",
775 1346 "en_US": "영어",
... ... @@ -779,5 +1350,5 @@ export default function addLocaleKorean(locales) {
779 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 }
\ No newline at end of file
... ...