thingsboard-aplcache

Changes

pom.xml 6(+6 -0)

tools/pom.xml 1(+0 -1)

Details

diff --git a/application/pom.xml b/application/pom.xml
index e1a0731..b2f2de6 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -264,6 +264,10 @@
             <groupId>org.javadelight</groupId>
             <artifactId>delight-nashorn-sandbox</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.springfox.ui</groupId>
+            <artifactId>springfox-swagger-ui-rfc6570</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql
index 36ac8e4..73793c4 100644
--- a/application/src/main/data/upgrade/2.1.1/schema_update.cql
+++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql
@@ -14,13 +14,6 @@
 -- limitations under the License.
 --
 
-DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
-DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
-DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
-DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
-
-DROP TABLE IF EXISTS thingsboard.entity_views;
-
 CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
     id timeuuid,
     entity_id timeuuid,
diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.sql b/application/src/main/data/upgrade/2.1.1/schema_update.sql
index bd2c341..ecf90e0 100644
--- a/application/src/main/data/upgrade/2.1.1/schema_update.sql
+++ b/application/src/main/data/upgrade/2.1.1/schema_update.sql
@@ -14,10 +14,8 @@
 -- limitations under the License.
 --
 
-DROP TABLE IF EXISTS entity_views;
-
 CREATE TABLE IF NOT EXISTS entity_views (
-    id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
+    id varchar(31) NOT NULL CONSTRAINT entity_views_pkey PRIMARY KEY,
     entity_id varchar(31),
     entity_type varchar(255),
     tenant_id varchar(31),
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index e46a959..c0030cb 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -16,7 +16,6 @@
 package org.thingsboard.server.actors.device;
 
 import akka.actor.ActorContext;
-import akka.event.LoggingAdapter;
 import com.datastax.driver.core.utils.UUIDs;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
@@ -26,12 +25,12 @@ import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.protobuf.InvalidProtocolBufferException;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
 import org.thingsboard.rule.engine.api.RpcError;
 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
-import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
@@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
-import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
@@ -81,12 +79,14 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
+import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
+import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
+
 /**
  * @author Andrew Shvayka
  */
@@ -263,10 +263,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
     }
 
     private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
-        ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, toOptionalSet(request.getClientAttributeNamesList()));
-        ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, toOptionalSet(request.getSharedAttributeNamesList()));
         int requestId = request.getRequestId();
-        Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
+        Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<List<List<AttributeKvEntry>>>() {
             @Override
             public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
                 GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
@@ -287,16 +285,35 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
         });
     }
 
-    private ListenableFuture<List<AttributeKvEntry>> getAttributeKvEntries(DeviceId deviceId, String scope, Optional<Set<String>> names) {
-        if (names.isPresent()) {
-            if (!names.get().isEmpty()) {
-                return systemContext.getAttributesService().find(tenantId, deviceId, scope, names.get());
-            } else {
-                return systemContext.getAttributesService().findAll(tenantId, deviceId, scope);
-            }
+    private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {
+        ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
+        ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
+        if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
+            clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE);
+            sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE);
+        } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
+            clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
+            sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
+        } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
+            clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
+            sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
         } else {
-            return Futures.immediateFuture(Collections.emptyList());
+            sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
+            clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
         }
+        return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
+    }
+
+    private ListenableFuture<List<AttributeKvEntry>> findAllAttributesByScope(String scope) {
+        return systemContext.getAttributesService().findAll(tenantId, deviceId, scope);
+    }
+
+    private ListenableFuture<List<AttributeKvEntry>> findAttributesByScope(Set<String> attributesSet, String scope) {
+        return systemContext.getAttributesService().find(tenantId, deviceId, scope, attributesSet);
+    }
+
+    private Set<String> toSet(List<String> strings) {
+        return new HashSet<>(strings);
     }
 
     private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) {
@@ -368,7 +385,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
             AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
             if (msg.isDeleted()) {
                 List<String> sharedKeys = msg.getDeletedKeys().stream()
-                        .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
+                        .filter(key -> SHARED_SCOPE.equals(key.getScope()))
                         .map(AttributeKey::getAttributeKey)
                         .collect(Collectors.toList());
                 if (!sharedKeys.isEmpty()) {
@@ -376,7 +393,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
                     hasNotificationData = true;
                 }
             } else {
-                if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
+                if (SHARED_SCOPE.equals(msg.getScope())) {
                     List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
                     if (attributes.size() > 0) {
                         List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
@@ -545,14 +562,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
         return json;
     }
 
-    private Optional<Set<String>> toOptionalSet(List<String> strings) {
-        if (strings == null || strings.isEmpty()) {
-            return Optional.empty();
-        } else {
-            return Optional.of(new HashSet<>(strings));
-        }
-    }
-
     private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
         DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
                 .setSessionIdMSB(sessionInfo.getSessionIdMSB())
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
index 7f200e1..452b9d7 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
@@ -17,7 +17,6 @@ package org.thingsboard.server.actors.ruleChain;
 
 import akka.actor.ActorContext;
 import akka.actor.ActorRef;
-import akka.event.LoggingAdapter;
 import org.thingsboard.rule.engine.api.TbContext;
 import org.thingsboard.rule.engine.api.TbNode;
 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
index 1f084e1..6a53fd3 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
@@ -16,9 +16,6 @@
 package org.thingsboard.server.actors.service;
 
 import akka.actor.ActorRef;
-import akka.event.Logging;
-import akka.event.LoggingAdapter;
-import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
 import org.thingsboard.server.actors.stats.StatsPersistMsg;
@@ -60,7 +57,8 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
                 scheduleStatsPersistTick();
             }
         } catch (Exception e) {
-            log.warn("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
+            log.warn("[{}][{}] Failed to start {} processor.", tenantId, id, id.getEntityType());
+            log.warn("Error:", e);
             logAndPersist("OnStart", e, true);
             logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
         }
@@ -149,10 +147,13 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
 
     private void logAndPersist(String method, Exception e, boolean critical) {
         errorsOccurred++;
+        String componentName = processor != null ? processor.getComponentName() : "Unknown";
         if (critical) {
-            log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
+            log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
+            log.warn("Critical Error: ", e);
         } else {
-            log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
+            log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
+            log.debug("Debug Error: ", e);
         }
         long ts = System.currentTimeMillis();
         if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {
diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
index 2254cf3..52eef82 100644
--- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
@@ -64,7 +64,8 @@ public class SwaggerConfiguration {
                     .paths(apiPaths())
                     .build()
                     .securitySchemes(newArrayList(jwtTokenKey()))
-                    .securityContexts(newArrayList(securityContext()));
+                    .securityContexts(newArrayList(securityContext()))
+                    .enableUrlTemplating(true);
       }
 
       private ApiKey jwtTokenKey() {
diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
index 44553c8..a621ed1 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -280,8 +280,14 @@ public class TelemetryController extends BaseController {
             deleteFromTs = 0L;
             deleteToTs = System.currentTimeMillis();
         } else {
-            deleteFromTs = startTs;
-            deleteToTs = endTs;
+            if (startTs == null || endTs == null) {
+                deleteToTs = endTs;
+                return getImmediateDeferredResult("When deleteAllDataForKeys is false, start and end timestamp values shouldn't be empty", HttpStatus.BAD_REQUEST);
+            }
+            else{
+                deleteFromTs = startTs;
+                deleteToTs = endTs;
+            }
         }
 
         return accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entityIdStr, (result, tenantId, entityId) -> {
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
index 7436de8..2ad5e37 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
@@ -100,7 +101,7 @@ public class LocalTransportApiService implements TransportApiService {
         //TODO: Make async and enable caching
         DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
         if (credentials != null && credentials.getCredentialsType() == credentialsType) {
-            return getDeviceInfo(credentials.getDeviceId());
+            return getDeviceInfo(credentials.getDeviceId(), credentials);
         } else {
             return getEmptyTransportApiResponseFuture();
         }
@@ -135,15 +136,20 @@ public class LocalTransportApiService implements TransportApiService {
     }
 
 
-    private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId) {
+    private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
         return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
             if (device == null) {
                 log.trace("[{}] Failed to lookup device by id", deviceId);
                 return getEmptyTransportApiResponse();
             }
             try {
+                ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
+                builder.setDeviceInfo(getDeviceInfoProto(device));
+                if(!StringUtils.isEmpty(credentials.getCredentialsValue())){
+                    builder.setCredentialsBody(credentials.getCredentialsValue());
+                }
                 return TransportApiResponseMsg.newBuilder()
-                        .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
+                        .setValidateTokenResponseMsg(builder.build()).build();
             } catch (JsonProcessingException e) {
                 log.warn("[{}] Failed to lookup device by id", deviceId, e);
                 return getEmptyTransportApiResponse();
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
index aa643eb..29e5859 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
@@ -54,18 +54,18 @@ import java.util.concurrent.TimeUnit;
 @Slf4j
 @Component("MqttSslHandlerProvider")
 @ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')")
-@ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
+@ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
 public class MqttSslHandlerProvider {
 
-    @Value("${mqtt.ssl.protocol}")
+    @Value("${transport.mqtt.ssl.protocol}")
     private String sslProtocol;
-    @Value("${mqtt.ssl.key_store}")
+    @Value("${transport.mqtt.ssl.key_store}")
     private String keyStoreFile;
-    @Value("${mqtt.ssl.key_store_password}")
+    @Value("${transport.mqtt.ssl.key_store_password}")
     private String keyStorePassword;
-    @Value("${mqtt.ssl.key_password}")
+    @Value("${transport.mqtt.ssl.key_password}")
     private String keyPassword;
-    @Value("${mqtt.ssl.key_store_type}")
+    @Value("${transport.mqtt.ssl.key_store_type}")
     private String keyStoreType;
 
     @Autowired
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index 3986837..1a759bc 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -271,6 +271,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
                     case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
                     case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
                     case MqttTopics.GATEWAY_RPC_TOPIC:
+                    case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
                     case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
                         registerSubQoS(topic, grantedQoSList, reqQoS);
                         break;
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
index 5508378..eb8e5fb 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
@@ -36,9 +36,8 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
     @Override
     public void initChannel(SocketChannel ch) {
         ChannelPipeline pipeline = ch.pipeline();
-        SslHandler sslHandler = null;
         if (context.getSslHandlerProvider() != null) {
-            sslHandler = context.getSslHandlerProvider().getSslHandler();
+            SslHandler sslHandler = context.getSslHandlerProvider().getSslHandler();
             pipeline.addLast(sslHandler);
             context.setSslHandler(sslHandler);
         }
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
index 5a81b97..b5e5d74 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
@@ -40,7 +40,7 @@ public abstract class AbstractTransportService implements TransportService {
     private boolean rateLimitEnabled;
     @Value("${transport.rate_limits.tenant}")
     private String perTenantLimitsConf;
-    @Value("${transport.rate_limits.tenant}")
+    @Value("${transport.rate_limits.device}")
     private String perDevicesLimitsConf;
     @Value("${transport.sessions.inactivity_timeout}")
     private long sessionInactivityTimeout;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
index a5415f0..8a70059 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
@@ -15,6 +15,7 @@
  */
 package org.thingsboard.server.dao.util;
 
+import com.datastax.driver.core.*;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -22,6 +23,7 @@ import com.google.common.util.concurrent.SettableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.tools.TbRateLimits;
+import org.thingsboard.server.dao.nosql.CassandraStatementTask;
 
 import javax.annotation.Nullable;
 import java.util.UUID;
@@ -183,12 +185,39 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
 
     private void logTask(String action, AsyncTaskContext<T, V> taskCtx) {
         if (log.isTraceEnabled()) {
-            log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx);
+            if (taskCtx.getTask() instanceof CassandraStatementTask) {
+                CassandraStatementTask cassStmtTask = (CassandraStatementTask) taskCtx.getTask();
+                if (cassStmtTask.getStatement() instanceof BoundStatement) {
+                    BoundStatement stmt = (BoundStatement) cassStmtTask.getStatement();
+                    String query = toStringWithValues(stmt, ProtocolVersion.V5);
+                    log.trace("[{}] {} task: {}, BoundStatement query: {}", taskCtx.getId(), action, taskCtx, query);
+                }
+            } else {
+                log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx);
+            }
         } else {
             log.debug("[{}] {} task", taskCtx.getId(), action);
         }
     }
 
+    private static String toStringWithValues(BoundStatement boundStatement, ProtocolVersion protocolVersion) {
+        CodecRegistry codecRegistry = boundStatement.preparedStatement().getCodecRegistry();
+        PreparedStatement preparedStatement = boundStatement.preparedStatement();
+        String query = preparedStatement.getQueryString();
+        ColumnDefinitions defs = preparedStatement.getVariables();
+        int index = 0;
+        for (ColumnDefinitions.Definition def : defs) {
+            DataType type = def.getType();
+            TypeCodec<Object> codec = codecRegistry.codecFor(type);
+            if (boundStatement.getBytesUnsafe(index) != null) {
+                Object value = codec.deserialize(boundStatement.getBytesUnsafe(index), protocolVersion);
+                query = query.replaceFirst("\\?", codec.format(value));
+            }
+            index++;
+        }
+        return query;
+    }
+
     protected int getQueueSize() {
         return queue.size();
     }
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index 6d08611..7a6ef7b 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS attribute_kv (
   long_v bigint,
   dbl_v double precision,
   last_update_ts bigint,
-  CONSTRAINT attribute_kv_unq_key UNIQUE (entity_type, entity_id, attribute_type, attribute_key)
+  CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key)
 );
 
 CREATE TABLE IF NOT EXISTS component_descriptor (
@@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS relation (
     relation_type_group varchar(255),
     relation_type varchar(255),
     additional_info varchar,
-    CONSTRAINT relation_unq_key UNIQUE (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
+    CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
 );
 
 CREATE TABLE IF NOT EXISTS tb_user (
diff --git a/dao/src/main/resources/sql/schema-ts.sql b/dao/src/main/resources/sql/schema-ts.sql
index 53bc15a..821e297 100644
--- a/dao/src/main/resources/sql/schema-ts.sql
+++ b/dao/src/main/resources/sql/schema-ts.sql
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS ts_kv (
     str_v varchar(10000000),
     long_v bigint,
     dbl_v double precision,
-    CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
+    CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts)
 );
 
 CREATE TABLE IF NOT EXISTS ts_kv_latest (
@@ -35,5 +35,5 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest (
     str_v varchar(10000000),
     long_v bigint,
     dbl_v double precision,
-    CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
+    CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key)
 );
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
index 4c3ac83..bbabc2e 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
@@ -32,7 +32,8 @@ import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.apache.http.ssl.SSLContextBuilder;
 import org.apache.http.ssl.SSLContexts;
-import org.junit.*;
+import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.rules.TestRule;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
@@ -43,7 +44,10 @@ import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
 
-import javax.net.ssl.*;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
 import java.net.URI;
 import java.security.cert.X509Certificate;
 import java.util.List;
@@ -54,6 +58,7 @@ import java.util.Random;
 public abstract class AbstractContainerTest {
     protected static final String HTTPS_URL = "https://localhost";
     protected static final String WSS_URL = "wss://localhost";
+    protected static String TB_TOKEN;
     protected static RestClient restClient;
     protected ObjectMapper mapper = new ObjectMapper();
 
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
index a6e89de..bb3380f 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
@@ -15,6 +15,7 @@
  */
 package org.thingsboard.server.msa.connectivity;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.Sets;
 import org.junit.Assert;
 import org.junit.Test;
@@ -25,6 +26,17 @@ import org.thingsboard.server.msa.AbstractContainerTest;
 import org.thingsboard.server.msa.WsClient;
 import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
 
+
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.thingsboard.server.common.data.DataConstants.DEVICE;
+import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
+
 public class HttpClientTest extends AbstractContainerTest {
 
     @Test
@@ -52,6 +64,58 @@ public class HttpClientTest extends AbstractContainerTest {
         Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
         Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
 
-        restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
+        restClient.deleteDevice(device.getId());
+    }
+
+    @Test
+    public void getAttributes() throws Exception {
+        restClient.login("tenant@thingsboard.org", "tenant");
+        TB_TOKEN = restClient.getToken();
+
+        Device device = createDevice("test");
+        String accessToken = restClient.getCredentials(device.getId()).getCredentialsId();
+        assertNotNull(accessToken);
+
+        ResponseEntity deviceSharedAttributes = restClient.getRestTemplate()
+                .postForEntity(HTTPS_URL + "/api/plugins/telemetry/" + DEVICE + "/" + device.getId().toString() + "/attributes/" + SHARED_SCOPE, mapper.readTree(createPayload().toString()),
+                        ResponseEntity.class,
+                        accessToken);
+
+        Assert.assertTrue(deviceSharedAttributes.getStatusCode().is2xxSuccessful());
+
+        ResponseEntity deviceClientsAttributes = restClient.getRestTemplate()
+                .postForEntity(HTTPS_URL + "/api/v1/" + accessToken + "/attributes/", mapper.readTree(createPayload().toString()),
+                        ResponseEntity.class,
+                        accessToken);
+
+        Assert.assertTrue(deviceClientsAttributes.getStatusCode().is2xxSuccessful());
+
+        TimeUnit.SECONDS.sleep(3);
+
+        Optional<JsonNode> allOptional = restClient.getAttributes(accessToken, null, null);
+        assertTrue(allOptional.isPresent());
+
+
+        JsonNode all = allOptional.get();
+        assertEquals(2, all.size());
+        assertEquals(mapper.readTree(createPayload().toString()), all.get("shared"));
+        assertEquals(mapper.readTree(createPayload().toString()), all.get("client"));
+
+        Optional<JsonNode> sharedOptional = restClient.getAttributes(accessToken, null, "stringKey");
+        assertTrue(sharedOptional.isPresent());
+
+        JsonNode shared = sharedOptional.get();
+        assertEquals(shared.get("shared").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString()));
+        assertFalse(shared.has("client"));
+
+        Optional<JsonNode> clientOptional = restClient.getAttributes(accessToken, "longKey,stringKey", null);
+        assertTrue(clientOptional.isPresent());
+
+        JsonNode client = clientOptional.get();
+        assertFalse(client.has("shared"));
+        assertEquals(mapper.readTree(createPayload().get("longKey").toString()), client.get("client").get("longKey"));
+        assertEquals(client.get("client").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString()));
+
+        restClient.deleteDevice(device.getId());
     }
 }
diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java
index c927c42..d2a1ad1 100644
--- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java
+++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java
@@ -222,6 +222,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> 
 
     private void handlePuback(MqttPubAckMessage message) {
         MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(message.variableHeader().messageId());
+        if (pendingPublish == null) {
+            return;
+        }
         pendingPublish.getFuture().setSuccess(null);
         pendingPublish.onPubackReceived();
         this.client.getPendingPublishes().remove(message.variableHeader().messageId());
diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java
index 9f86b8e..000925e 100644
--- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java
+++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java
@@ -15,8 +15,6 @@
  */
 package org.thingsboard.mqtt;
 
-import io.netty.channel.ChannelId;
-
 /**
  * Created by Valerii Sosliuk on 12/30/2017.
  */

pom.xml 6(+6 -0)

diff --git a/pom.xml b/pom.xml
index b52a1e7..ae83608 100755
--- a/pom.xml
+++ b/pom.xml
@@ -70,6 +70,7 @@
         <surfire.version>2.19.1</surfire.version>
         <jar-plugin.version>3.0.2</jar-plugin.version>
         <springfox-swagger.version>2.6.1</springfox-swagger.version>
+        <springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version>
         <bouncycastle.version>1.56</bouncycastle.version>
         <winsw.version>2.0.1</winsw.version>
         <hsqldb.version>2.4.0</hsqldb.version>
@@ -794,6 +795,11 @@
                 <artifactId>bucket4j-core</artifactId>
                 <version>${bucket4j.version}</version>
             </dependency>
+            <dependency>
+                <groupId>io.springfox.ui</groupId>
+                <artifactId>springfox-swagger-ui-rfc6570</artifactId>
+                <version>${springfox-swagger-ui-rfc6570.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNodeConfiguration.java
index 3f587b5..86b8cb8 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNodeConfiguration.java
@@ -17,9 +17,6 @@ package org.thingsboard.rule.engine.action;
 
 import lombok.Data;
 
-/**
- * Created by igor on 6/1/18.
- */
 @Data
 public abstract class TbAbstractCustomerActionNodeConfiguration {
 
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java
new file mode 100644
index 0000000..c3608fc
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java
@@ -0,0 +1,222 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.rule.engine.util.EntityContainer;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.DashboardInfo;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.relation.EntitySearchDirection;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.dao.asset.AssetService;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.entityview.EntityViewService;
+
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
+import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
+import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
+
+@Slf4j
+public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationActionNodeConfiguration> implements TbNode {
+
+    protected C config;
+    protected EntityId fromId;
+    protected EntityId toId;
+
+    private LoadingCache<Entitykey, EntityContainer> entityIdCache;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = loadEntityNodeActionConfig(configuration);
+        CacheBuilder cacheBuilder = CacheBuilder.newBuilder();
+        if (this.config.getEntityCacheExpiration() > 0) {
+            cacheBuilder.expireAfterWrite(this.config.getEntityCacheExpiration(), TimeUnit.SECONDS);
+        }
+        entityIdCache = cacheBuilder
+                .build(new EntityCacheLoader(ctx, createEntityIfNotExists()));
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) {
+        withCallback(processEntityRelationAction(ctx, msg),
+                filterResult -> ctx.tellNext(msg, filterResult ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    private ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
+        return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
+    }
+
+    protected abstract boolean createEntityIfNotExists();
+
+    protected abstract ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer);
+
+    protected abstract C loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException;
+
+    protected ListenableFuture<EntityContainer> getEntity(TbContext ctx, TbMsg msg) {
+        String entityName = TbNodeUtils.processPattern(this.config.getEntityNamePattern(), msg.getMetaData());
+        String type = null;
+        if (this.config.getEntityTypePattern() != null) {
+            type = TbNodeUtils.processPattern(this.config.getEntityTypePattern(), msg.getMetaData());
+        }
+        EntityType entityType = EntityType.valueOf(this.config.getEntityType());
+        Entitykey key = new Entitykey(entityName, type, entityType);
+        return ctx.getDbCallbackExecutor().executeAsync(() -> {
+            EntityContainer entityContainer = entityIdCache.get(key);
+            if (entityContainer.getEntityId() == null) {
+                throw new RuntimeException("No entity found with type '" + key.getEntityType() + " ' and name '" + key.getEntityName() + "'.");
+            }
+            return entityContainer;
+        });
+    }
+
+    protected void processSearchDirection(TbMsg msg, EntityContainer entityContainer) {
+        if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
+            fromId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
+            toId = msg.getOriginator();
+        } else {
+            toId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
+            fromId = msg.getOriginator();
+        }
+    }
+
+    @Data
+    @AllArgsConstructor
+    private static class Entitykey {
+        private String entityName;
+        private String type;
+        private EntityType entityType;
+    }
+
+    private static class EntityCacheLoader extends CacheLoader<Entitykey, EntityContainer> {
+
+        private final TbContext ctx;
+        private final boolean createIfNotExists;
+
+        private EntityCacheLoader(TbContext ctx, boolean createIfNotExists) {
+            this.ctx = ctx;
+            this.createIfNotExists = createIfNotExists;
+        }
+
+        @Override
+        public EntityContainer load(Entitykey key) {
+            return loadEntity(key);
+        }
+
+        private EntityContainer loadEntity(Entitykey entitykey) {
+            EntityType type = entitykey.getEntityType();
+            EntityContainer targetEntity = new EntityContainer();
+            targetEntity.setEntityType(type);
+            switch (type) {
+                case DEVICE:
+                    DeviceService deviceService = ctx.getDeviceService();
+                    Device device = deviceService.findDeviceByTenantIdAndName(ctx.getTenantId(), entitykey.getEntityName());
+                    if (device != null) {
+                        targetEntity.setEntityId(device.getId());
+                    } else if (createIfNotExists) {
+                        Device newDevice = new Device();
+                        newDevice.setName(entitykey.getEntityName());
+                        newDevice.setType(entitykey.getType());
+                        newDevice.setTenantId(ctx.getTenantId());
+                        Device savedDevice = deviceService.saveDevice(newDevice);
+                        targetEntity.setEntityId(savedDevice.getId());
+                    }
+                    break;
+                case ASSET:
+                    AssetService assetService = ctx.getAssetService();
+                    Asset asset = assetService.findAssetByTenantIdAndName(ctx.getTenantId(), entitykey.getEntityName());
+                    if (asset != null) {
+                        targetEntity.setEntityId(asset.getId());
+                    } else if (createIfNotExists) {
+                        Asset newAsset = new Asset();
+                        newAsset.setName(entitykey.getEntityName());
+                        newAsset.setType(entitykey.getType());
+                        newAsset.setTenantId(ctx.getTenantId());
+                        Asset savedAsset = assetService.saveAsset(newAsset);
+                        targetEntity.setEntityId(savedAsset.getId());
+                    }
+                    break;
+                case CUSTOMER:
+                    CustomerService customerService = ctx.getCustomerService();
+                    Optional<Customer> customerOptional = customerService.findCustomerByTenantIdAndTitle(ctx.getTenantId(), entitykey.getEntityName());
+                    if (customerOptional.isPresent()) {
+                        targetEntity.setEntityId(customerOptional.get().getId());
+                    } else if (createIfNotExists) {
+                        Customer newCustomer = new Customer();
+                        newCustomer.setTitle(entitykey.getEntityName());
+                        newCustomer.setTenantId(ctx.getTenantId());
+                        Customer savedCustomer = customerService.saveCustomer(newCustomer);
+                        targetEntity.setEntityId(savedCustomer.getId());
+                    }
+                    break;
+                case TENANT:
+                    targetEntity.setEntityId(ctx.getTenantId());
+                    break;
+                case ENTITY_VIEW:
+                    EntityViewService entityViewService = ctx.getEntityViewService();
+                    EntityView entityView = entityViewService.findEntityViewByTenantIdAndName(ctx.getTenantId(), entitykey.getEntityName());
+                    if (entityView != null) {
+                        targetEntity.setEntityId(entityView.getId());
+                    }
+                    break;
+                case DASHBOARD:
+                    DashboardService dashboardService = ctx.getDashboardService();
+                    TextPageData<DashboardInfo> dashboardInfoTextPageData = dashboardService.findDashboardsByTenantId(ctx.getTenantId(), new TextPageLink(200, entitykey.getEntityName()));
+                    for (DashboardInfo dashboardInfo : dashboardInfoTextPageData.getData()) {
+                        if (dashboardInfo.getTitle().equals(entitykey.getEntityName())) {
+                            targetEntity.setEntityId(dashboardInfo.getId());
+                        }
+                    }
+                    break;
+                default:
+                    return targetEntity;
+            }
+            return targetEntity;
+        }
+
+
+    }
+
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNodeConfiguration.java
new file mode 100644
index 0000000..761dc43
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNodeConfiguration.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import lombok.Data;
+
+@Data
+public abstract class TbAbstractRelationActionNodeConfiguration {
+
+    private String direction;
+    private String relationType;
+
+    private String entityType;
+    private String entityNamePattern;
+    private String entityTypePattern;
+
+    private long entityCacheExpiration;
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java
index 7c50aad..70b5bd3 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java
@@ -52,6 +52,10 @@ public class TbAssignToCustomerNode extends TbAbstractCustomerActionNode<TbAssig
 
     @Override
     protected void doProcessCustomerAction(TbContext ctx, TbMsg msg, CustomerId customerId) {
+        processAssign(ctx, msg, customerId);
+    }
+
+    private void processAssign(TbContext ctx, TbMsg msg, CustomerId customerId) {
         EntityType originatorType = msg.getOriginator().getEntityType();
         switch (originatorType) {
             case DEVICE:
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
index a660c93..82a645c 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
@@ -16,6 +16,7 @@
 package org.thingsboard.rule.engine.action;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Function;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -31,6 +32,8 @@ import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.common.msg.TbMsg;
 
+import java.io.IOException;
+
 @Slf4j
 @RuleNode(
         type = ComponentType.ACTION,
@@ -49,6 +52,8 @@ import org.thingsboard.server.common.msg.TbMsg;
 )
 public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
 
+    private static ObjectMapper mapper = new ObjectMapper();
+
     @Override
     protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
         return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class);
@@ -56,32 +61,59 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
 
     @Override
     protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
-        ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
-        return Futures.transformAsync(latest, a -> {
-            if (a == null || a.getStatus().isCleared()) {
-                return createNewAlarm(ctx, msg);
+        String alarmType;
+        final Alarm msgAlarm;
+
+        if (!config.isUseMessageAlarmData()) {
+            alarmType = config.getAlarmType();
+            msgAlarm = null;
+        } else {
+            try {
+                msgAlarm = mapper.readValue(msg.getData(), Alarm.class);
+                msgAlarm.setTenantId(ctx.getTenantId());
+                alarmType = msgAlarm.getType();
+            } catch (IOException e) {
+                ctx.tellFailure(msg, e);
+                return null;
+            }
+        }
+
+        ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
+        return Futures.transformAsync(latest, existingAlarm -> {
+            if (existingAlarm == null || existingAlarm.getStatus().isCleared()) {
+                return createNewAlarm(ctx, msg, msgAlarm);
             } else {
-                return updateAlarm(ctx, msg, a);
+                return updateAlarm(ctx, msg, existingAlarm, msgAlarm);
             }
         }, ctx.getDbCallbackExecutor());
 
     }
 
-    private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) {
-        ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
-                details -> buildAlarm(msg, details, ctx.getTenantId()));
+    private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) {
+        ListenableFuture<Alarm> asyncAlarm;
+        if (msgAlarm != null ) {
+            asyncAlarm = Futures.immediateCheckedFuture(msgAlarm);
+        } else {
+            asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
+                    details -> buildAlarm(msg, details, ctx.getTenantId()));
+        }
         ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm,
                 alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
         return Futures.transform(asyncCreated, alarm -> new AlarmResult(true, false, false, alarm));
     }
 
-    private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
-        ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, alarm.getDetails()), (Function<JsonNode, Alarm>) details -> {
-            alarm.setSeverity(config.getSeverity());
-            alarm.setPropagate(config.isPropagate());
-            alarm.setDetails(details);
-            alarm.setEndTs(System.currentTimeMillis());
-            return ctx.getAlarmService().createOrUpdateAlarm(alarm);
+    private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) {
+        ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, existingAlarm.getDetails()), (Function<JsonNode, Alarm>) details -> {
+            if (msgAlarm != null) {
+                existingAlarm.setSeverity(msgAlarm.getSeverity());
+                existingAlarm.setPropagate(msgAlarm.isPropagate());
+            } else {
+                existingAlarm.setSeverity(config.getSeverity());
+                existingAlarm.setPropagate(config.isPropagate());
+            }
+            existingAlarm.setDetails(details);
+            existingAlarm.setEndTs(System.currentTimeMillis());
+            return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
         }, ctx.getDbCallbackExecutor());
 
         return Futures.transform(asyncUpdated, a -> new AlarmResult(false, true, false, a));
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
index b424794..892ad27 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
@@ -24,6 +24,7 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
 
     private AlarmSeverity severity;
     private boolean propagate;
+    private boolean useMessageAlarmData;
 
     @Override
     public TbCreateAlarmNodeConfiguration defaultConfiguration() {
@@ -36,6 +37,7 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
         configuration.setAlarmType("General Alarm");
         configuration.setSeverity(AlarmSeverity.CRITICAL);
         configuration.setPropagate(false);
+        configuration.setUseMessageAlarmData(false);
         return configuration;
     }
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
new file mode 100644
index 0000000..a0b9709
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
@@ -0,0 +1,156 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.rule.engine.util.EntityContainer;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityViewId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.msg.TbMsg;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "create relation",
+        configClazz = TbCreateRelationNodeConfiguration.class,
+        nodeDescription = "Finds target Entity by entity name pattern and (entity type pattern for Asset, Device) and then create a relation to Originator Entity by type and direction." +
+                " If Selected entity type: Asset, Device or Customer will create new Entity if it doesn't exist and 'Create new entity if not exists' is set to true.",
+        nodeDetails = "If the relation already exists or successfully created -  Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeCreateRelationConfig",
+        icon = "add_circle"
+)
+public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateRelationNodeConfiguration> {
+
+    @Override
+    protected TbCreateRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException {
+        return TbNodeUtils.convert(configuration, TbCreateRelationNodeConfiguration.class);
+    }
+
+    @Override
+    protected boolean createEntityIfNotExists() {
+        return config.isCreateEntityIfNotExists();
+    }
+
+    @Override
+    protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) {
+        return createIfAbsent(ctx, msg, entity);
+    }
+
+    private ListenableFuture<Boolean> createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
+        processSearchDirection(msg, entityContainer);
+        return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
+                result -> {
+                    if (!result) {
+                        return processCreateRelation(ctx, entityContainer);
+                    }
+                    return Futures.immediateFuture(true);
+                });
+    }
+
+    private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer) {
+        switch (entityContainer.getEntityType()) {
+            case ASSET:
+                return processAsset(ctx, entityContainer);
+            case DEVICE:
+                return processDevice(ctx, entityContainer);
+            case CUSTOMER:
+                return processCustomer(ctx, entityContainer);
+            case DASHBOARD:
+                return processDashboard(ctx, entityContainer);
+            case ENTITY_VIEW:
+                return processView(ctx, entityContainer);
+            case TENANT:
+                return processTenant(ctx, entityContainer);
+        }
+        return Futures.immediateFuture(true);
+    }
+
+    private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> {
+            if (entityView != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+    private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> {
+            if (device != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+    private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> {
+            if (asset != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+    private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> {
+            if (customer != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+    private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> {
+            if (dashboard != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+    private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer) {
+        return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> {
+            if (tenant != null) {
+                return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
+            } else {
+                return Futures.immediateFuture(true);
+            }
+        });
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java
new file mode 100644
index 0000000..83d267a
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.relation.EntitySearchDirection;
+
+@Data
+public class TbCreateRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration<TbCreateRelationNodeConfiguration> {
+
+    private boolean createEntityIfNotExists;
+
+    @Override
+    public TbCreateRelationNodeConfiguration defaultConfiguration() {
+        TbCreateRelationNodeConfiguration configuration = new TbCreateRelationNodeConfiguration();
+        configuration.setDirection(EntitySearchDirection.FROM.name());
+        configuration.setRelationType("Contains");
+        configuration.setEntityNamePattern("");
+        configuration.setEntityCacheExpiration(300);
+        configuration.setCreateEntityIfNotExists(false);
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
new file mode 100644
index 0000000..c339ffc
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.rule.engine.util.EntityContainer;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.msg.TbMsg;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "delete relation",
+        configClazz = TbDeleteRelationNodeConfiguration.class,
+        nodeDescription = "Finds target Entity by entity name pattern and then delete a relation to Originator Entity by type and direction.",
+        nodeDetails = "If the relation successfully deleted -  Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeDeleteRelationConfig",
+        icon = "remove_circle"
+)
+public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteRelationNodeConfiguration> {
+
+    @Override
+    protected TbDeleteRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException {
+        return TbNodeUtils.convert(configuration, TbDeleteRelationNodeConfiguration.class);
+    }
+
+    @Override
+    protected boolean createEntityIfNotExists() {
+        return false;
+    }
+
+    @Override
+    protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
+        return deleteIfExist(ctx, msg, entityContainer);
+    }
+
+    private ListenableFuture<Boolean> deleteIfExist(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
+        processSearchDirection(msg, entityContainer);
+        return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
+                result -> {
+                    if (result) {
+                        return processDeleteRelation(ctx);
+                    }
+                    return Futures.immediateFuture(true);
+                });
+    }
+
+    private ListenableFuture<Boolean> processDeleteRelation(TbContext ctx) {
+        return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON);
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java
new file mode 100644
index 0000000..f786404
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.action;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.relation.EntitySearchDirection;
+
+@Data
+public class TbDeleteRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration<TbDeleteRelationNodeConfiguration> {
+
+
+    @Override
+    public TbDeleteRelationNodeConfiguration defaultConfiguration() {
+        TbDeleteRelationNodeConfiguration configuration = new TbDeleteRelationNodeConfiguration();
+        configuration.setDirection(EntitySearchDirection.FROM.name());
+        configuration.setRelationType("Contains");
+        configuration.setEntityNamePattern("");
+        configuration.setEntityCacheExpiration(300);
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
index 66569b7..d636339 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
@@ -21,8 +21,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.util.concurrent.ListenableFuture;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.rule.engine.api.*;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
 import org.thingsboard.rule.engine.api.util.DonAsynchron;
 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@@ -37,7 +44,9 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
-import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.*;
+import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL;
+import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST;
+import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
 import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
 
 /**
@@ -50,6 +59,7 @@ import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
         nodeDescription = "Add Message Originator Telemetry for selected time range into Message Metadata\n",
         nodeDetails = "The node allows you to select fetch mode <b>FIRST/LAST/ALL</b> to fetch telemetry of certain time range that are added into Message metadata without any prefix. " +
                 "If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry. " +
+                "<b>Note</b>: The maximum size of the fetched array is 1000 records. " +
                 "If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp",
         uiResources = {"static/rulenode/rulenode-core-config.js"},
         configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
@@ -57,8 +67,6 @@ public class TbGetTelemetryNode implements TbNode {
 
     private TbGetTelemetryNodeConfiguration config;
     private List<String> tsKeyNames;
-    private long startTsOffset;
-    private long endTsOffset;
     private int limit;
     private ObjectMapper mapper;
     private String fetchMode;
@@ -67,8 +75,6 @@ public class TbGetTelemetryNode implements TbNode {
     public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
         this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class);
         tsKeyNames = config.getLatestTsKeyNames();
-        startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval());
-        endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
         limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
         fetchMode = config.getFetchMode();
         mapper = new ObjectMapper();
@@ -82,8 +88,10 @@ public class TbGetTelemetryNode implements TbNode {
             ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!"));
         } else {
             try {
-                List<ReadTsKvQuery> queries = buildQueries();
-                ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), queries);
+                if (config.isUseMetadataIntervalPatterns()) {
+                    checkMetadataKeyPatterns(msg);
+                }
+                ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg));
                 DonAsynchron.withCallback(list, data -> {
                     process(data, msg);
                     TbMsg newMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
@@ -95,10 +103,12 @@ public class TbGetTelemetryNode implements TbNode {
         }
     }
 
-    private List<ReadTsKvQuery> buildQueries() {
-        long ts = System.currentTimeMillis();
-        long startTs = ts - startTsOffset;
-        long endTs = ts - endTsOffset;
+    @Override
+    public void destroy() {
+
+    }
+
+    private List<ReadTsKvQuery> buildQueries(TbMsg msg) {
         String orderBy;
         if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
             orderBy = "ASC";
@@ -106,7 +116,7 @@ public class TbGetTelemetryNode implements TbNode {
             orderBy = "DESC";
         }
         return tsKeyNames.stream()
-                .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
+                .map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, limit, NONE, orderBy))
                 .collect(Collectors.toList());
     }
 
@@ -162,8 +172,79 @@ public class TbGetTelemetryNode implements TbNode {
         return obj;
     }
 
-    @Override
-    public void destroy() {
+    private Interval getInterval(TbMsg msg) {
+        Interval interval = new Interval();
+        if (config.isUseMetadataIntervalPatterns()) {
+            if (isParsable(msg, config.getStartIntervalPattern())) {
+                interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg.getMetaData())));
+            }
+            if (isParsable(msg, config.getEndIntervalPattern())) {
+                interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg.getMetaData())));
+            }
+        } else {
+            long ts = System.currentTimeMillis();
+            interval.setStartTs(ts - TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval()));
+            interval.setEndTs(ts - TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval()));
+        }
+        return interval;
+    }
+
+    private boolean isParsable(TbMsg msg, String pattern) {
+        return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData()));
+    }
 
+    private void checkMetadataKeyPatterns(TbMsg msg) {
+        isUndefined(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern());
+        isInvalid(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern());
     }
+
+    private void isUndefined(TbMsg msg, String startIntervalPattern, String endIntervalPattern) {
+        if (getMetadataValue(msg, startIntervalPattern) == null && getMetadataValue(msg, endIntervalPattern) == null) {
+            throw new IllegalArgumentException("Message metadata values: '" +
+                    replaceRegex(startIntervalPattern) + "' and '" +
+                    replaceRegex(endIntervalPattern) + "' are undefined");
+        } else {
+            if (getMetadataValue(msg, startIntervalPattern) == null) {
+                throw new IllegalArgumentException("Message metadata value: '" +
+                        replaceRegex(startIntervalPattern) + "' is undefined");
+            }
+            if (getMetadataValue(msg, endIntervalPattern) == null) {
+                throw new IllegalArgumentException("Message metadata value: '" +
+                        replaceRegex(endIntervalPattern) + "' is undefined");
+            }
+        }
+    }
+
+    private void isInvalid(TbMsg msg, String startIntervalPattern, String endIntervalPattern) {
+        if (getInterval(msg).getStartTs() == null && getInterval(msg).getEndTs() == null) {
+            throw new IllegalArgumentException("Message metadata values: '" +
+                    replaceRegex(startIntervalPattern) + "' and '" +
+                    replaceRegex(endIntervalPattern) + "' have invalid format");
+        } else {
+            if (getInterval(msg).getStartTs() == null) {
+                throw new IllegalArgumentException("Message metadata value: '" +
+                        replaceRegex(startIntervalPattern) + "' has invalid format");
+            }
+            if (getInterval(msg).getEndTs() == null) {
+                throw new IllegalArgumentException("Message metadata value: '" +
+                        replaceRegex(endIntervalPattern) + "' has invalid format");
+            }
+        }
+    }
+
+    private String getMetadataValue(TbMsg msg, String pattern) {
+        return msg.getMetaData().getValue(replaceRegex(pattern));
+    }
+
+    private String replaceRegex(String pattern) {
+        return pattern.replaceAll("[${}]", "");
+    }
+
+    @Data
+    @NoArgsConstructor
+    private static class Interval {
+        private Long startTs;
+        private Long endTs;
+    }
+
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
index 37ce0af..31bacf9 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
@@ -35,6 +35,12 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
 
     private int startInterval;
     private int endInterval;
+
+    private String startIntervalPattern;
+    private String endIntervalPattern;
+
+    private boolean useMetadataIntervalPatterns;
+
     private String startIntervalTimeUnit;
     private String endIntervalTimeUnit;
     private String fetchMode; //FIRST, LAST, LATEST
@@ -52,6 +58,9 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
         configuration.setStartInterval(2);
         configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
         configuration.setEndInterval(1);
+        configuration.setUseMetadataIntervalPatterns(false);
+        configuration.setStartIntervalPattern("");
+        configuration.setEndIntervalPattern("");
         return configuration;
     }
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java
new file mode 100644
index 0000000..33c6a49
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.util;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.EntityId;
+
+@Data
+public class EntityContainer {
+
+    private EntityId entityId;
+    private EntityType entityType;
+
+}
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
index 8151e7d..53b5213 100644
--- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
@@ -1,4 +1,5 @@
-!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(85)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-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=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <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=0> <div ng-messages=msgDelayConfigForm.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-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </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 ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</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> "},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 class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <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> <md-input-container style=margin-bottom:38px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </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> "},26,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" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </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 r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(61),i=r(a),o=n(44),l=r(o),s=n(47),u=r(s),d=n(46),c=r(d),m=n(45),g=r(m),p=n(50),f=r(p),b=n(56),v=r(b),y=n(57),q=r(y),h=n(55),$=r(h),T=n(49),k=r(T),x=n(59),w=r(x),C=n(60),M=r(C),_=n(54),S=r(_),N=n(51),V=r(N),E=n(58),P=r(E),F=n(53),j=r(F),A=n(52),O=r(A),I=n(43),K=r(I),D=n(62),R=r(D);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",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",w.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",j.default).directive("tbActionNodeMsgCountConfig",O.default).directive("tbActionNodeAssignToCustomerConfig",K.default).directive("tbActionNodeUnAssignToCustomerConfig",R.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$mdExpansionPanel=t,r.ruleNodeTypes=n,r.credentialsTypeChanged=function(){var e=r.configuration.credentials.type;r.configuration.credentials={},r.configuration.credentials.type=e,r.updateValidity()},r.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){r.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(r.configuration.credentials.caCertFileName=e.name,r.configuration.credentials.caCert=a),"privateKey"==t&&(r.configuration.credentials.privateKeyFileName=e.name,r.configuration.credentials.privateKey=a),"Cert"==t&&(r.configuration.credentials.certFileName=e.name,r.configuration.credentials.cert=a)),r.updateValidity()}})},n.readAsText(e.file)},r.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(r.configuration.credentials.caCertFileName=null,r.configuration.credentials.caCert=null),"privateKey"==e&&(r.configuration.credentials.privateKeyFileName=null,r.configuration.credentials.privateKey=null),"Cert"==e&&(r.configuration.credentials.certFileName=null,r.configuration.credentials.cert=null),r.updateValidity()},r.updateValidity=function(){var e=!0,t=r.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";
-function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(21),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(22),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(23),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(24),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(25),o=r(i)},function(e,t){"use strict";function n(e){var t=function(t,n,r,a){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(26),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(27),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s);var u=186;r.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],r.ruleNodeTypes=n,r.aggPeriodTimeUnits={},r.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,r.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,r.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,r.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,r.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(28),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(69),i=r(a),o=n(70),l=r(o),s=n(66),u=r(s),d=n(71),c=r(d),m=n(65),g=r(m),p=n(72),f=r(p),b=n(67),v=r(b);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).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(29),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(30),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(31),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(32),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(33),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(77),i=r(a),o=n(75),l=r(o),s=n(78),u=r(s),d=n(73),c=r(d),m=n(76),g=r(m);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){r.messageTypesWatch&&(r.messageTypesWatch(),r.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.messageTypesWatch=r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(4);var i=n(34),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(35),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(36),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(37),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(38),o=r(i);n(5)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(39),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(40),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(81),i=r(a),o=n(83),l=r(o),s=n(84),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(41),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(42),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(88),i=r(a),o=n(74),l=r(o),s=n(68),u=r(s),d=n(82),c=r(d),m=n(48),g=r(m),p=n(64),f=r(p),b=n(80),v=r(b),y=n(63),q=r(y),h=n(79),$=r(h),T=n(87),k=r(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",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","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(86),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{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"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},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}]));
+!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(89)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-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> <md-checkbox aria-label=\"{{ 'tb.rulenode.use-metadata-interval-patterns' | translate }}\" ng-model=configuration.useMessageAlarmData>{{ 'tb.rulenode.use-message-alarm-data' | translate }} </md-checkbox> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <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> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> </section> "},function(e,t){e.exports=" <section ng-form name=createRelationConfigForm layout=column style=min-width:650px> <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> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=createRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=createRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if=\"configuration.entityType  == 'DEVICE' || configuration.entityType  == 'ASSET'\" style=margin-top:38px> <label translate>tb.rulenode.entity-type-pattern</label> <input ng-required=true name=entityTypePattern ng-model=configuration.entityTypePattern> <div ng-messages=createRelationConfigForm.entityTypePattern.$error> <div ng-message=required translate>tb.rulenode.entity-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> <md-checkbox flex ng-if=\"configuration.entityType  == 'CUSTOMER' || configuration.entityType  == 'ASSET' || configuration.entityType  == 'DEVICE'\" aria-label=\"{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}\" ng-model=configuration.createEntityIfNotExists>{{ 'tb.rulenode.create-entity-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=createRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=deleteRelationConfigForm layout=column> <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> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=deleteRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=deleteRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=deleteRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.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=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <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=0> <div ng-messages=msgDelayConfigForm.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-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </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 ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</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> "},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 class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <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> <md-input-container style=margin-bottom:38px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}" ng-model=configuration.useMetadataIntervalPatterns>{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }} </md-checkbox> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns == false "> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns === false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true" style=margin-top:38px> <label translate>tb.rulenode.start-interval-pattern</label> <input ng-required=true name=startIntervalPattern ng-model=configuration.startIntervalPattern> <div ng-messages=getTelemetryConfigForm.startIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.start-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.start-interval-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true"> <label translate>tb.rulenode.end-interval-pattern</label> <input ng-required=true name=endIntervalPattern ng-model=configuration.endIntervalPattern> <div ng-messages=getTelemetryConfigForm.endIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.end-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.end-interval-pattern-hint</div> </md-input-container> </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> "},28,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" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </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(6),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(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.$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(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,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(9),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(10),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(11),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(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(65),i=a(r),o=n(46),l=a(o),s=n(51),u=a(s),d=n(48),c=a(d),m=n(47),g=a(m),p=n(54),f=a(p),b=n(60),v=a(b),y=n(61),q=a(y),h=n(59),$=a(h),x=n(53),T=a(x),k=n(63),C=a(k),w=n(64),M=a(w),_=n(58),S=a(_),N=n(55),E=a(N),P=n(62),V=a(P),F=n(57),j=a(F),A=n(56),I=a(A),O=n(45),D=a(O),K=n(66),R=a(K),U=n(50),L=a(U),z=n(49),B=a(z);
+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",$.default).directive("tbActionNodeKafkaConfig",T.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",V.default).directive("tbActionNodeMsgDelayConfig",j.default).directive("tbActionNodeMsgCountConfig",I.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",R.default).directive("tbActionNodeDeleteRelationConfig",L.default).directive("tbActionNodeCreateRelationConfig",B.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(13),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(14),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(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){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){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(18),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(19),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(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.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(22),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(23),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(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,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(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("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t){"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(28),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(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);var u=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,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:{},link:a}}r.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i);n(3)},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(73),i=a(r),o=n(74),l=a(o),s=n(70),u=a(s),d=n(75),c=a(d),m=n(69),g=a(m),p=n(76),f=a(p),b=n(71),v=a(b);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).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.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(31),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(32),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(33),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(34),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(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(81),i=a(r),o=n(79),l=a(o),s=n(82),u=a(s),d=n(77),c=a(d),m=n(80),g=a(m);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);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.messageTypesWatch=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","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(4);var i=n(36),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.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],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(37),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(38),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(39),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(40),o=a(i);n(5)},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(41),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(42),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(85),i=a(r),o=n(87),l=a(o),s=n(88),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(43),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(44),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(92),i=a(r),o=n(78),l=a(o),s=n(72),u=a(s),d=n(86),c=a(d),m=n(52),g=a(m),p=n(68),f=a(p),b=n(84),v=a(b),y=n(67),q=a(y),h=n(83),$=a(h),x=n(91),T=a(x);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",$.default).config(T.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",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","use-metadata-interval-patterns":"Use metadata interval patterns","use-message-alarm-data":"Use message alarm data","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","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","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("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){(0,o.default)(e)}r.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(90),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{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"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},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}]));
 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file

tools/pom.xml 1(+0 -1)

diff --git a/tools/pom.xml b/tools/pom.xml
index 2150803..be66716 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -23,7 +23,6 @@
         <version>2.3.0-SNAPSHOT</version>
         <artifactId>thingsboard</artifactId>
     </parent>
-    <groupId>org.thingsboard</groupId>
     <artifactId>tools</artifactId>
     <packaging>jar</packaging>
 
diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java
index 4c8c0a4..53ec22d 100644
--- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java
+++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java
@@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.relation.EntityRelation;
 import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -107,6 +108,27 @@ public class RestClient implements ClientHttpRequestInterceptor {
         }
     }
 
+    public Optional<JsonNode> getAttributes(String accessToken, String clientKeys, String sharedKeys) {
+        Map<String, String> params = new HashMap<>();
+        params.put("accessToken", accessToken);
+        params.put("clientKeys", clientKeys);
+        params.put("sharedKeys", sharedKeys);
+        try {
+            ResponseEntity<JsonNode> telemetryEntity = restTemplate.getForEntity(baseURL + "/api/v1/{accessToken}/attributes?clientKeys={clientKeys}&sharedKeys={sharedKeys}", JsonNode.class, params);
+            return Optional.of(telemetryEntity.getBody());
+        } catch (HttpClientErrorException exception) {
+            if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
+                return Optional.empty();
+            } else {
+                throw exception;
+            }
+        }
+    }
+
+    public Customer createCustomer(Customer customer) {
+        return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody();
+    }
+
     public Customer createCustomer(String title) {
         Customer customer = new Customer();
         customer.setTitle(title);
@@ -120,6 +142,25 @@ public class RestClient implements ClientHttpRequestInterceptor {
         return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
     }
 
+    public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) {
+        DeviceCredentials deviceCredentials = getCredentials(deviceId);
+        deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
+        deviceCredentials.setCredentialsId(token);
+        return saveDeviceCredentials(deviceCredentials);
+    }
+
+    public DeviceCredentials saveDeviceCredentials(DeviceCredentials deviceCredentials) {
+        return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody();
+    }
+
+    public Device createDevice(Device device) {
+        return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
+    }
+
+    public Asset createAsset(Asset asset) {
+        return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody();
+    }
+
     public Asset createAsset(String name, String type) {
         Asset asset = new Asset();
         asset.setName(name);
@@ -131,6 +172,18 @@ public class RestClient implements ClientHttpRequestInterceptor {
         return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody();
     }
 
+    public void deleteCustomer(CustomerId customerId) {
+        restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId);
+    }
+
+    public void deleteDevice(DeviceId deviceId) {
+        restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId);
+    }
+
+    public void deleteAsset(AssetId assetId) {
+        restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId);
+    }
+
     public Device assignDevice(CustomerId customerId, DeviceId deviceId) {
         return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class,
                 customerId.toString(), deviceId.toString()).getBody();
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 4a38f71..4ac98b5 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -648,6 +648,12 @@ export default class Subscription {
     }
 
     dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
+        for (var x = 0; x < this.datasourceListeners.length; x++) {
+            this.datasources[x].dataReceived = this.datasources[x].dataReceived === true;
+            if (this.datasourceListeners[x].datasourceIndex === datasourceIndex && sourceData.data.length > 0) {
+                this.datasources[x].dataReceived = true;
+            }
+        }
         this.notifyDataLoaded();
         var update = true;
         var currentData;
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
index 2dfc961..96c398b 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
@@ -31,17 +31,38 @@ export default function ExtensionFormModbusDirective($compile, $templateCache, $
 
     var linker = function(scope, element) {
 
+        function TcpTransport() {
+            this.type = "tcp",
+            this.host = "localhost",
+            this.port = 502,
+            this.timeout = 5000,
+            this.reconnect = true,
+            this.rtuOverTcp = false
+        }
+
+        function UdpTransport() {
+            this.type = "udp",
+            this.host = "localhost",
+            this.port = 502,
+            this.timeout = 5000
+        }
+
+        function RtuTransport() {
+            this.type = "rtu",
+            this.portName = "COM1",
+            this.encoding = "ascii",
+            this.timeout = 5000,
+            this.baudRate = 115200,
+            this.dataBits = 7,
+            this.stopBits = 1,
+            this.parity ="even"
+        }
 
         function Server() {
-            this.transport = {
-                "type": "tcp",
-                "host": "localhost",
-                "port": 502,
-                "timeout": 3000
-            };
+            this.transport = new TcpTransport();
             this.devices = []
         }
-
+		
         function Device() {
             this.unitId = 1;
             this.deviceName = "";
@@ -105,9 +126,13 @@ export default function ExtensionFormModbusDirective($compile, $templateCache, $
         scope.onTransportChanged = function(server) {
             var type = server.transport.type;
 
-            server.transport = {};
-            server.transport.type = type;
-            server.transport.timeout = 3000;
+            if (type === "tcp") {
+                server.transport = new TcpTransport();
+            } else if (type === "udp") {
+                server.transport = new UdpTransport();
+            } else if (type === "rtu") {
+                server.transport = new RtuTransport();
+            }
 
             scope.theForm.$setDirty();
         };
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
index 989d3f0..508ddc0 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
@@ -73,49 +73,69 @@
                                             
                                         </div>
                                         
-                                        <div layout="row" ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
-                                            <md-input-container flex="33" class="md-block">
-                                                <label translate>extension.host</label>
-                                                <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
-                                                <div ng-messages="theForm['transportHost_' + serverIndex].$error">
-                                                    <div translate ng-message="required">extension.field-required</div>
-                                                </div>
-                                            </md-input-container>
+                                        <div ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
+                                            <div layout="row">
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.host</label>
+                                                    <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
+                                                    <div ng-messages="theForm['transportHost_' + serverIndex].$error">
+                                                        <div translate ng-message="required">extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
 
-                                            <md-input-container flex="33" class="md-block">
-                                                <label translate>extension.port</label>
-                                                <input type="number"
-                                                       required
-                                                       name="transportPort_{{serverIndex}}"
-                                                       ng-model="server.transport.port"
-                                                       min="1"
-                                                       max="65535"
-                                                >
-                                                <div ng-messages="theForm['transportPort_' + serverIndex].$error">
-                                                    <div translate
-                                                         ng-message="required"
-                                                    >extension.field-required</div>
-                                                    <div translate
-                                                         ng-message="min"
-                                                    >extension.port-range</div>
-                                                    <div translate
-                                                         ng-message="max"
-                                                    >extension.port-range</div>
-                                                </div>
-                                            </md-input-container>
-                                            
-                                            <md-input-container flex="33" class="md-block">
-                                                <label translate>extension.timeout</label>
-                                                <input type="number"
-                                                       required name="transportTimeout_{{serverIndex}}"
-                                                       ng-model="server.transport.timeout"
-                                                >
-                                                <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
-                                                    <div translate
-                                                         ng-message="required"
-                                                    >extension.field-required</div>
-                                                </div>
-                                            </md-input-container>
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.port</label>
+                                                    <input type="number"
+                                                           required
+                                                           name="transportPort_{{serverIndex}}"
+                                                           ng-model="server.transport.port"
+                                                           min="1"
+                                                           max="65535"
+                                                    >
+                                                    <div ng-messages="theForm['transportPort_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                        <div translate
+                                                             ng-message="min"
+                                                        >extension.port-range</div>
+                                                        <div translate
+                                                             ng-message="max"
+                                                        >extension.port-range</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.timeout</label>
+                                                    <input type="number"
+                                                           required name="transportTimeout_{{serverIndex}}"
+                                                           ng-model="server.transport.timeout"
+                                                    >
+                                                    <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                            </div>
+
+                                            <div layout="row" ng-if="server.transport.type == 'tcp'">
+                                                <md-input-container flex="50" class="md-block">
+                                                    <md-checkbox aria-label="{{ 'extension.modbus-tcp-reconnect' | translate }}"
+                                                                 ng-checked="server.transport.reconnect"
+                                                                 name="transportTcpReconnect_{{serverIndex}}"
+                                                                 ng-model="server.transport.reconnect">{{ 'extension.modbus-tcp-reconnect' | translate }}
+                                                    </md-checkbox>
+                                                </md-input-container>
+
+                                                <md-input-container flex="50" class="md-block">
+                                                    <md-checkbox aria-label="{{ 'extension.modbus-rtu-over-tcp' | translate }}"
+                                                                 ng-checked="server.transport.rtuOverTcp"
+                                                                 name="transportRtuOverTcp_{{serverIndex}}"
+                                                                 ng-model="server.transport.rtuOverTcp">{{ 'extension.modbus-rtu-over-tcp' | translate }}
+                                                    </md-checkbox>
+                                                </md-input-container>
+                                            </div>
                                         </div>
                                         
                                         <div ng-if="server.transport.type == 'rtu'">
diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
index 9f0b547..6c0ab90 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
@@ -261,7 +261,7 @@
                                                                                 </md-input-container>
                                                                                 <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid">
                                                                                     <label translate>extension.filter-expression</label>
-                                                                                    <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
+                                                                                    <input name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
                                                                                     <div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error">
                                                                                         <div translate ng-message="required">extension.field-required</div>
                                                                                     </div>
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 30e1eb1..3aa520a 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -1008,6 +1008,8 @@
         "modbus-add-server": "Add server/slave",
         "modbus-add-server-prompt": "Please add server/slave",
         "modbus-transport": "Transport",
+        "modbus-tcp-reconnect": "Automatically reconnect",
+        "modbus-rtu-over-tcp": "RTU over TCP",
         "modbus-port-name": "Serial port name",
         "modbus-encoding": "Encoding",
         "modbus-parity": "Parity",
diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json
index 7b02141..2a40c86 100644
--- a/ui/src/app/locale/locale.constant-es_ES.json
+++ b/ui/src/app/locale/locale.constant-es_ES.json
@@ -2,9 +2,9 @@
     "access": {
         "unauthorized": "No autorizado",
         "unauthorized-access": "Acceso no autorizado",
-        "unauthorized-access-text": "Debe registrarse para tener acceso a este recurso!",
+        "unauthorized-access-text": "¡Debe registrarse para tener acceso a este recurso!",
         "access-forbidden": "Acceso Prohibido",
-        "access-forbidden-text": "No tiene derechos para acceder a esta ubicación!<br/>Intente registrarse con otro usuario si aún desea acceder a esta ubicación.",
+        "access-forbidden-text": "No tiene permisos para acceder a esta ubicación!<br/>Intente registrarse con otro usuario si aún desea acceder a esta ubicación.",
         "refresh-token-expired": "La sesión ha expirado",
         "refresh-token-failed": "No se puede actualizar la sesión"
     },
@@ -14,7 +14,7 @@
         "save": "Guardar",
         "saveAs": "Guardar como",
         "cancel": "Cancelar",
-        "ok": "OK",
+        "ok": "Aceptar",
         "delete": "Eliminar",
         "add": "Agregar",
         "yes": "Si",
@@ -30,17 +30,17 @@
         "apply": "Aplicar",
         "apply-changes": "Aplicar cambios",
         "edit-mode": "Modo edición",
-        "enter-edit-mode": "Ingresar modo edición",
+        "enter-edit-mode": "Entrar en modo edición",
         "decline-changes": "Descartar cambios",
         "close": "Cerrar",
         "back": "Atras",
         "run": "Ejecutar",
-        "sign-in": "Registrarse!",
+        "sign-in": "¡Registrarse!",
         "edit": "Editar",
         "view": "Ver",
         "create": "Crear",
         "drag": "Arrastrar",
-        "refresh": "Refrecar",
+        "refresh": "Actualizar",
         "undo": "Deshacer",
         "copy": "Copiar",
         "paste": "Pegar",
@@ -48,7 +48,7 @@
         "paste-reference": "Pegar referencia",
         "import": "Importar",
         "export": "Exportar",
-        "share-via": "Share via {{provider}}"
+        "share-via": "Compartir via {{provider}}"
     },
     "aggregation": {
         "aggregation": "Agregación",
@@ -68,11 +68,11 @@
         "outgoing-mail": "Servidor de correo",
         "outgoing-mail-settings": "Configuración del servidor de correo de salida",
         "system-settings": "Configuración del sistema",
-        "test-mail-sent": "Correo de prueba fue enviado exitosamente!",
+        "test-mail-sent": "¡El correo de prueba fue enviado correctamente!",
         "base-url": "URL base",
         "base-url-required": "URL base es requerida.",
         "mail-from": "Correo desde",
-        "mail-from-required": "Correo Desde es requerido.",
+        "mail-from-required": "Correo desde es requerido.",
         "smtp-protocol": "Protocolo SMTP",
         "smtp-host": "Host SMTP",
         "smtp-host-required": "Host SMTP es requerido.",
@@ -93,11 +93,11 @@
         "alarm-required": "Alarma es requerida",
         "alarm-status": "Estado de la alarma",
         "search-status": {
-            "ANY": "Alguna",
-            "ACTIVE": "Activa",
-            "CLEARED": "Borrada",
-            "ACK": "Reconocida",
-            "UNACK": "Ignorada"
+            "ANY": "Todas",
+            "ACTIVE": "Activas",
+            "CLEARED": "Borradas",
+            "ACK": "Reconocidas",
+            "UNACK": "Ignoradas"
         },
         "display-status": {
             "ACTIVE_UNACK": "Activa ignorada",
@@ -105,10 +105,10 @@
             "CLEARED_UNACK": "Borrada ignorada",
             "CLEARED_ACK": "Borrada reconocida"
         },
-        "no-alarms-prompt": "Alarmas no encontradas",
+        "no-alarms-prompt": "No se encontraron alarmas",
         "created-time": "Tiempo de creación",
         "type": "Tipo",
-        "severity": "Severidad",
+        "severity": "Criticidad",
         "originator": "Origen",
         "originator-type": "Tipo de origen",
         "details": "Detalles",
@@ -119,27 +119,27 @@
         "ack-time": "Tiempo de reconocimiento",
         "clear-time": "Tiempo de borrado",
         "severity-critical": "Crítica",
-        "severity-major": "Mayor",
-        "severity-minor": "Menor",
+        "severity-major": "Alta",
+        "severity-minor": "Baja",
         "severity-warning": "Alerta",
         "severity-indeterminate": "Indeterminada",
         "acknowledge": "Reconocer",
         "clear": "Borrar",
         "search": "buscar alarmas",
-        "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } seleccionadas",
+        "selected-alarms": "{ count, plural, 1 {1 alarma} other {# alarmas} } seleccionadas",
         "no-data": "No hay datos para mostrar",
         "polling-interval": "Intervalo de sondeo de alarmas (seg)",
         "polling-interval-required": "Intervalo de sondeo de alarmas es requerido.",
         "min-polling-interval-message": "Se permite al menos 1 segundo de intervalo de sondeo.",
-        "aknowledge-alarms-title": "Reconocer { count, plural, 1 {1 alarm} other {# alarms} }",
-        "aknowledge-alarms-text": "¿Está seguro de que desea reconocer { count, plural, 1 {1 alarm} other {# alarms} }?",
-        "aknowledge-alarm-title": "Acknowledge Alarm",
-        "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?",
-        "clear-alarms-title": "Borrar { count, plural, 1 {1 alarm} other {# alarms} }",
-        "clear-alarms-text": "¿Está seguro de que desea borrar { count, plural, 1 {1 alarm} other {# alarms} }?",
-        "clear-alarm-title": "Clear Alarm",
-        "clear-alarm-text": "Are you sure you want to clear Alarm?",
-        "alarm-status-filter": "Alarm Status Filter"
+        "aknowledge-alarms-title": "Reconocer { count, plural, 1 {1 alarma} other {# alarmas} }",
+        "aknowledge-alarms-text": "¿Está seguro de que desea reconocer { count, plural, 1 {1 alarma} other {# alarmas} }?",
+        "aknowledge-alarm-title": "Reconocer alarma",
+        "aknowledge-alarm-text": "¿Está seguro que quirere reconocer la alarma?",
+        "clear-alarms-title": "Quitar { count, plural, 1 {1 alarma} other {# alarmas} }",
+        "clear-alarms-text": "¿Está seguro de que desea quitar { count, plural, 1 {1 alarma} other {# alarmas}?",
+        "clear-alarm-title": "Quitar alarma",
+        "clear-alarm-text": "¿Está seguro que quiere quitar la alarma?",
+        "alarm-status-filter": "Filtro de estado de alarma"
     },
     "alias": {
         "add": "Agregar alias",
@@ -151,24 +151,29 @@
         "filter-type-entity-list": "Lista de entidades",
         "filter-type-entity-name": "Nombre de entidad",
         "filter-type-state-entity": "Entidad del panel de estados",
-        "filter-type-state-entity-description": "Entidad tomada desde los parámetro del panel de estados",
+        "filter-type-state-entity-description": "Entidad tomada desde los parámetros del panel de estados",
         "filter-type-asset-type": "Tipo de activo",
         "filter-type-asset-type-description": "Activos de tipo '{{assetType}}'",
-        "filter-type-asset-type-and-name-description": "Activos de tipo '{{assetType}}' y con nombre comenzando con '{{prefix}}'",
+        "filter-type-asset-type-and-name-description": "Activos del tipo '{{assetType}}' y su nombre empieza con '{{prefix}}'",
         "filter-type-device-type": "Tipo de dispositivo",
         "filter-type-device-type-description": "Dispositivos de tipo '{{deviceType}}'",
-        "filter-type-device-type-and-name-description": "Dispositivos de tipo '{{deviceType}}' y con nombre comenzando con '{{prefix}}'",
+        "filter-type-device-type-and-name-description": "Dispositivos del tipo '{{deviceType}}' y  su nombre empieza con '{{prefix}}'",
+        "filter-type-entity-view-type": "Tipo de vista de entidad",
+        "filter-type-entity-view-type-description": "Vista de entidad del tipo '{{entityView}}'",
+        "filter-type-entity-view-type-and-name-description": "Las vista de entidad del tipo '{{entityView}}' y cuyo nombre comienza con '{{prefix}}'",
         "filter-type-relations-query": "Consulta de relaciones",
         "filter-type-relations-query-description": "{{entities}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}",
         "filter-type-asset-search-query": "Consultar búsqueda de activos",
         "filter-type-asset-search-query-description": "Activos con tipos {{assetTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}",
         "filter-type-device-search-query": "Consultar búqueda de dispositivos",
         "filter-type-device-search-query-description": "Dispositivos con tipos {{deviceTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}",
+        "filter-type-entity-view-search-query": "Consultar vista de entidad",
+        "filter-type-entity-view-search-query-description": "Las vista de entidad de tipo {{entityViewTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}",
         "entity-filter": "Filtro de entidad",
         "resolve-multiple": "Resolver como entidades múltiples",
         "filter-type": "Tipo de filtro",
         "filter-type-required": "Tipo de filtro es requerido.",
-        "entity-filter-no-entity-matched": "Entidades que coincidan con el filtro especificado no fueron encontradas.",
+        "entity-filter-no-entity-matched": "No se encontraron entidades que coincidan con el filtro especificado.",
         "no-entity-filter-specified": "No se especificó el filtro de entidad",
         "root-state-entity": "Utilizar la entidad del panel de estados como raíz",
         "root-entity": "Entidad raíz",
@@ -185,13 +190,13 @@
         "asset": "Activo",
         "assets": "Activos",
         "management": "Gestión de activos",
-        "view-assets": "Ver Activos",
-        "add": "Agregar Activo",
+        "view-assets": "Ver activos",
+        "add": "Agregar activo",
         "assign-to-customer": "Asignar al cliente",
-        "assign-asset-to-customer": "Asignar Activo(s) Al Cliente",
-        "assign-asset-to-customer-text": "Por favor seleccionar los activos para asignar al cliente",
-        "no-assets-text": "Activos no encontrados",
-        "assign-to-customer-text": "Por favor seleccionar el cliente para asignar el(los) activo(s)",
+        "assign-asset-to-customer": "Asignar activo(s) al cliente",
+        "assign-asset-to-customer-text": "Por favor, seleccione los activos para asignar al cliente",
+        "no-assets-text": "No se encontraron activos",
+        "assign-to-customer-text": "Por favor, seleccione el cliente para asignar el(los) activo(s)",
         "public": "Público",
         "assignedToCustomer": "Asignado al cliente",
         "make-public": "Hacer público el activo",
@@ -202,9 +207,9 @@
         "asset-type": "Tipo de activo",
         "asset-type-required": "El tipo de activo es requerido.",
         "select-asset-type": "Seleccionar tipo de activo",
-        "enter-asset-type": "Ingresar tipo de activo",
+        "enter-asset-type": "Introduzca el tipo de activo",
         "any-asset": "Algún activo",
-        "no-asset-types-matching": "Tipos de activos que coincidan con '{{entitySubtype}}' no fueron encontrados.",
+        "no-asset-types-matching": "No se encontraron tipos de activos que coincidan con '{{entitySubtype}}'.",
         "asset-type-list-empty": "No se seleccionaron tipos de activos.",
         "asset-types": "Tipos de activos",
         "name": "Nombre",
@@ -217,16 +222,16 @@
         "add-asset-text": "Agregar nuevos activos",
         "asset-details": "Detalles del activo",
         "assign-assets": "Asignar activos",
-        "assign-assets-text": "Asignar { count, plural, 1 {1 asset} other {# assets} } al cliente",
+        "assign-assets-text": "Asignar { count, plural, 1 {1 activo} other {# activos} } al cliente",
         "delete-assets": "Eliminar activos",
         "unassign-assets": "Anular asignación de activos",
-        "unassign-assets-action-title": "Anular asignación { count, plural, 1 {1 asset} other {# assets} } del cliente",
+        "unassign-assets-action-title": "Anular asignación { count, plural, 1 {1 activo} other {# activos} } del cliente",
         "assign-new-asset": "Asignar nuevo activo",
         "delete-asset-title": "¿Está seguro de que desea eliminar el activo '{{assetName}}'?",
-        "delete-asset-text": "Tener cuidado, después de la confirmación, el activo y todos los datos relacionados se volverán irrecuperables.",
-        "delete-assets-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 asset} other {# assets} }?",
-        "delete-assets-action-title": "Eliminar { count, plural, 1 {1 asset} other {# assets} }",
-        "delete-assets-text": "Tener cuidado, después de la confirmación se eliminarán todos los activos seleccionados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-asset-text": "¡Cuidado! Después de la confirmación, el activo y todos los datos relacionados serán irrecuperables.",
+        "delete-assets-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 activos} other {# activos} }?",
+        "delete-assets-action-title": "Eliminar { count, plural, 1 {1 activo} other {# activos} }",
+        "delete-assets-text": "¡Cuidado! Después de la confirmación se eliminarán todos los activos seleccionados y todos los datos relacionados serán irrecuperables.",
         "make-public-asset-title": "¿Está seguro de que desea que el activo '{{assetName}}' sea público?",
         "make-public-asset-text": "Después de la confirmación, el activo y todos sus datos se harán públicos y accesibles por otros.",
         "make-private-asset-title": "¿Está seguro de que desea que el activo '{{assetName}}' sea privado?",
@@ -234,12 +239,12 @@
         "unassign-asset-title": "¿Está seguro de que desea anular asignación del activo '{{assetName}}'?",
         "unassign-asset-text": "Después de la confirmación, se anulará asignación del activo y no será accesible por el cliente.",
         "unassign-asset": "Anular asignación activo",
-        "unassign-assets-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 asset} other {# assets} }?",
+        "unassign-assets-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 activo} other {# activos} }?",
         "unassign-assets-text": "Después de la confirmación, se anulará asignación de todos los activos seleccionados y no serán accesibles por el cliente",
         "copyId": "Copiar ID del activo",
         "idCopiedMessage": "ID del activo has sido copiada al portapapeles",
         "select-asset": "Seleccionar activo",
-        "no-assets-matching": "Activos que coincidan con '{{entity}}' no fueron encontrados.",
+        "no-assets-matching": "No se encontraron activos que coincidan con '{{entity}}'.",
         "asset-required": "El activo es requerido",
         "name-starts-with": "El nombre del activo comienza con"
     },
@@ -257,25 +262,25 @@
         "key-required": "La clave del aributo es requerida.",
         "value": "Valor",
         "value-required": "Valor del atributo es requerido.",
-        "delete-attributes-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 attribute} other {# attributes} }?",
-        "delete-attributes-text": "Tener cuidado, después de la confirmación, se eliminarán todos los atributos seleccionados.",
+        "delete-attributes-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 atributo} other {# atributos} }?",
+        "delete-attributes-text": "¡Cuidado! Después de la confirmación, se eliminarán todos los atributos seleccionados.",
         "delete-attributes": "Eliminar atributos",
-        "enter-attribute-value": "Ingresar valor del atributo",
+        "enter-attribute-value": "Introduzca el valor del atributo",
         "show-on-widget": "Mostrar en widget",
         "widget-mode": "Modo widget",
         "next-widget": "Widget siguiente",
         "prev-widget": "Widget previo",
         "add-to-dashboard": "Agregar al panel",
         "add-widget-to-dashboard": "Agregar widget al panel",
-        "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } seleccionados",
+        "selected-attributes": "{ count, plural, 1 {1 atributo} other {# atributos} } seleccionados",
         "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } seleccionadas"
     },
     "audit-log": {
-        "audit": "Auditar",
-        "audit-logs": "Auditar Registros",
+        "audit": "Auditoría",
+        "audit-logs": "Registros de auditoría",
         "timestamp": "Marca de tiempo",
-        "entity-type": "Tipo de Entidad",
-        "entity-name": "Nombre de Entidad",
+        "entity-type": "Tipo de entidad",
+        "entity-name": "Nombre de entidad",
         "user": "Usuario",
         "type": "Tipo",
         "status": "Estado",
@@ -287,7 +292,7 @@
         "type-attributes-deleted": "Atributos eliminados",
         "type-rpc-call": "Llamada RPC",
         "type-credentials-updated": "Credenciales actualizadas",
-        "type-assigned-to-customer": "Asignado al Cliente",
+        "type-assigned-to-customer": "Asignado al cliente",
         "type-unassigned-from-customer": "Asignación anulada del cliente",
         "type-activated": "Activado",
         "type-suspended": "Suspendido",
@@ -298,18 +303,18 @@
         "type-relations-delete": "Toda relación eliminada",
         "type-alarm-ack": "Reconocida",
         "type-alarm-clear": "Borrada",
-        "status-success": "Éxito",
-        "status-failure": "Falla",
-        "audit-log-details": "Auditar detalles de regisstro",
-        "no-audit-logs-prompt": "Registros no encontrados",
+        "status-success": "Correcto",
+        "status-failure": "Erróneo",
+        "audit-log-details": "Detalle del registro de auditoría",
+        "no-audit-logs-prompt": "No se encontraron registros",
         "action-data": "Datos de acción",
-        "failure-details": "Detalles de falla",
+        "failure-details": "Detalles del error",
         "search": "Buscar registros de auditoría",
         "clear-search": "Borrar búsqueda"
     },
     "confirm-on-exit": {
         "message": "Tiene cambios sin guardar. ¿Está seguro de que desea salir de esta página?",
-        "html-message": "Tiene cambios sin guardar..<br/>¿Está seguro de que desea salir de esta página?",
+        "html-message": "Tiene cambios sin guardar.<br/>¿Está seguro de que desea salir de esta página?",
         "title": "Cambios sin guardar"
     },
     "contact": {
@@ -327,9 +332,9 @@
     "common": {
         "username": "Nombre de usuario",
         "password": "Contraseña",
-        "enter-username": "Ingresar nombre de usuario",
-        "enter-password": "Ingresar contraseña",
-        "enter-search": "Ingresar búsqueda"
+        "enter-username": "Introduzca nombre de usuario",
+        "enter-password": "Introduzca contraseña",
+        "enter-search": "Introduzca búsqueda"
     },
     "content-type": {
         "json": "Json",
@@ -340,15 +345,15 @@
         "customer": "Cliente",
         "customers": "Clientes",
         "management": "Gestión del cliente",
-        "dashboard": "Panel del Cliente",
-        "dashboards": "Paneles del Cliente",
-        "devices": "Dispositivos del Cliente",
-        "entity-views": "Customer Entity Views",
+        "dashboard": "Panel del cliente",
+        "dashboards": "Paneles del cliente",
+        "devices": "Dispositivos del cliente",
+        "entity-views": "Vistas de entidad del cliente",
         "assets": "Activos del Cliente",
-        "public-dashboards": "Paneles Públicos",
-        "public-devices": "Dispositivos Públicos",
-        "public-assets": "Activos Públicos",
-        "public-entity-views": "Public Entity Views",
+        "public-dashboards": "Paneles públicos",
+        "public-devices": "Dispositivos públicos",
+        "public-assets": "Activos públicos",
+        "public-entity-views": "Vista de entidad públicas",
         "add": "Agregar cliente",
         "delete": "Eliminar cliente",
         "manage-customer-users": "Gestionar usuarios del cliente",
@@ -359,13 +364,13 @@
         "manage-customer-assets": "Gestionar activos del cliente",
         "manage-public-assets": "Gestionar activos públicos",
         "add-customer-text": "Agregar nuevo cliente",
-        "no-customers-text": "Clientes no encontrados",
+        "no-customers-text": "No se encontraron clientes",
         "customer-details": "Detalles del cliente",
         "delete-customer-title": "¿Está seguro de que desea eliminar al cliente '{{customerTitle}}'?",
-        "delete-customer-text": "Tener cuidado, después de la confirmación, el cliente y todos los datos relacionados se volverán irrecuperables.",
-        "delete-customers-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 customer} other {# customers} }?",
-        "delete-customers-action-title": "Eliminar { count, plural, 1 {1 customer} other {# customers} }",
-        "delete-customers-text": "Tener cuidado, después de la confirmación, todos los clientes seleccionados serán eliminados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-customer-text": "¡Cuidado! Después de la confirmación, el cliente y todos los datos relacionados serán irrecuperables.",
+        "delete-customers-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 cliente} other {# clientes} }?",
+        "delete-customers-action-title": "Eliminar { count, plural, 1 {1 cliente} other {# clientes} }",
+        "delete-customers-text": "¡Cuidado! Después de la confirmación, todos los clientes seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.",
         "manage-users": "Gestionar usuarios",
         "manage-assets": "Gestionar activos",
         "manage-devices": "Gestionar dispositivos",
@@ -378,7 +383,7 @@
         "copyId": "Copiar ID del cliente",
         "idCopiedMessage": "ID del cliente ha sido copiada al portapapeles",
         "select-customer": "Seleccionar cliente",
-        "no-customers-matching": "Clientes que coincidan con '{{entity}}' no fueron encontrados.",
+        "no-customers-matching": "No se encontraron clientes que coincidan con '{{entity}}'.",
         "customer-required": "El cliente es requerido",
         "select-default-customer": "Seleccionar cliente predeterminado",
         "default-customer": "Cliente predeterminado",
@@ -394,22 +399,22 @@
         "dashboard": "Panel",
         "dashboards": "Paneles",
         "management": "Gestión del panel",
-        "view-dashboards": "Ver Panel",
-        "add": "Agregar Paneles",
-        "assign-dashboard-to-customer": "Asignar Panel(es) Al Cliente",
-        "assign-dashboard-to-customer-text": "Por favor seleccionar los paneles para asignar al cliente",
-        "assign-to-customer-text": "Por favor seleccionar el cliente para asignar el(los) panel(es)",
+        "view-dashboards": "Ver panel",
+        "add": "Agregar paneles",
+        "assign-dashboard-to-customer": "Asignar panel(es) al cliente",
+        "assign-dashboard-to-customer-text": "Por favor selecciona los paneles para asignar al cliente",
+        "assign-to-customer-text": "Por favor selecciona el cliente para asignar el(los) panel(es)",
         "assign-to-customer": "Asignar al cliente",
         "unassign-from-customer": "Anular asignación del cliente",
         "make-public": "Hacer panel público",
-        "make-private": "Hcer panel privado",
+        "make-private": "Hacer panel privado",
         "manage-assigned-customers": "Gestionar clientes asignados",
         "assigned-customers": "Clientes asignados",
-        "assign-to-customers": "Asignar Panel(es) Al(Los) Cliente(s)",
+        "assign-to-customers": "Asignar panel(es) al(los) cliente(s)",
         "assign-to-customers-text": "Por favor seleccionar los clientes para asignar el(los) panel(es)",
-        "unassign-from-customers": "Anular Asignación Del(De Los) Panel(es) De Los Clientes",
-        "unassign-from-customers-text": "Por favor seeccionar los clientes oara anular asignación del(de los) panel(es)",
-        "no-dashboards-text": "Paneles no encontrados",
+        "unassign-from-customers": "Anular asignación del(de los) panel(es) de los clientes",
+        "unassign-from-customers-text": "Por favor selecciona los clientes para anular asignación del(de los) panel(es)",
+        "no-dashboards-text": "No se encontraron paneles",
         "no-widgets": "Sin widgets configurados",
         "add-widget": "Agregar nuevo widget",
         "title": "Título",
@@ -419,7 +424,7 @@
         "title-required": "El título es requerido.",
         "description": "Descripción",
         "details": "Detalles",
-        "dashboard-details": "Detalles del Panel",
+        "dashboard-details": "Detalles del panel",
         "add-dashboard-text": "Agregar nuevo panel",
         "assign-dashboards": "Asignar paneles",
         "assign-new-dashboard": "Aignar nuevo panel",
@@ -429,10 +434,10 @@
         "unassign-dashboards": "Anular asignación de paneles",
         "unassign-dashboards-action-title": "Anular asignación { count, plural, 1 {1 dashboard} other {# dashboards} } del cliente",
         "delete-dashboard-title": "¿Está seguro de que desea eliminar el panel '{{dashboardTitle}}'?",
-        "delete-dashboard-text": "Tener cuidado, después de la confirmación, el panel y todos los datos relacionados se volverán irrecuperables.",
+        "delete-dashboard-text": "¡Cuidado! Después de la confirmación, el panel y todos los datos relacionados serán irrecuperables.",
         "delete-dashboards-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 dashboard} other {# dashboards} }?",
         "delete-dashboards-action-title": "Eliminar { count, plural, 1 {1 dashboard} other {# dashboards} }",
-        "delete-dashboards-text": "Tener cuidado, después de la confirmación, todos los paneles seleccionados serán eliminados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-dashboards-text": "¡Cuidado! Después de la confirmación, todos los paneles seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.",
         "unassign-dashboard-title": "¿Está seguro de que desea anular la asignación del panel '{{dashboardTitle}}'?",
         "unassign-dashboard-text": "Después de la confirmación, se anulará la asignación del panel y no será accesible por el cliente.",
         "unassign-dashboard": "Anular asignación del panel",
@@ -460,25 +465,25 @@
         "no-image": "Ninguna imagen seleccionada",
         "drop-image": "Colocar una imagen o hacer clic para seleccionar un archivo para cargar.",
         "settings": "Configuración",
-        "columns-count": "Conteo de columnas",
-        "columns-count-required": "Conteo de columnas es requerido.",
-        "min-columns-count-message": "Solamente contar 10 columnas como mínimo es permitido",
-        "max-columns-count-message": "Solamente contar 1000 columnas como máximo es permitiddo",
+        "columns-count": "Número de columnas",
+        "columns-count-required": "Número de columnas es requerido.",
+        "min-columns-count-message": "Sólo está permitido 10 columnas como mínimo.",
+        "max-columns-count-message": "Sólo está permitido 1000 columnas como máximo.",
         "widgets-margins": "Margen entre widgets",
         "horizontal-margin": "Margen horizontal",
         "horizontal-margin-required": "El valor del margen horizontal es requerido.",
-        "min-horizontal-margin-message": "Solamente es permitido el 0 como valor mínimo para el margen horizontal",
-        "max-horizontal-margin-message": "Solamente es permitido el 50 como valor máximo para el margen horizontal",
+        "min-horizontal-margin-message": "Sólo está permitido el 0 como valor mínimo para el margen horizontal",
+        "max-horizontal-margin-message": "Sólo está permitido el 50 como valor máximo para el margen horizontal",
         "vertical-margin": "Margen vertical",
         "vertical-margin-required": "El valor del margen vertical es requerido.",
-        "min-vertical-margin-message": "Solamente es permitido el 0 como valor mínimo para el margen vertical.",
-        "max-vertical-margin-message": "Solamente es permitido el 50 como valor máximo para el margen vertical",
+        "min-vertical-margin-message": "Sólo está permitido el 0 como valor mínimo para el margen vertical.",
+        "max-vertical-margin-message": "Sólo está permitido el 50 como valor máximo para el margen vertical",
         "autofill-height": "Llenado automático de altura de diseño",
         "mobile-layout": "Configuración de diseño para móvil",
         "mobile-row-height": "Altura de fila para móvil, píxel",
         "mobile-row-height-required": "Altura de fila para móvil es requerida.",
-        "min-mobile-row-height-message": "Solamente es permitido 5 píxeles como valor mínimo de altura de fila para móvil.",
-        "max-mobile-row-height-message": "Solamente es permitido 200 píxeles como valor máximo de altura de fila para móvil.",
+        "min-mobile-row-height-message": "Sólo está permitido 5 píxeles como valor mínimo en la altura de fila para móvil.",
+        "max-mobile-row-height-message": "Sólo está permitido 200 píxeles como valor máximo en la altura de fila para móvil.",
         "display-title": "Mostrar título del panel",
         "toolbar-always-open": "Mantener la barra de herramientas abierta",
         "title-color": "Color del título",
@@ -550,7 +555,12 @@
         "alarm-fields-required": "Campos de alarma son requeridos.",
         "function-types": "Tipos de funciones",
         "function-types-required": "Tipos de funciones son requeridos.",
-        "maximum-function-types": "Máximo { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }"
+        "maximum-function-types": "Máximo { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }",
+        "time-description": "timestamp of the current value;",
+        "value-description": "the current value;",
+        "prev-value-description": "result of the previous function call;",
+        "time-prev-description": "timestamp of the previous value;",
+        "prev-orig-value-description": "original previous value;"
     },
     "datasource": {
         "type": "Tipo de fuente de datos",
@@ -558,23 +568,23 @@
         "add-datasource-prompt": "Por favor agregue fuente de datos"
     },
     "details": {
-        "details": "Detalles",
-        "edit-mode": "Modo de edición"
+        "edit-mode": "Modo de edición",
+        "toggle-edit-mode": "Cambiar mode de edición"
     },
     "device": {
         "device": "Dispositivo",
         "device-required": "Dispositivo es requerido.",
         "devices": "Dispositivos",
         "management": "Gestión del dispositivo",
-        "view-devices": "Ver Dispositivos",
+        "view-devices": "Ver dispositivos",
         "device-alias": "Alias del dispositivo",
         "aliases": "Alias de los dispositivos",
         "no-alias-matching": "'{{alias}}' no encontrado.",
-        "no-aliases-found": "Alias no encontrados.",
+        "no-aliases-found": "No se encontraron alias.",
         "no-key-matching": "'{{key}}' no encontrado.",
         "no-keys-found": "Claves no encontradas.",
-        "create-new-alias": "Crear uno nuevo!",
-        "create-new-key": "Crear una nueva!",
+        "create-new-alias": "¡Crear uno nuevo!",
+        "create-new-key": "¡Crear una nueva!",
         "duplicate-alias-error": "Alias duplicado encontrado '{{alias}}'.<br>Los alias del dispositivo deben ser únicos dentro del panel.",
         "configure-alias": "Configurar '{{alias}}' alias",
         "no-devices-matching": "Dispositivos que coincidan con '{{entity}}' no fueron encontrados.",
@@ -591,10 +601,10 @@
         "add": "Agregar Dispositivo",
         "assign-to-customer": "Asignar al cliente",
         "assign-device-to-customer": "Asignar Dispositivo(s) Al Cliente",
-        "assign-device-to-customer-text": "Por favor seleccionar los dispositivos para asignar al cliente",
+        "assign-device-to-customer-text": "Por favor selecciona los dispositivos para asignar al cliente",
         "make-public": "Hacer público el dispositivo",
         "make-private": "Hacer privado el dispositivo",
-        "no-devices-text": "Dispositivos no encontrados",
+        "no-devices-text": "No se encontraron dispositivos",
         "assign-to-customer-text": "Por favor seleccionar el cliente para asignar el(los) dispositivo(s)",
         "device-details": "Detalles del dispositivo",
         "add-device-text": "Agregar nuevo dispositivo",
@@ -602,11 +612,11 @@
         "manage-credentials": "Gestionar credenciales",
         "delete": "Eliminar dispositivo",
         "assign-devices": "Asignar dispositivos",
-        "assign-devices-text": "Asignar { count, plural, 1 {1 device} other {# devices} } al cliente",
+        "assign-devices-text": "Asignar { count, plural, 1 {1 dispositivo} other {# dispositivos} } al cliente",
         "delete-devices": "Eliminar dispositivos",
         "unassign-from-customer": "Anular asignación del cliente",
         "unassign-devices": "Anular asignación de dispositivos",
-        "unassign-devices-action-title": "Anular asignación { count, plural, 1 {1 device} other {# devices} } del cliente",
+        "unassign-devices-action-title": "Anular asignación { count, plural, 1 {1 dispositivo} other {# dispositivos} } del cliente",
         "assign-new-device": "Asignar nuevo dispositivo",
         "make-public-device-title": "¿Está seguro de que desea hacer el dispositivo '{{deviceName}}' público?",
         "make-public-device-text": "Después de la confirmación, el dispositivo y todos sus datos se harán públicos y accesibles por otros.",
@@ -614,14 +624,14 @@
         "make-private-device-text": "Después de la confirmación, el dispositivo y todos sus datos se harán privados y no serán accesibles para otros.",
         "view-credentials": "Ver credenciales",
         "delete-device-title": "¿Está seguro de que desea hacer el dispositivo '{{deviceName}}'?",
-        "delete-device-text": "Tener cuidado, después de la confirmación, el dispositivo y todos sus datos relacionados se volverán irrecuperables.",
-        "delete-devices-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 device} other {# devices} }?",
-        "delete-devices-action-title": "Eliminar { count, plural, 1 {1 device} other {# devices} }",
-        "delete-devices-text": "Tener cuidado, después de la confirmación, todos los dispositivos seleccionados serán eliminados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-device-text": "¡Cuidado! Después de la confirmación, el dispositivo y todos sus datos relacionados serán irrecuperables.",
+        "delete-devices-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?",
+        "delete-devices-action-title": "Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }",
+        "delete-devices-text": "¡Cuidado! Después de la confirmación, todos los dispositivos seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.",
         "unassign-device-title": "¿Está seguro de que desea anular la asignación del dispositivo '{{deviceName}}'?",
         "unassign-device-text": "Después de la confirmación, se anulará asignación del dispositivo y no será accesible por el cliente.",
         "unassign-device": "Anular asignación del dispositivo",
-        "unassign-devices-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 device} other {# devices} }?",
+        "unassign-devices-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 dispositivo} other {# dispositivos} }?",
         "unassign-devices-text": "Después de la confirmación, se anulará asignación de todos los dispositivos seleccionados y no serán accesibles por el cliente.",
         "device-credentials": "Credenciales del dispositivo",
         "credentials-type": "Tipo de credenciales",
@@ -635,7 +645,7 @@
         "device-type": "Tipo de dispositivo",
         "device-type-required": "Tipo de dispositivo es requerido.",
         "select-device-type": "Seleccionar tipo de dispositivo",
-        "enter-device-type": "Ingresar tipo de dispositivo",
+        "enter-device-type": "Teclee tipo de dispositivo",
         "any-device": "Algún dispositivo",
         "no-device-types-matching": "Tipos de dispositivos que coincidan con '{{entitySubtype}}' no fueron encontrados.",
         "device-type-list-empty": "No se seleccionaron tipos de dispositivos.",
@@ -661,7 +671,7 @@
         "close": "Cerrar diálogo"
     },
     "error": {
-        "unable-to-connect": "No se puede conectar al servidor! Por favor revise su conexión a Internet.",
+        "unable-to-connect": "¡No se puede conectar al servidor! Por favor, revise su conexión a Internet.",
         "unhandled-error-code": "Código de error no controlado: {{errorCode}}",
         "unknown-error": "Error desconocido"
     },
@@ -671,7 +681,7 @@
         "aliases": "Alias de las entidades",
         "entity-alias": "Alias de la entidad",
         "unable-delete-entity-alias-title": "No se puede borrar alias de la entidad",
-        "unable-delete-entity-alias-text": "Alias de la entidad '{{entityAlias}}' no piuede ser eliminado porque es usado por los siguientes widget(s):<br/>{{widgetsList}}",
+        "unable-delete-entity-alias-text": "Alias de la entidad '{{entityAlias}}' no se puede eliminar porque es usado por los siguientes widget(s):<br/>{{widgetsList}}",
         "duplicate-alias-error": "Alias duplicado fue encontrado '{{alias}}'.<br>Alias de las entidades deben ser únicos dentro del panel.",
         "missing-entity-filter-error": "Falta filtro para el alias '{{alias}}'.",
         "configure-alias": "Configurar '{{alias}}' alias",
@@ -684,58 +694,58 @@
         "entity-types": "Tipos de entidades",
         "entity-type-list": "Lista de tipos de entidades",
         "any-entity": "Alguna entidad",
-        "enter-entity-type": "Ingresara tipo de entidad",
-        "no-entities-matching": "Entidades que coincidan con '{{entity}}' no fueron encontradas.",
-        "no-entity-types-matching": "Tipos de entidades que coincidan con '{{entityType}}' no fueron encontrados.",
+        "enter-entity-type": "Teclee tipo de entidad",
+        "no-entities-matching": "No se encontraron entidades que coincidan con '{{entity}}'.",
+        "no-entity-types-matching": "No se encontraron tipos de entidades que coincidan con '{{entityType}}'.",
         "name-starts-with": "El nombre comienza con",
         "use-entity-name-filter": "Utilizar filtro",
         "entity-list-empty": "Entidades no seleccionadas.",
         "entity-type-list-empty": "Tipos de entidades no seleccionados.",
         "entity-name-filter-required": "Filtro del nombre de la entidad es requerido.",
-        "entity-name-filter-no-entity-matched": "Entidades que comienzan con  '{{entity}}' no fueron encontradas.",
+        "entity-name-filter-no-entity-matched": "No se encontraron entidades que comienzan con  '{{entity}}'.",
         "all-subtypes": "Todas",
         "select-entities": "Seleccionar entidades",
-        "no-aliases-found": "Alias no encontrados.",
+        "no-aliases-found": "No se encontraron alias.",
         "no-alias-matching": "'{{alias}}' no encontrado.",
-        "create-new-alias": "Crear uno nuevo!",
+        "create-new-alias": "¡Crear uno nuevo!",
         "key": "Clave",
         "key-name": "Nombre de clave",
-        "no-keys-found": "Claves no encontradas.",
+        "no-keys-found": "No se encontraron claves.",
         "no-key-matching": "'{{key}}' no encontrada.",
-        "create-new-key": "Crear una nueva!",
+        "create-new-key": "¡Crear una nueva!",
         "type": "Tipo",
         "type-required": "Tipo de entidad es requerido.",
         "type-device": "Dispositivo",
         "type-devices": "Dispositivos",
-        "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }",
+        "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista de # dispositivos} }",
         "device-name-starts-with": "Dispositivos cuyos nombres comienzan con '{{prefix}}'",
         "type-asset": "Activo",
         "type-assets": "Activos",
-        "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }",
+        "list-of-assets": "{ count, plural, 1 {Un activo} other {Lista de # activos} }",
         "asset-name-starts-with": "Activos cuyos nombres comienzan con '{{prefix}}'",
-        "type-entity-view": "Entity View",
-        "type-entity-views": "Entity Views",
-        "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }",
-        "entity-view-name-starts-with": "Entity Views whose names start with '{{prefix}}'",
+        "type-entity-view": "Vista de entidad",
+        "type-entity-views": "Vistas de entidad",
+        "list-of-entity-views": "{ count, plural, 1 {Una vista de entidad} other {Lista de # vistas de entidad} }",
+        "entity-view-name-starts-with": "Vistas de entidad cuyos nombres que comienzan con '{{prefix}}'",
         "type-rule": "Regla",
         "type-rules": "Reglas",
-        "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }",
+        "list-of-rules": "{ count, plural, 1 {Una regla} other {Lista de # reglas} }",
         "rule-name-starts-with": "Reglas cuyos nombres comienzan con '{{prefix}}'",
         "type-plugin": "Complemento",
         "type-plugins": "Complementos",
-        "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }",
+        "list-of-plugins": "{ count, plural, 1 {Un complemento} other {Lista de # complementos} }",
         "plugin-name-starts-with": "Complementos cuyos nombres comienzan con '{{prefix}}'",
         "type-tenant": "Organización",
         "type-tenants": "Organizaciones",
-        "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }",
+        "list-of-tenants": "{ count, plural, 1 {Una organización} other {Lista de # organizaciones} }",
         "tenant-name-starts-with": "Organizaciones cuyos nombres comienzan con '{{prefix}}'",
         "type-customer": "Cliente",
         "type-customers": "Clientes",
-        "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }",
+        "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista de # clientes} }",
         "customer-name-starts-with": "Clientes cuyos nombres comienzan con '{{prefix}}'",
         "type-user": "Usuario",
         "type-users": "Usuarios",
-        "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }",
+        "list-of-users": "{ count, plural, 1 {Un usuario} other {Lista de # usuarios} }",
         "user-name-starts-with": "Usuarios cuyos nombres comienzan con '{{prefix}}'",
         "type-dashboard": "Panel",
         "type-dashboards": "Paneles",
@@ -743,103 +753,115 @@
         "dashboard-name-starts-with": "Paneles cuyos nombres comienzan con '{{prefix}}'",
         "type-alarm": "Alarma",
         "type-alarms": "Alarmas",
-        "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }",
+        "list-of-alarms": "{ count, plural, 1 {Una alarma} other {Lista de # alarmas} }",
         "alarm-name-starts-with": "Alarmas cuyos nombres comienzan con '{{prefix}}'",
         "type-rulechain": "Cadena de reglas",
         "type-rulechains": "Cadenas de reglas",
-        "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }",
-        "rulechain-name-starts-with": "Cadenas reglas cuyos nombres comienzan con '{{prefix}}'",
+        "list-of-rulechains": "{ count, plural, 1 {Una cadena de reglas} other {Lista de # cadenas de reglas} }",
+        "rulechain-name-starts-with": "Cadenas de reglas cuyos nombres comienzan con '{{prefix}}'",
         "type-rulenode": "Nodo de reglas",
         "type-rulenodes": "Nodos de reglas",
-        "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
+        "list-of-rulenodes": "{ count, plural, 1 {Un nodo de reglas} other {Lista de # nodos de reglas} }",
         "rulenode-name-starts-with": "Nodos de reglas cuyos nombres comienzan con '{{prefix}}'",
         "type-current-customer": "Cliente Actual",
         "search": "Buscar entidades",
-        "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } seleccionadas",
+        "selected-entities": "{ count, plural, 1 {1 entidad} other {# entidades} } seleccionadas",
         "entity-name": "Nombre de la entidad",
         "details": "Detalles de la entidad",
         "no-entities-prompt": "Entidades no encontradas",
         "no-data": "No hay datos para mostrar",
-        "columns-to-display": "Columns to Display"
+        "columns-to-display": "Columnas a mostrar"
     },
     "entity-view": {
-        "entity-view": "Entity View",
-        "entity-views": "Entity Views",
-        "management": "Entity View management",
-        "view-entity-views": "View Entity Views",
-        "entity-view-alias": "Entity View alias",
-        "aliases": "Entity View aliases",
-        "no-alias-matching": "'{{alias}}' not found.",
-        "no-aliases-found": "No aliases found.",
-        "no-key-matching": "'{{key}}' not found.",
-        "no-keys-found": "No keys found.",
-        "create-new-alias": "Create a new one!",
-        "create-new-key": "Create a new one!",
-        "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity View aliases must be unique whithin the dashboard.",
-        "configure-alias": "Configure '{{alias}}' alias",
-        "no-entity-views-matching": "No entity views matching '{{entity}}' were found.",
+        "entity-view": "Vista de entidad",
+        "entity-view-required": "Vista de entidad es requerido.",
+        "entity-views": "Vistas de entidad",
+        "management": "Gestión de vistas de entidad",
+        "view-entity-views": "Ver vista de entidad",
+        "entity-view-alias": "Alias de vista de entidad",
+        "aliases": "Alias de vista de entidad",
+        "no-alias-matching": "'{{alias}}' no encontrado.",
+        "no-aliases-found": "No se encontraron alias.",
+        "no-key-matching": "'{{key}}' no encontrado.",
+        "no-keys-found": "No se encontraron claves.",
+        "create-new-alias": "¡Crear un nuevo!",
+        "create-new-key": "¡Crear una nueva!",
+        "duplicate-alias-error": "Alias duplicado'{{alias}}'.<br>El alias de la vista de entidad debe ser único en el dashboard.",
+        "configure-alias": "Configurar alias '{{alias}}'",
+        "no-entity-views-matching": "No se encontraron vistas que coincidan con '{{entity}}'.",
         "alias": "Alias",
-        "alias-required": "Entity View alias is required.",
-        "remove-alias": "Remove entity view alias",
-        "add-alias": "Add entity view alias",
-        "name-starts-with": "Entity View name starts with",
-        "entity-view-list": "Entity View list",
-        "use-entity-view-name-filter": "Use filter",
-        "entity-view-list-empty": "No entity views selected.",
-        "entity-view-name-filter-required": "Entity view name filter is required.",
-        "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.",
-        "add": "Add Entity View",
-        "assign-to-customer": "Assign to customer",
-        "assign-entity-view-to-customer": "Assign Entity View(s) To Customer",
-        "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer",
-        "no-entity-views-text": "No entity views found",
-        "assign-to-customer-text": "Please select the customer to assign the entity view(s)",
-        "entity-view-details": "Entity view details",
-        "add-entity-view-text": "Add new entity view",
-        "delete": "Delete entity view",
-        "assign-entity-views": "Assign entity views",
-        "assign-entity-views-text": "Assign { count, plural, 1 {1 entityView} other {# entityViews} } to customer",
-        "delete-entity-views": "Delete entity views",
-        "unassign-from-customer": "Unassign from customer",
-        "unassign-entity-views": "Unassign entity views",
-        "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entityView} other {# entityViews} } from customer",
-        "assign-new-entity-view": "Assign new entity view",
-        "delete-entity-view-title": "Are you sure you want to delete the entity view '{{entityViewName}}'?",
-        "delete-entity-view-text": "Be careful, after the confirmation the entity view and all related data will become unrecoverable.",
-        "delete-entity-views-title": "Are you sure you want to entity view { count, plural, 1 {1 entityView} other {# entityViews} }?",
-        "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entityView} other {# entityViews} }",
-        "delete-entity-views-text": "Be careful, after the confirmation all selected entity views will be removed and all related data will become unrecoverable.",
-        "unassign-entity-view-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?",
-        "unassign-entity-view-text": "After the confirmation the entity view will be unassigned and won't be accessible by the customer.",
-        "unassign-entity-view": "Unassign entity view",
-        "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entityView} other {# entityViews} }?",
-        "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.",
-        "entity-view-type": "Entity View type",
-        "entity-view-type-required": "Entity View type is required.",
-        "select-entity-view-type": "Select entity view type",
-        "enter-entity-view-type": "Enter entity view type",
-        "any-entity-view": "Any entity view",
-        "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.",
-        "entity-view-type-list-empty": "No entity view types selected.",
-        "entity-view-types": "Entity View types",
-        "name": "Name",
-        "name-required": "Name is required.",
-        "description": "Description",
-        "events": "Events",
-        "details": "Details",
-        "copyId": "Copy entity view Id",
-        "assignedToCustomer": "Assigned to customer",
-        "unable-entity-view-device-alias-title": "Unable to delete entity view alias",
-        "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
-        "select-entity-view": "Select entity view",
-        "make-public": "Make entity view public",
-        "start-ts": "Start time",
-        "end-ts": "End time",
-        "date-limits": "Date limits",
-        "client-attributes": "Client attributes",
-        "shared-attributes": "Shared attributes",
-        "server-attributes": "Server attributes",
-        "timeseries": "Timeseries"
+        "alias-required": "Alias de vista de entidad es requerido.",
+        "remove-alias": "Borrar alias de la vista de entidad",
+        "add-alias": "Añadir alias a la vista de entidad",
+        "name-starts-with": "Nombre de vista de entidad comienza con",
+        "entity-view-list": "Lista de vistas de entidad",
+        "use-entity-view-name-filter": "Usar el filtro",
+        "entity-view-list-empty": "No hay vistas de entidad seleccionadas.",
+        "entity-view-name-filter-required": "Nombre del filtro de vista de entidad es requerido.",
+        "entity-view-name-filter-no-entity-view-matched": "No se econtraron vistas de entidad que comiencen con '{{entityView}}'.",
+        "add": "Añadir vista de entidad",
+        "assign-to-customer": "Asignar a cliente",
+        "assign-entity-view-to-customer": "Asignar vista de entidad a cliente",
+        "assign-entity-view-to-customer-text": "Por favor, seleccione las vistas de entidad para asignar al cliente",
+        "no-entity-views-text": "No se encontraron vistas de entidad",
+        "assign-to-customer-text": "Por favor, seleccione el cliente para asignar la vista de entidad",
+        "entity-view-details": "Detalles de la vista de entidad",
+        "add-entity-view-text": "Añadir nueva vista de entidad",
+        "delete": "Borrar vista de entidad",
+        "assign-entity-views": "Asignar vistas de entidad",
+        "assign-entity-views-text": "Asignar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } a cliente",
+        "delete-entity-views": "Borrar vistas de entidad",
+        "unassign-from-customer": "Anular asignación a cliente",
+        "unassign-entity-views": "Anular asignación de vistas de entidad",
+        "unassign-entity-views-action-title": "Anular asignación { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } al cliente",
+        "assign-new-entity-view": "Asignar nueva vista de entidad",
+        "delete-entity-view-title": "¿Está seguro que quiere borrar la vista de entidad '{{entityViewName}}'?",
+        "delete-entity-view-text": "¡Cuidado! Después de la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.",
+        "delete-entity-views-title": "¿Está seguro que quiere borrar las vistas de entidad { count, plural, 1 {1 entityView} other {# entityViews} }?",
+        "delete-entity-views-action-title": "Borrar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }",
+        "delete-entity-views-text": "¡Cuidado! Después de la confirmación, todas las vistas de entidades seleccionadas se eliminarán y todos los datos relacionados serán irrecuperables.",
+        "unassign-entity-view-title": "¿Está seguro que quiere anular la asignación de la vista de entidad '{{entityViewName}}'?",
+        "unassign-entity-view-text": "Después de la confirmación, la vista de la entidad quedará sin asignar y el cliente no podrá acceder a ella.",
+        "unassign-entity-view": "Anular asignación de la vista de entidad",
+        "unassign-entity-views-title": "¿Está seguro que quiere anular la asignación de { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }?",
+        "unassign-entity-views-text": "Después de la confirmación, todas las vistas de entidades seleccionadas quedarán sin asignar y el cliente no podrá acceder a ellas.",
+        "entity-view-type": "Tipo de vista de entidad",
+        "entity-view-type-required": "Tipo de vista de entidad es requerido.",
+        "select-entity-view-type": "Seleccione el tipo de vista de entidad",
+        "enter-entity-view-type": "Teclee el tipo de vista de entidad",
+        "any-entity-view": "Cualquier vista de entidad",
+        "no-entity-view-types-matching": "No se encontraron typos de vista de entidad '{{entitySubtype}}'.",
+        "entity-view-type-list-empty": "No hay tipos de vista de entidad seleccionados.",
+        "entity-view-types": "Tipos de vista de entidad",
+        "name": "Nombre",
+        "name-required": "Nombre es requerido.",
+        "description": "Descripción",
+        "events": "Eventos",
+        "details": "Detalles",
+        "copyId": "Copiar el Id de la vista de entidad",
+        "assignedToCustomer": "Asignado a cliente",
+        "unable-entity-view-device-alias-title": "No se puede eliminar el alias de vista de entidad",
+        "unable-entity-view-device-alias-text": "El alias del dispositivo '{{entityViewAlias}}' no se puede borrar porque está siendo usado por el widget(s):<br/>{{widgetsList}}",
+        "select-entity-view": "Seleccionar vista de entidad",
+        "make-public": "Hacer pública la vista de entidad",
+        "start-date": "Fecha de inicio",
+        "start-ts": "Tiempo de inicio",
+        "end-date": "Fecha de finalización",
+        "end-ts": "Tiempo de finalización",
+        "date-limits": "Limites de fecha",
+        "client-attributes": "Atributos de cliente",
+        "shared-attributes": "Atributos compartidos",
+        "server-attributes": "Atributos de servidor",
+        "timeseries": "Series de tiempo",
+        "client-attributes-placeholder": "Atributos de cliente",
+        "shared-attributes-placeholder": "Atributos compartidos",
+        "server-attributes-placeholder": "Atributos de servidor",
+        "timeseries-placeholder": "Series de tiempo",
+        "target-entity": "Entidad objetivo",
+        "attributes-propagation": "Propagación de atributos",
+        "attributes-propagation-hint": "La vista de entidad copiará automáticamente los atributos especificados de la entidad de destino cada vez que guarde o actualice esta vista de entidad. Por razones de rendimiento, los atributos de entidad objetivo no se propagan a la vista de entidad en cada cambio de atributo. Puede habilitar la propagación automática configurando el nodo de la regla \"copiar a la vista\" en su cadena de reglas y vincular los mensajes \"Atributos de la publicación\" y \"Atributos actualizados\" al nuevo nodo de la regla.",
+        "timeseries-data": "Datos de series de tiempo",
+        "timeseries-data-hint": "Configure las claves de los datos de las series de tiempo de la entidad de destino que serán accesibles para la vista de la entidad. Los datos de esta serie temporal son de solo lectura."
     },
     "event": {
         "event-type": "Tipo de evento",
@@ -848,7 +870,7 @@
         "type-stats": "Estadísticas",
         "type-debug-rule-node": "Depurar",
         "type-debug-rule-chain": "Depurar",
-        "no-events-prompt": "Eventos no encontrados",
+        "no-events-prompt": "No se encontraron eventos",
         "error": "Error",
         "alarm": "Alarma",
         "event-time": "Tiempo del evento",
@@ -865,14 +887,14 @@
         "data": "Datos",
         "event": "Evento",
         "status": "Estado",
-        "success": "Éxito",
-        "failed": "Falla",
+        "success": "Correcto",
+        "failed": "Erróneo",
         "messages-processed": "Mensajes procesados",
         "errors-occurred": "Errores ocurridos"
     },
     "extension": {
         "extensions": "Extensiones",
-        "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } seleccionadas",
+        "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensiones} } seleccionadas",
         "type": "Tipo",
         "key": "Clave",
         "value": "Valor",
@@ -885,9 +907,9 @@
         "add": "Agregar extensión",
         "edit": "Editar extensión",
         "delete-extension-title": "¿Está seguro de que desea eliminar la extensión '{{extensionId}}'?",
-        "delete-extension-text": "Tener cuidado, después de la confirmación, la extensión y todos los datos relacionados se volverán irrecuperables.",
-        "delete-extensions-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 extension} other {# extensions} }?",
-        "delete-extensions-text": "Tener cuidado, después de la confirmación, se eliminarán todas las extensiones seleccionadas.",
+        "delete-extension-text": "¡Cuidado! Después de la confirmación, la extensión y todos los datos relacionados serán irrecuperables.",
+        "delete-extensions-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 extension} other {# extensiones} }?",
+        "delete-extensions-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las extensiones seleccionadas.",
         "converters": "Conversores",
         "converter-id": "ID del conversor",
         "configuration": "Configuración",
@@ -986,6 +1008,8 @@
         "modbus-add-server": "Agregar servidor/esclavo",
         "modbus-add-server-prompt": "Por favor agregar servidor/esclavo",
         "modbus-transport": "Transporte",
+        "modbus-tcp-reconnect": "Reconexión automática",
+        "modbus-rtu-over-tcp": "RTU sobre TCP",
         "modbus-port-name": "Nombre del puerto serial",
         "modbus-encoding": "Codificación",
         "modbus-parity": "Paridad",
@@ -1038,12 +1062,12 @@
     },
     "grid": {
         "delete-item-title": "¿Está seguro de que desea eliminar este ítem?",
-        "delete-item-text": "Tener cuidado, después de la confirmación, este ítem y todos los datos relacionados se volverán irrecuperables.",
-        "delete-items-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 item} other {# items} }?",
-        "delete-items-action-title": "Eliminar { count, plural, 1 {1 item} other {# items} }",
-        "delete-items-text": "Tener cuidado, después de la confirmación se eliminarán todos los ítems seleccionados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-item-text": "¡Cuiado! Después de la confirmación, este ítem y todos los datos relacionados serán irrecuperables.",
+        "delete-items-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 ítem} other {# ítems} }?",
+        "delete-items-action-title": "Eliminar { count, plural, 1 {1 ítem} other {# ítems} }",
+        "delete-items-text": "¡Cuidado! Después de la confirmación se eliminarán todos los ítems seleccionados y todos los datos relacionados serán irrecuperables.",
         "add-item-text": "Agregar nuevo ítem",
-        "no-items-text": "Ítems no encontrados",
+        "no-items-text": "No se encontraron ítems",
         "item-details": "Detalles del ítem",
         "delete-item": "Eliminar ítem",
         "delete-items": "Eliminar ítems",
@@ -1070,7 +1094,7 @@
     "js-func": {
         "no-return-error": "La función debe devolver el valor!",
         "return-type-mismatch": "La función debe devolver el valor de '{{type}}' type!",
-        "tidy": "Ordenado"
+        "tidy": "Formatear"
     },
     "key-val": {
         "key": "Clave",
@@ -1102,19 +1126,19 @@
     },
     "login": {
         "login": "Iniciar sesión",
-        "request-password-reset": "Solicitar Restablecimiento Contraseña",
-        "reset-password": "Restablecer Contraseña",
-        "create-password": "Crear Contraseña",
-        "passwords-mismatch-error": "Las contraseñas introducidas deben ser las mismas!",
-        "password-again": "Contraseña nuevamente",
-        "sign-in": "Por favor registrarse",
+        "request-password-reset": "Restablecer contraseña",
+        "reset-password": "Restablecer contraseña",
+        "create-password": "Crear contraseña",
+        "passwords-mismatch-error": "¡Las contraseñas introducidas deben ser iguales!",
+        "password-again": "Repita la contraseña de nuevo",
+        "sign-in": "Por favor registrese",
         "username": "Nombre de usuario (correo electrónico)",
         "remember-me": "Recordarme",
-        "forgot-password": "Olvidó Contraseña?",
-        "password-reset": "Restablecimiento de Contraseña",
+        "forgot-password": "¿Olvidó la contraseña?",
+        "password-reset": "Restablecer contraseña",
         "new-password": "Nueva contraseña",
-        "new-password-again": "Nueva contraseña otra vez",
-        "password-link-sent-message": "El enlace para el restablecimieneto de la contraseña fue enviado exitosamente!",
+        "new-password-again": "Repita la nueva contraseña",
+        "password-link-sent-message": "¡El enlace para el restablecer la contraseña fue enviado correctamente!",
         "email": "Correo electrónico"
     },
     "position": {
@@ -1141,7 +1165,7 @@
         },
         "from-relations": "Relaciones salientes",
         "to-relations": "Relaciones entrantes",
-        "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selecciondas",
+        "selected-relations": "{ count, plural, 1 {1 relación} other {# relaciones} } selecciondas",
         "type": "Tipo",
         "to-entity-type": "Hacia tipo de entidad",
         "to-entity-name": "Hacia nombre de entidad",
@@ -1156,13 +1180,13 @@
         "add": "Agregar relación",
         "edit": "Editar relación",
         "delete-to-relation-title": "¿Está seguro de que desea eliminar la relación hacia la entidad '{{entityName}}'?",
-        "delete-to-relation-text": "Tener cuidado, después de la confirmación, la entidad '{{entityName}}' no estará relacionada desde la entidad actual.",
-        "delete-to-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relation} other {# relations} }?",
-        "delete-to-relations-text": "Tener cuidado, después de la confirmación, se eliminarán todas las relaciones seleccionadas y las entidades correspondientes no estarán relacionadas desde la entidad actual.",
+        "delete-to-relation-text": "¡Cuidado! Después de la confirmación, la entidad '{{entityName}}' no estará relacionada desde la entidad actual.",
+        "delete-to-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relación} other {# relaciones} }?",
+        "delete-to-relations-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las relaciones seleccionadas y las entidades correspondientes no estarán relacionadas desde la entidad actual.",
         "delete-from-relation-title": "¿Está seguro de que desea eliminar la relación desde la entidad '{{entityName}}'?",
-        "delete-from-relation-text": "Tener cuidado, después de la confirmación, la entidad actual no será relacionada desde la entidad '{{entityName}}'.",
-        "delete-from-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relation} other {# relations} }?",
-        "delete-from-relations-text": "Tener cuidado, después de la confirmación, se eliminarán todas las relaciones seleccionadas y la entidad actual no será relacionada desde las correspondientes entidades.",
+        "delete-from-relation-text": "¡Cuidado! Después de la confirmación, la entidad actual no será relacionada desde la entidad '{{entityName}}'.",
+        "delete-from-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relación} other {# relaciones} }?",
+        "delete-from-relations-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las relaciones seleccionadas y la entidad actual no será relacionada desde las correspondientes entidades.",
         "remove-relation-filter": "Eliminar filtro de relación",
         "add-relation-filter": "Agregar filtro de relación",
         "any-relation": "Alguna relación",
@@ -1183,10 +1207,10 @@
         "set-root-rulechain-title": "¿Está seguro de que desea hacer la cadena de reglas '{{ruleChainName}}' root?",
         "set-root-rulechain-text": "Después de la confirmación, la cadena de reglas se volverá raíz y manejará todos los mensajes de transporte entrantes.",
         "delete-rulechain-title": "¿Está seguro de que desea eliminar la cadena de reglas '{{ruleChainName}}'?",
-        "delete-rulechain-text": "Tener cuidado, después de la confirmación, la cadena de reglas y todos los datos relacionados se volverán irrecuperables.",
-        "delete-rulechains-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 rule chain} other {# rule chains} }?",
-        "delete-rulechains-action-title": "Eliminar { count, plural, 1 {1 rule chain} other {# rule chains} }",
-        "delete-rulechains-text": "Tener cuidado, después de la confirmación se eliminarán todas las cadenas de reglas seleccionadas y todos los datos relacionados se volverán irrecuperables.",
+        "delete-rulechain-text": "¡Cuidado! Después de la confirmación, la cadena de reglas y todos los datos relacionados serán irrecuperables.",
+        "delete-rulechains-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }?",
+        "delete-rulechains-action-title": "Eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }",
+        "delete-rulechains-text": "¡Cuidado! Después de la confirmación se eliminarán todas las cadenas de reglas seleccionadas y todos los datos relacionados serán irrecuperables.",
         "add-rulechain-text": "Agregar nueva cadena de reglas",
         "no-rulechains-text": "Cadenas de reglas no encontradas",
         "rulechain-details": "Detalles de la cadena de reglas",
@@ -1211,7 +1235,7 @@
         "details": "Detalles",
         "events": "Eventos",
         "search": "Nodos de búsqueda",
-        "open-node-library": "Abrir libreria de nodos",
+        "open-node-library": "Abrir librería de nodos",
         "add": "Agregar nodo de reglas",
         "name": "Nombre",
         "name-required": "El nombre es requerido.",
@@ -1237,7 +1261,7 @@
         "custom-link-label-required": "Etiqueta del enlace personalizado es requerida.",
         "link-labels": "Etiquetas del enlace",
         "link-labels-required": "Etiquetas del enlace son requeridas.",
-        "no-link-labels-found": "Etiquetas de enlaces no encontradas",
+        "no-link-labels-found": "No se encontraron etiquetas de enlaces",
         "no-link-label-matching": "'{{label}}' no encontrada.",
         "create-new-link-label": "Crear una nueva!",
         "type-filter": "Filtro",
@@ -1258,7 +1282,7 @@
         "type-unknown-details": "Regla de nodo no resuelta",
         "directive-is-not-loaded": "La directiva de configuración definida '{{directiveName}}' no está disponible.",
         "ui-resources-load-error": "Error al cargar los recursos de configuración ui.",
-        "invalid-target-rulechain": "No se puede resolver la cadena de reglas objetivo!",
+        "invalid-target-rulechain": "¡No se puede resolver la cadena de reglas objetivo!",
         "test-script-function": "Probar función script",
         "message": "Mensaje",
         "message-type": "Tipo de mensaje",
@@ -1274,7 +1298,7 @@
         "tenant": "Organización",
         "tenants": "Organizaciones",
         "management": "Gestión de la organización",
-        "add": "Agregar Organización",
+        "add": "Agregar organización",
         "admins": "Administradores",
         "manage-tenant-admins": "Gestionar administradores de la organización",
         "delete": "Eliminar organización",
@@ -1282,10 +1306,10 @@
         "no-tenants-text": "Organizaciones no encontradas",
         "tenant-details": "Detalles de la organización",
         "delete-tenant-title": "¿Está seguro de que desea eliminar la organización '{{tenantTitle}}'?",
-        "delete-tenant-text": "Tener cuidado, después de la confirmación, la organización y todos los datos relacionados se volverán irrecuperables.",
-        "delete-tenants-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 tenant} other {# tenants} }?",
-        "delete-tenants-action-title": "Eliminar { count, plural, 1 {1 tenant} other {# tenants} }",
-        "delete-tenants-text": "Tener cuidado, después de la confirmación se eliminarán todas las organizaciones seleccionadas y todos los datos relacionados se volverán irrecuperables.",
+        "delete-tenant-text": "¡Cuidado! Después de la confirmación, la organización y todos los datos relacionados serán irrecuperables.",
+        "delete-tenants-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 organización} other {# organizaciones} }?",
+        "delete-tenants-action-title": "Eliminar { count, plural, 1 {1 organización} other {# organizaciones} }",
+        "delete-tenants-text": "¡Cuidado! Después de la confirmación se eliminarán todas las organizaciones seleccionadas y todos los datos relacionados serán irrecuperables.",
         "title": "Título",
         "title-required": "Título es requerido.",
         "description": "Descripción",
@@ -1294,14 +1318,14 @@
         "copyId": "Copiar ID de la organización",
         "idCopiedMessage": "ID de la organización ha sido copiado al portapapeles",
         "select-tenant": "Seleccionar organización",
-        "no-tenants-matching": "Organizaciones que coincidan con '{{entity}}' no fueron encontradas.",
+        "no-tenants-matching": "No se encontraron organizaciones que coincidan con '{{entity}}'.",
         "tenant-required": "Organización es requerida"
     },
     "timeinterval": {
-        "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",
-        "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }",
-        "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }",
-        "days-interval": "{ days, plural, 1 {1 day} other {# days} }",
+        "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }",
+        "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }",
+        "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }",
+        "days-interval": "{ days, plural, 1 {1 día} other {# días} }",
         "days": "Días",
         "hours": "Horas",
         "minutes": "Minutos",
@@ -1309,17 +1333,17 @@
         "advanced": "Avanzado"
     },
     "timewindow": {
-        "days": "{ days, plural, 1 { day } other {# days } }",
-        "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }",
-        "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }",
-        "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }",
+        "days": "{ days, plural, 1 { día } other {# días } }",
+        "hours": "{ hours, plural, 0 { horas } 1 {1 hora } other {# horas } }",
+        "minutes": "{ minutes, plural, 0 { minutos } 1 {1 minuto } other {# minutos } }",
+        "seconds": "{ seconds, plural, 0 { segundos } 1 {1 segundo } other {# segundos } }",
         "realtime": "Tiempo real",
         "history": "Historia",
-        "last-prefix": "última",
-        "period": "desde {{ startTime }} to {{ endTime }}",
-        "edit": "Editat ventana de tiempo",
+        "last-prefix": "último(s)",
+        "period": "desde {{ startTime }} hasta {{ endTime }}",
+        "edit": "Editar ventana de tiempo",
         "date-range": "Rango de fecha",
-        "last": "Última",
+        "last": "Último(s)",
         "time-period": "Período de tiempo"
     },
     "user": {
@@ -1334,14 +1358,14 @@
         "add": "Agregar Usuario",
         "delete": "Eliminar usuario",
         "add-user-text": "Agregar nuevo usuario",
-        "no-users-text": "Usuarios no encontrados",
+        "no-users-text": "No se encontraron usuarios",
         "user-details": "Detalles de usuario",
         "delete-user-title": "¿Está seguro de que desea eliminar el usuario '{{userEmail}}'?",
-        "delete-user-text": "Tener cuidado, después de la confirmación, el usuario y todos los datos relacionados se volverán irrecuperables.",
-        "delete-users-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 user} other {# users} }?",
-        "delete-users-action-title": "Delete { count, plural, 1 {1 user} other {# users} }",
-        "delete-users-text": "Tener cuidado, después de la confirmación se eliminarán todas los usuarios seleccionados y todos los datos relacionados se volverán irrecuperables.",
-        "activation-email-sent-message": "Correo electrónico de activación fue enviado exitosamente!",
+        "delete-user-text": "¡Cuidado! Después de la confirmación, el usuario y todos los datos relacionados serán irrecuperables.",
+        "delete-users-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?",
+        "delete-users-action-title": "Borrar { count, plural, 1 {1 usuario} other {# usuarios} }",
+        "delete-users-text": "¡Cuidado! Después de la confirmación se eliminarán todas los usuarios seleccionados y todos los datos relacionados serán irrecuperables.",
+        "activation-email-sent-message": "¡Correo electrónico de activación fue enviado correctamente!",
         "resend-activation": "Reenviar activación",
         "email": "Correo electrónico",
         "email-required": "Correo electrónico es requerido.",
@@ -1358,12 +1382,12 @@
         "display-activation-link": "Mostrar enlace de activación",
         "send-activation-mail": "Enviar correo electrónico de activación",
         "activation-link": "Enlace de activación de usuario",
-        "activation-link-text": "Para activar el usuario, utilizar los siguientes <a href='{{activationLink}}' target='_blank'>activation link</a> :",
+        "activation-link-text": "Para activar el usuario, utilice el siguiente <a href='{{activationLink}}' target='_blank'>enlace de activación</a> :",
         "copy-activation-link": "Copiar enlace de activación",
         "activation-link-copied-message": "El enlace de activación de usuario ha sido copiado al portapapeles",
         "details": "Detalles",
         "login-as-tenant-admin": "Iniciar sesión como Administrador de la Organización",
-        "login-as-customer-user": "Iniciar sesión como Usuario Cliente"
+        "login-as-customer-user": "Iniciar sesión como Usuario cliente"
     },
     "value": {
         "type": "Tipo de valor",
@@ -1381,32 +1405,32 @@
         "long": "Largo"
     },
     "widget": {
-        "widget-library": "Libreria de Widgets",
-        "widget-bundle": "Paquete de Widgets",
+        "widget-library": "Librería de widgets",
+        "widget-bundle": "Paquete de widgets",
         "select-widgets-bundle": "Seleccionar paquete de widgets",
         "management": "Gestión de widget",
         "editor": "Editor de Widget",
-        "widget-type-not-found": "Problema cargando configuración de widget.<br>Probablemente tipo de widget asociado fue eliminado.",
+        "widget-type-not-found": "Problema cargando configuración de widget.<br>Probablemente el tipo de widget asociado fue eliminado.",
         "widget-type-load-error": "El widget no fue cargado debido a los siguientes errores:",
         "remove": "Eliminar widget",
         "edit": "Editar widget",
         "remove-widget-title": "¿Está seguro de que desea eliminar el widget '{{widgetTitle}}'?",
-        "remove-widget-text": "Después de la confirmación, el widget y todos los datos relacionados se volverán irrecuperables.",
+        "remove-widget-text": "Después de la confirmación, el widget y todos los datos relacionados serán irrecuperables.",
         "timeseries": "Series temporales",
         "search-data": "Buscar datos",
-        "no-data-found": "Datos no encontrados",
+        "no-data-found": "No se encontraron datos",
         "latest-values": "Últimos valores",
         "rpc": "Widget de control",
         "alarm": "Widget de alarma",
         "static": "Widget estático",
         "select-widget-type": "Seleccionar tipo de widget",
-        "missing-widget-title-error": "Título del widget debe ser especificado!",
+        "missing-widget-title-error": "¡Título del widget debe ser especificado!",
         "widget-saved": "Widget guardado",
-        "unable-to-save-widget-error": "No se puede guardar widget! El widget tiene errores!",
+        "unable-to-save-widget-error": "¡No se puede guardar widget! ¡El widget tiene errores!",
         "save": "Guardar widget",
         "saveAs": "Guardar widget como",
         "save-widget-type-as": "Guardar tipo de widget como",
-        "save-widget-type-as-text": "Por favor ingresar nuevo título del widget y/o seleccionar paquete de widgets objetivo",
+        "save-widget-type-as-text": "Por favor escriba el nuevo título del widget y/o seleccionar paquete de widgets objetivo",
         "toggle-fullscreen": "Alternar pantalla completa",
         "run": "Ejecutar widget",
         "title": "Título del widget",
@@ -1415,20 +1439,20 @@
         "resources": "Recursos",
         "resource-url": "JavaScript/CSS URL",
         "remove-resource": "Eliminar recurso",
-        "add-resource": "Agregar recursose",
+        "add-resource": "Agregar recurso",
         "html": "HTML",
-        "tidy": "Ordenado",
+        "tidy": "Formatear",
         "css": "CSS",
         "settings-schema": "Esquema de configuración",
         "datakey-settings-schema": "Esquema de configuración de clave de datos",
         "javascript": "Javascript",
         "remove-widget-type-title": "¿Está seguro de que desea eliminar el tipo de widget '{{widgetName}}'?",
-        "remove-widget-type-text": "Después de la confirmación, el tipo de widget y todos los datos relacionados se volverán irrecuperables.",
+        "remove-widget-type-text": "Después de la confirmación, el tipo de widget y todos los datos relacionados serán irrecuperables.",
         "remove-widget-type": "Eliminar tipo de widget",
         "add-widget-type": "Agregar nuevo tipo de widget",
-        "widget-type-load-failed-error": "Failed to load widget type!",
-        "widget-template-load-failed-error": "Error al cargar la plantilla del widget!",
-        "add": "Agregar Widget",
+        "widget-type-load-failed-error": "¡Error al cargar el tipo de widget!",
+        "widget-template-load-failed-error": "¡Error al cargar la plantilla del widget!",
+        "add": "Agregar widget",
         "undo": "Deshacer cambios en el widget",
         "export": "Exportar widget"
     },
@@ -1446,21 +1470,21 @@
     },
     "widgets-bundle": {
         "current": "Paquete actual",
-        "widgets-bundles": "Paquetes de Widgets",
-        "add": "Agregar Paquete de Widgets",
-        "delete": "Eliminar paquete de widget",
+        "widgets-bundles": "Paquetes de widgets",
+        "add": "Agregar paquete de widgets",
+        "delete": "Eliminar paquete de widgets",
         "title": "Título",
         "title-required": "Título es requerido.",
         "add-widgets-bundle-text": "Agregar nuevo paquete de widgets",
-        "no-widgets-bundles-text": "Paquetes de widgets no encontrados",
+        "no-widgets-bundles-text": "No se encontraron paquetes de widgets",
         "empty": "Paquete de widgets está vacío",
         "details": "Detalles",
         "widgets-bundle-details": "Detalles del paquete de widgets",
         "delete-widgets-bundle-title": "¿Está seguro de que desea eliminar el paquete de widgets '{{widgetsBundleTitle}}'?",
-        "delete-widgets-bundle-text": "Tener cuidado, después de la confirmación, el paquete de widgets y todos los datos relacionados se volverán irrecuperables.",
-        "delete-widgets-bundles-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?",
-        "delete-widgets-bundles-action-title": "Eliminar { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }",
-        "delete-widgets-bundles-text": "Tener cuidado, después de la confirmación se eliminarán todas los paquetes de widgets seleccionados y todos los datos relacionados se volverán irrecuperables.",
+        "delete-widgets-bundle-text": "¡Cuidado! Después de la confirmación, el paquete de widgets y todos los datos relacionados serán irrecuperables.",
+        "delete-widgets-bundles-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?",
+        "delete-widgets-bundles-action-title": "Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }",
+        "delete-widgets-bundles-text": "¡Cuidado! Después de la confirmación se eliminarán todas los paquetes de widgets seleccionados y todos los datos relacionados serán irrecuperables.",
         "no-widgets-bundles-matching": "Paquetes de widgets que coincidan con '{{widgetsBundle}}' no fueron encontrados.",
         "widgets-bundle-required": "Paquete de widgets es requerido.",
         "system": "Sistema",
@@ -1492,25 +1516,25 @@
         "units": "Símbolo especial para mostrar junto al valor.",
         "decimals": "Número de dígitos después del punto flotante",
         "timewindow": "Ventana de tiempo",
-        "use-dashboard-timewindow": "Utilizar ventana de timpo del panel",
+        "use-dashboard-timewindow": "Utilizar ventana de tiempo del panel",
         "display-legend": "Mostrar leyenda",
-        "datasources": "Fuentes de datos",
-        "maximum-datasources": "Máximo { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
+        "datasources": "Orígenes de datos",
+        "maximum-datasources": "Máximo { count, plural, 1 {1 origen de datos permitido.} other {# origenes de datos permitidos} }",
         "datasource-type": "Tipo",
-        "datasource-parameters": "Parametros",
-        "remove-datasource": "Eliminar fuente de datos",
-        "add-datasource": "Agregar fuente de datos",
+        "datasource-parameters": "Parámetros",
+        "remove-datasource": "Eliminar origen de datos",
+        "add-datasource": "Agregar origen de datos",
         "target-device": "Dispositivo objetivo",
-        "alarm-source": "Fuente de alarma",
-        "actions": "Accines",
+        "alarm-source": "Origen de la alarma",
+        "actions": "Acciones",
         "action": "Acción",
         "add-action": "Agregar acción",
         "search-actions": "Buscar acciones",
-        "action-source": "Fuente de acción",
+        "action-source": "Origen de acción",
         "action-source-required": "Fuente de acción es requerida.",
         "action-name": "Nombre",
         "action-name-required": "Nombre de acción es requerido.",
-        "action-name-not-unique": "Ya existe otra acción con el mismo nombre.<br/>El nombre de la acción debe ser único dentro de la misma fuente de acción.",
+        "action-name-not-unique": "Ya existe otra acción con el mismo nombre.<br/>El nombre de la acción debe ser único dentro del mismo orígen de acción.",
         "action-icon": "Icono",
         "action-type": "Tipo",
         "action-type-required": "Tipo de acción es requerido.",
@@ -1530,7 +1554,7 @@
     "icon": {
         "icon": "Icono",
         "select-icon": "Seleccionar icono",
-        "material-icons": "Iconos de maerial",
+        "material-icons": "Iconos de material design",
         "show-all": "Mostrar todos los iconos"
     },
     "custom": {
diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json
index eb9996d..3c6126e 100644
--- a/ui/src/app/locale/locale.constant-ru_RU.json
+++ b/ui/src/app/locale/locale.constant-ru_RU.json
@@ -22,8 +22,9 @@
         "update": "Обновить",
         "remove": "Удалить",
         "search": "Поиск",
+        "clear-search": "Очистить",
         "assign": "Присвоить",
-        "unassign": "Отменить присвоение",
+        "unassign": "Отозвать",
         "share": "Поделиться",
         "make-private": "Закрыть для общего доступа",
         "apply": "Применить",
@@ -43,6 +44,8 @@
         "undo": "Откатить",
         "copy": "Копировать",
         "paste": "Вставить",
+        "copy-reference": "Копировать ссылку",
+        "paste-reference": "Вставить ссылку",
         "import": "Импортировать",
         "export": "Экспортировать",
         "share-via": "Поделиться в {{provider}}"
@@ -83,168 +86,179 @@
         "send-test-mail": "Отправить пробное письмо"
     },
     "alarm": {
-        "alarm": "Alarm",
-        "alarms": "Alarms",
-        "select-alarm": "Select alarm",
-        "no-alarms-matching": "No alarms matching '{{entity}}' were found.",
-        "alarm-required": "Alarm is required",
-        "alarm-status": "Alarm status",
+        "alarm": "Оповещение",
+        "alarms": "Оповещения",
+        "select-alarm": "Выбрать оповещение",
+        "no-alarms-matching": "Оповещения '{{entity}}' не найдены.",
+        "alarm-required": "Оповещение обязательно",
+        "alarm-status": "Статус оповещения",
         "search-status": {
-            "ANY": "Any",
-            "ACTIVE": "Active",
-            "CLEARED": "Cleared",
-            "ACK": "Acknowledged",
-            "UNACK": "Unacknowledged"
+            "ANY": "Все",
+            "ACTIVE": "Активные",
+            "CLEARED": "Сброшенные",
+            "ACK": "Подтвержденные",
+            "UNACK": "Неподтвержденные"
         },
         "display-status": {
-            "ACTIVE_UNACK": "Active Unacknowledged",
-            "ACTIVE_ACK": "Active Acknowledged",
-            "CLEARED_UNACK": "Cleared Unacknowledged",
-            "CLEARED_ACK": "Cleared Acknowledged"
+            "ACTIVE_UNACK": "Активные неподтвержденные",
+            "ACTIVE_ACK": "Активные подтвержденные",
+            "CLEARED_UNACK": "Сброшенные неподтвержденные",
+            "CLEARED_ACK": "Сброшенные подтвержденные"
         },
-        "no-alarms-prompt": "No alarms found",
-        "created-time": "Created time",
-        "type": "Type",
-        "severity": "Severity",
-        "originator": "Originator",
-        "originator-type": "Originator type",
-        "details": "Details",
-        "status": "Status",
-        "alarm-details": "Alarm details",
-        "start-time": "Start time",
-        "end-time": "End time",
-        "ack-time": "Acknowledged time",
-        "clear-time": "Cleared time",
-        "severity-critical": "Critical",
-        "severity-major": "Major",
-        "severity-minor": "Minor",
-        "severity-warning": "Warning",
-        "severity-indeterminate": "Indeterminate",
-        "acknowledge": "Acknowledge",
-        "clear": "Clear",
-        "search": "Search alarms",
-        "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selected",
-        "no-data": "No data to display",
-        "polling-interval": "Alarms polling interval (sec)",
-        "polling-interval-required": "Alarms polling interval is required.",
-        "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
-        "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }",
-        "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?",
-        "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }",
-        "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?"
+        "no-alarms-prompt": "Оповещения отсутствуют",
+        "created-time": "Время создания",
+        "type": "Тип",
+        "severity": "Уровень",
+        "originator": "Инициатор",
+        "originator-type": "Тип инициатора",
+        "details": "Подробности",
+        "status": "Статус",
+        "alarm-details": "Подробности об оповещении",
+        "start-time": "Время начала",
+        "end-time": "Время окончания",
+        "ack-time": "Время подтверждения",
+        "clear-time": "Время сброса",
+        "severity-critical": "Критический",
+        "severity-major": "Основной",
+        "severity-minor": "Второстепенный",
+        "severity-warning": "Предупреждение",
+        "severity-indeterminate": "Неопределенный",
+        "acknowledge": "Подтвердить",
+        "clear": "Сбросить",
+        "search": "Поиск оповещений",
+        "selected-alarms": "Выбрано { count, plural, 1 {1 оповещение} few {# оповещения} other {# оповещений} }",
+        "no-data": "Нет данных для отображения",
+        "polling-interval": "Интервал опроса оповещений (сек)",
+        "polling-interval-required": "Интервал опроса оповещений обязателен.",
+        "min-polling-interval-message": "Минимальный интервал опроса оповещений 1 секунда.",
+        "aknowledge-alarms-title": "Подтвердить { count, plural, 1 {1 оповещение} other {# оповещений} }",
+        "aknowledge-alarms-text": "Вы точно хотите подтвердить { count, plural, 1 {1 оповещение} other {# оповещений} }?",
+        "aknowledge-alarm-title": "Подтвердить оповещение",
+        "aknowledge-alarm-text": "Вы точно хотите подтвердить оповещение?",
+        "clear-alarms-title": "Сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }",
+        "clear-alarms-text": "Вы точно хотите сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }?",
+        "clear-alarm-title": "Сбросить оповещение",
+        "clear-alarm-text": "Вы точно хотите сбросить оповещение?",
+        "alarm-status-filter": "Фильтр оповещений"
     },
     "alias": {
-        "add": "Add alias",
-        "edit": "Edit alias",
-        "name": "Alias name",
-        "name-required": "Alias name is required",
-        "duplicate-alias": "Alias with same name is already exists.",
-        "filter-type-single-entity": "Single entity",
-        "filter-type-entity-list": "Entity list",
-        "filter-type-entity-name": "Entity name",
-        "filter-type-state-entity": "Entity from dashboard state",
-        "filter-type-state-entity-description": "Entity taken from dashboard state parameters",
-        "filter-type-asset-type": "Asset type",
-        "filter-type-asset-type-description": "Assets of type '{{assetType}}'",
-        "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
-        "filter-type-device-type": "Device type",
-        "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
-        "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
-        "filter-type-relations-query": "Relations query",
-        "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
-        "filter-type-asset-search-query": "Asset search query",
-        "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
-        "filter-type-device-search-query": "Device search query",
-        "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
-        "entity-filter": "Entity filter",
-        "resolve-multiple": "Resolve as multiple entities",
-        "filter-type": "Filter type",
-        "filter-type-required": "Filter type is required.",
-        "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
-        "no-entity-filter-specified": "No entity filter specified",
-        "root-state-entity": "Use dashboard state entity as root",
-        "root-entity": "Root entity",
-        "state-entity-parameter-name": "State entity parameter name",
-        "default-state-entity": "Default state entity",
-        "default-entity-parameter-name": "By default",
-        "max-relation-level": "Max relation level",
-        "unlimited-level": "Unlimited level",
-        "state-entity": "Dashboard state entity",
-        "all-entities": "All entities",
-        "any-relation": "any"
+        "add": "Добавить псевдоним",
+        "edit": "Редактировать псевдоним",
+        "name": "Псевдоним",
+        "name-required": "Псевдоним обязателен",
+        "duplicate-alias": "Такой псевдоним уже существует.",
+        "filter-type-single-entity": "Отдельный объект",
+        "filter-type-entity-list": "Список объектов",
+        "filter-type-entity-name": "Название объекта",
+        "filter-type-state-entity": "Объект, полученный из дашборда",
+        "filter-type-state-entity-description": "Объект, полученный из параметров дашборда",
+        "filter-type-asset-type": "Тип актива",
+        "filter-type-asset-type-description": "Активы типа '{{assetType}}'",
+        "filter-type-asset-type-and-name-description": "Активы типа '{{assetType}}' и названием, начинающимся с '{{prefix}}'",
+        "filter-type-device-type": "Тип устройства",
+        "filter-type-device-type-description": "Устройства типа '{{deviceType}}'",
+        "filter-type-device-type-and-name-description": "Устройства типа '{{deviceType}}' и названием, начинающимся с '{{prefix}}'",
+        "filter-type-entity-view-type": "Тип Представления Объекта",
+        "filter-type-entity-view-type-description": "Представления Объекта типа '{{entityView}}'",
+        "filter-type-entity-view-type-and-name-description": "Представления Объекта типа '{{entityView}}' и названием, начинающимся с '{{prefix}}'",
+        "filter-type-relations-query": "Запрос по типу отношений",
+        "filter-type-relations-query-description": "{{entities}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}",
+        "filter-type-asset-search-query": "Поисковый запрос по активам",
+        "filter-type-asset-search-query-description": "Активы типа {{assetTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}",
+        "filter-type-device-search-query": "Поисковый запрос по устройствам",
+        "filter-type-device-search-query-description": "Устройства типа {{deviceTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}",
+        "filter-type-entity-view-search-query": "Поисковый запрос по представлениям объектов",
+        "filter-type-entity-view-search-query-description": "Представления объектов типа {{entityViewTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}",
+        "entity-filter": "Фильтр объектов",
+        "resolve-multiple": "Принять как несколько объектов",
+        "filter-type": "Тип фильтра",
+        "filter-type-required": "Тип фильтра обязателен.",
+        "entity-filter-no-entity-matched": "Объекты, соответствующие фильтру, не найдены.",
+        "no-entity-filter-specified": "Не указан фильтр объектов",
+        "root-state-entity": "Использовать объект, полученный из дашборда, как корневой",
+        "root-entity": "Корневой объект",
+        "state-entity-parameter-name": "Название объекта состояния",
+        "default-state-entity": "Объект состояния по умолчанию",
+        "default-entity-parameter-name": "По умолчанию",
+        "max-relation-level": "Максимальная глубина отношений",
+        "unlimited-level": "Неограниченная глубина",
+        "state-entity": "Объект состояния дашборда",
+        "all-entities": "Все объекты",
+        "any-relation": "не указано"
     },
     "asset": {
-        "asset": "Asset",
-        "assets": "Assets",
-        "management": "Asset management",
-        "view-assets": "View Assets",
-        "add": "Add Asset",
-        "assign-to-customer": "Assign to customer",
-        "assign-asset-to-customer": "Assign Asset(s) To Customer",
-        "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
-        "no-assets-text": "No assets found",
-        "assign-to-customer-text": "Please select the customer to assign the asset(s)",
-        "public": "Public",
-        "assignedToCustomer": "Assigned to customer",
-        "make-public": "Make asset public",
-        "make-private": "Make asset private",
-        "unassign-from-customer": "Unassign from customer",
-        "delete": "Delete asset",
-        "asset-public": "Asset is public",
-        "asset-type": "Asset type",
-        "asset-type-required": "Asset type is required.",
-        "select-asset-type": "Select asset type",
-        "enter-asset-type": "Enter asset type",
-        "any-asset": "Any asset",
-        "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
-        "asset-type-list-empty": "No asset types selected.",
-        "asset-types": "Asset types",
-        "name": "Name",
-        "name-required": "Name is required.",
-        "description": "Description",
-        "type": "Type",
-        "type-required": "Type is required.",
-        "details": "Details",
-        "events": "Events",
-        "add-asset-text": "Add new asset",
-        "asset-details": "Asset details",
-        "assign-assets": "Assign assets",
-        "assign-assets-text": "Assign { count, plural, 1 {1 asset} other {# assets} } to customer",
-        "delete-assets": "Delete assets",
-        "unassign-assets": "Unassign assets",
-        "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer",
-        "assign-new-asset": "Assign new asset",
-        "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?",
-        "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.",
-        "delete-assets-title": "Are you sure you want to delete { count, plural, 1 {1 asset} other {# assets} }?",
-        "delete-assets-action-title": "Delete { count, plural, 1 {1 asset} other {# assets} }",
-        "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.",
-        "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?",
-        "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.",
-        "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?",
-        "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.",
-        "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?",
-        "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.",
-        "unassign-asset": "Unassign asset",
-        "unassign-assets-title": "Are you sure you want to unassign { count, plural, 1 {1 asset} other {# assets} }?",
-        "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
-        "copyId": "Copy asset Id",
-        "idCopiedMessage": "Asset Id has been copied to clipboard",
-        "select-asset": "Select asset",
-        "no-assets-matching": "No assets matching '{{entity}}' were found.",
-        "asset-required": "Asset is required",
-        "name-starts-with": "Asset name starts with"
+        "asset": "Актив",
+        "assets": "Активы",
+        "management": "Управление активами",
+        "view-assets": "Просмотреть активы",
+        "add": "Добавить актив",
+        "assign-to-customer": "Присвоить клиенту",
+        "assign-asset-to-customer": "Присвоить актив(ы) клиенту",
+        "assign-asset-to-customer-text": "Пожалуйста, выберите активы, которые нужно присвоить объекту",
+        "no-assets-text": "Активы не найдены",
+        "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно присвоить актив(ы)",
+        "public": "Общедоступные",
+        "assignedToCustomer": "Присвоить клиенту",
+        "make-public": "Открыть общий доступ к активу",
+        "make-private": "Закрыть общий доступ к активу",
+        "unassign-from-customer": "Отозвать у клиента",
+        "delete": "Удалить актив",
+        "asset-public": "Актив общедоступный",
+        "asset-type": "Тип актива",
+        "asset-type-required": "Тип актива обязателен.",
+        "select-asset-type": "Выберите тип актива",
+        "enter-asset-type": "Введите тип актива",
+        "any-asset": "Любой актив",
+        "no-asset-types-matching": "Активы типа '{{entitySubtype}}' не найдены.",
+        "asset-type-list-empty": "Типы активов не выбраны.",
+        "asset-types": "Типы активов",
+        "name": "Название",
+        "name-required": "Название обязательно.",
+        "description": "Описание",
+        "type": "Тип",
+        "type-required": "Тип обязателен.",
+        "details": "Подробности",
+        "events": "События",
+        "add-asset-text": "Добавить новый актив",
+        "asset-details": "Подробности об активе",
+        "assign-assets": "Присвоить активы",
+        "assign-assets-text": "Присвоить { count, plural, 1 {1 актив} few {# актива} other {# активов} } клиенту",
+        "delete-assets": "Удалить активы",
+        "unassign-assets": "Отозвать активы",
+        "unassign-assets-action-title": "Отозвать { count, plural, 1 {1 актив} few {# актива} other {# активов} } у клиента",
+        "assign-new-asset": "Присвоить новый актив",
+        "delete-asset-title": "Вы точно хотите удалить '{{assetName}}'?",
+        "delete-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные будут безвозвратно удалены.",
+        "delete-assets-title": "Вы точно хотите удалить { count, plural, 1 {1 актив} few {# актива} other {# активов} }",
+        "delete-assets-action-title": "Удалить { count, plural, 1 {1 актив} few {# актива} other {# активов} }",
+        "delete-assets-text": "Внимание, после подтверждения выбранные активы и все связанные с ними данные будут безвозвратно удалены.",
+        "make-public-asset-title": "Вы точно хотите открыть общий доступ к активу '{{assetName}}'?",
+        "make-public-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные станут общедоступными.",
+        "make-private-asset-title": "Вы точно хотите закрыть общий доступ к активу '{{assetName}}'?",
+        "make-private-asset-text": "После подтверждения актив и все связанные с ним данные будут закрыты для общего доступа",
+        "unassign-asset-title": "Вы точно хотите отозвать актив '{{assetName}}'?",
+        "unassign-asset-text": "После подтверждения актив будут отозван, и клиент потеряет к нему доступ.",
+        "unassign-asset": "Отозвать актив",
+        "unassign-assets-title": "Вы точно хотите отозвать { count, plural, 1 {1 актив} few {# актива} other {# активов} }?",
+        "unassign-assets-text": "После подтверждения активы будут отозваны, и клиент потеряет к ним доступ.",
+        "copyId": "Копировать ИД актива",
+        "idCopiedMessage": "ИД актива скопировано в буфер обмена",
+        "select-asset": "Выбрать активы",
+        "no-assets-matching": "Активы, соответствующие '{{entity}}', не найдены.",
+        "asset-required": "Актив обязателен",
+        "name-starts-with": "Название актива, начинающееся с"
     },
     "attribute": {
         "attributes": "Атрибуты",
         "latest-telemetry": "Последняя телеметрия",
-        "attributes-scope": "Контекст атрибутов устройства",
+        "attributes-scope": "Контекст атрибутов объекта",
         "scope-latest-telemetry": "Последняя телеметрия",
         "scope-client": "Клиентские атрибуты",
         "scope-server": "Серверные атрибуты",
         "scope-shared": "Общие атрибуты",
         "add": "Добавить атрибут",
         "key": "Ключ",
+        "last-update-time": "Последнее обновление",
         "key-required": "Ключ атрибута обязателен.",
         "value": "Значение",
         "value-required": "Значение атрибута обязательно.",
@@ -262,36 +276,41 @@
         "selected-telemetry": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, 1 {1 параметр} few {# параметра} other {# параметров} } телеметрии"
     },
     "audit-log": {
-        "audit": "Audit",
-        "audit-logs": "Audit Logs",
-        "timestamp": "Timestamp",
-        "entity-type": "Entity Type",
-        "entity-name": "Entity Name",
-        "user": "User",
-        "type": "Type",
-        "status": "Status",
-        "details": "Details",
-        "type-added": "Added",
-        "type-deleted": "Deleted",
-        "type-updated": "Updated",
-        "type-attributes-updated": "Attributes updated",
-        "type-attributes-deleted": "Attributes deleted",
-        "type-rpc-call": "RPC call",
-        "type-credentials-updated": "Credentials updated",
-        "type-assigned-to-customer": "Assigned to Customer",
-        "type-unassigned-from-customer": "Unassigned from Customer",
-        "type-activated": "Activated",
-        "type-suspended": "Suspended",
-        "type-credentials-read": "Credentials read",
-        "type-attributes-read": "Attributes read",
-        "status-success": "Success",
-        "status-failure": "Failure",
-        "audit-log-details": "Audit log details",
-        "no-audit-logs-prompt": "No logs found",
-        "action-data": "Action data",
-        "failure-details": "Failure details",
-        "search": "Search audit logs",
-        "clear-search": "Clear search"
+        "audit": "Аудит",
+        "audit-logs": "Логи аудита",
+        "timestamp": "Время",
+        "entity-type": "Тип объекта",
+        "entity-name": "Название объекта",
+        "user": "Пользователь",
+        "type": "Тип",
+        "status": "Статус",
+        "details": "Подробности",
+        "type-added": "Добавленный",
+        "type-deleted": "Удаленный",
+        "type-updated": "Обновленный",
+        "type-attributes-updated": "Обновлены атрибуты",
+        "type-attributes-deleted": "Удалены атрибуты",
+        "type-rpc-call": "RPC вызов",
+        "type-credentials-updated": "Обновлены учетные данные",
+        "type-assigned-to-customer": "Присвоен клиенту",
+        "type-unassigned-from-customer": "Отозван у клиента",
+        "type-activated": "Активирован",
+        "type-suspended": "Приостановлен",
+        "type-credentials-read": "Чтение учетные данных",
+        "type-attributes-read": "Чтение атрибутов",
+        "type-relation-add-or-update": "Обновлены отношения",
+        "type-relation-delete": "Удалены отношения",
+        "type-relations-delete": "Удалены все отношения",
+        "type-alarm-ack": "Подтвержден",
+        "type-alarm-clear": "Сброшен",
+        "status-success": "Успех",
+        "status-failure": "Сбой",
+        "audit-log-details": "Подробности аудит лога",
+        "no-audit-logs-prompt": "Логи не найдены",
+        "action-data": "Данные действия",
+        "failure-details": "Подробности сбоя",
+        "search": "Поиск аудит логов",
+        "clear-search": "Очистить поиск"
     },
     "confirm-on-exit": {
         "message": "У вас есть несохраненные изменения. Вы точно хотите покинуть эту страницу?",
@@ -319,17 +338,22 @@
     },
     "content-type": {
         "json": "Json",
-        "text": "Text",
-        "binary": "Binary (Base64)"
+        "text": "Текстовый",
+        "binary": "Бинарный (Base64)"
     },
     "customer": {
+        "customer": "Клиент",
         "customers": "Клиенты",
         "management": "Управление клиентами",
-        "dashboard": "Дашборд клиентов",
-        "dashboards": "Дашборды клиентов",
+        "dashboard": "Дашборд клиента",
+        "dashboards": "Дашборды клиента",
         "devices": "Устройства клиента",
+        "entity-views": "Представления объектов клиента",
+        "assets": "Активы клиента",
         "public-dashboards": "Общедоступные дашборды",
         "public-devices": "Общедоступные устройства",
+        "public-assets": "Общедоступные активы",
+        "public-entity-views": "Общедоступные представления объектов",
         "add": "Добавить клиента",
         "delete": "Удалить клиента",
         "manage-customer-users": "Управление пользователями клиента",
@@ -337,31 +361,33 @@
         "manage-customer-dashboards": "Управление дашбордами клиента",
         "manage-public-devices": "Управление общедоступными устройствами",
         "manage-public-dashboards": "Управление общедоступными дашбордами",
+        "manage-customer-assets": "Управление активами клиента",
+        "manage-public-assets": "Управление общедоступными активами",
         "add-customer-text": "Добавить нового клиента",
         "no-customers-text": "Клиенты не найдены",
         "customer-details": "Подробности о клиенте",
         "delete-customer-title": "Вы точно хотите удалить клиента '{{customerTitle}}'?",
-        "delete-customer-text": "Внимание, после подтверждения клиент и вся связанная с ним информация будут безвозвратно утеряны.",
-        "delete-customers-title": "Вы точно хотите удалить { count, plural, one {1 клиента} other {# клиентов} }?",
-        "delete-customers-action-title": "Удалить { count, plural, one {1 клиента} other {# клиентов} } }",
-        "delete-customers-text": "Внимание, после подтверждения клиенты и вся связанная с ними информация будут безвозвратно утеряны.",
+        "delete-customer-text": "Внимание, после подтверждения клиент и все связанные с ним данные будут безвозвратно удалены.",
+        "delete-customers-title": "Вы точно хотите удалить { count, plural, 1 {1 клиент} few {# клиента} other {# клиентов} }?",
+        "delete-customers-action-title": "Удалить { count, plural, 1 {1 клиент} few {# клиента} other {# клиентов} }",
+        "delete-customers-text": "Внимание, после подтверждения выбранные клиенты и все связанные с ними данные будут безвозвратно удалены.",
         "manage-users": "Управление пользователями",
-        "manage-assets": "Manage assets",
+        "manage-assets": "Управление активами",
         "manage-devices": "Управление устройствами",
         "manage-dashboards": "Управление дашбордами",
         "title": "Имя",
         "title-required": "Название обязательно.",
         "description": "Описание",
-        "details": "Details",
-        "events": "Events",
-        "copyId": "Copy customer Id",
-        "idCopiedMessage": "Customer Id has been copied to clipboard",
-        "select-customer": "Select customer",
-        "no-customers-matching": "No customers matching '{{entity}}' were found.",
-        "customer-required": "Customer is required",
-        "select-default-customer": "Select default customer",
-        "default-customer": "Default customer",
-        "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
+        "details": "Подробности",
+        "events": "События",
+        "copyId": "Копировать ИД клиента",
+        "idCopiedMessage": "ИД клиента скопирован в буфер обмена",
+        "select-customer": "Выбрать клиента",
+        "no-customers-matching": "Клиенты, соответствующие '{{entity}}', не найдены.",
+        "customer-required": "Клиент обязателен",
+        "select-default-customer": "Выбрать клиента по умолчанию",
+        "default-customer": "Клиент по умолчанию",
+        "default-customer-required": "Клиент по умолчанию обязателен для отладки дашборда на уровне на уровне Владельца"
     },
     "datetime": {
         "date-from": "Дата с",
@@ -379,9 +405,15 @@
         "assign-dashboard-to-customer-text": "Пожалуйста, выберите дашборды, которые нужно прикрепить к клиенту",
         "assign-to-customer-text": "Пожалуйста, выберите клиента, к которому нужно прикрепить дашборд(ы)",
         "assign-to-customer": "Прикрепить к клиенту",
-        "unassign-from-customer": "Открепить от клиента",
+        "unassign-from-customer": "Отозвать у клиента",
         "make-public": "Открыть дашборд для общего доступа",
         "make-private": "Закрыть дашборд для общего доступа",
+        "manage-assigned-customers": "Управление назначенными клиентами",
+        "assigned-customers": "Назначенные клиенты",
+        "assign-to-customers": "Присвоить дашборд(ы) клиенту",
+        "assign-to-customers-text": "Пожалуйста, выбери клиентов, которым нужно присвоить дашборд(ы)",
+        "unassign-from-customers": "Отозвать дашборд(ы) у клиентов",
+        "unassign-from-customers-text": "Пожалуйста, выберите клиентов, у которых нужно отозвать дашборд(ы)",
         "no-dashboards-text": "Дашборды не найдены",
         "no-widgets": "Виджеты не сконфигурированы",
         "add-widget": "Добавить новый виджет",
@@ -396,19 +428,20 @@
         "add-dashboard-text": "Добавить новый дашборд",
         "assign-dashboards": "Прикрепить дашборды",
         "assign-new-dashboard": "Прикрепить новый дашборд",
-        "assign-dashboards-text": "Прикрепить { count, plural, 1 {1 дашборд} other {# дашборда} } к клиенту",
+        "assign-dashboards-text": "Прикрепить { count, plural, 1 {1 дашборд} few {# дашборда} other {# дашбордов} } к клиенту",
+        "unassign-dashboards-action-text": "Отозвать { count, plural, 1 {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента",
         "delete-dashboards": "Удалить дашборды",
-        "unassign-dashboards": "Открепить дашборды",
-        "unassign-dashboards-action-title": "Открепить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} } от клиента",
+        "unassign-dashboards": "Отозвать дашборды",
+        "unassign-dashboards-action-title": "Отозвать { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента",
         "delete-dashboard-title": "Вы точно хотите удалить дашборд '{{dashboardTitle}}'?",
         "delete-dashboard-text": "Внимание, после подтверждения дашборд и все связанные с ним данные будут безвозвратно утеряны.",
         "delete-dashboards-title": "Вы точно хотите удалить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }?",
         "delete-dashboards-action-title": "Удалить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }",
         "delete-dashboards-text": "Внимание, после подтверждения дашборды и все связанные с ними данные будут безвозвратно утеряны.",
-        "unassign-dashboard-title": "Вы точно хотите открепить дашборд '{{dashboardTitle}}'?",
+        "unassign-dashboard-title": "Вы точно хотите отозвать дашборд '{{dashboardTitle}}'?",
         "unassign-dashboard-text": "После подтверждения дашборд не будет доступен клиенту.",
-        "unassign-dashboard": "Открепить дашборд",
-        "unassign-dashboards-title": "Вы точно хотите открепить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }?",
+        "unassign-dashboard": "Отозвать дашборд",
+        "unassign-dashboards-title": "Вы точно хотите отозвать { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }?",
         "unassign-dashboards-text": "После подтверждения выбранные дашборды не будут доступны клиенту.",
         "public-dashboard-title": "Теперь дашборд общедоступный",
         "public-dashboard-text": "Теперь ваш дашборд <b>{{dashboardTitle}}</b> доступен всем по <a href='{{publicLink}}' target='_blank'>ссылке</a>:",
@@ -445,9 +478,17 @@
         "vertical-margin-required": "Величина вертикального отступа обязательна.",
         "min-vertical-margin-message": "Минимальная величина вертикального отступа - 0.",
         "max-vertical-margin-message": "Максимальная величина вертикального отступа - 50.",
+        "autofill-height": "Автозаполнение по высоте",
+        "mobile-layout": "Настройки мобильного режима",
+        "mobile-row-height": "Высота строки в мобильном режиме, px",
+        "mobile-row-height-required": "Высота строки в мобильном режиме обязательна.",
+        "min-mobile-row-height-message": "Минимальная высота строки в мобильном режиме составляет 5 px.",
+        "max-mobile-row-height-message": "Максимальная высота строки в мобильном режиме составляет 200 px.",
         "display-title": "Показать название дашборда",
+        "toolbar-always-open": "Отображать панель инструментов",
         "title-color": "Цвет названия",
-        "display-device-selection": "Показать выборку устройств",
+        "display-dashboards-selection": "Отображать выборку дашбордов",
+        "display-entities-selection": "Отображать выбору объектов",
         "display-dashboard-timewindow": "Показать временное окно",
         "display-dashboard-export": "Показать экспорт",
         "import": "Импортировать дашборд",
@@ -456,66 +497,74 @@
         "create-new-dashboard": "Создать новый дашборд",
         "dashboard-file": "Файл дашборда",
         "invalid-dashboard-file-error": "Не удалось импортировать дашборд: неизвестная схема данных дашборда.",
-        "dashboard-import-missing-aliases-title": "Конфигурировать псевдонимы импортированного дашборда",
+        "dashboard-import-missing-aliases-title": "Настроить псевдонимы импортированного дашборда",
         "create-new-widget": "Создать новый виджет",
-        "import-widget": "Импортировать новый виджет",
-        "widget-file": "Файл виджета",
-        "invalid-widget-file-error": "Не удалось импортировать виджет: неизвестная схема данных виджета.",
-        "widget-import-missing-aliases-title": "Конфигурировать псевдонимы импортированного виджета",
-        "open-toolbar": "Открыть панель инструментов",
+        "import-widget": "Импортировать виджет",
+        "widget-file": "Виджет-файл",
+        "invalid-widget-file-error": "Не удалось импортировать виджет: неправильный формат данных.",
+        "widget-import-missing-aliases-title": "Настроить псевдонимы, которые использует импортированный виджет",
+        "open-toolbar": "Открыть панель инструментов дашборда",
         "close-toolbar": "Закрыть панель инструментов",
-        "configuration-error": "Ошибка конфигурирования",
-        "alias-resolution-error-title": "Ошибка конфигурирования псевдонимов дашборда",
-        "invalid-aliases-config": "Не удалось найти устройства, соответствующие фильтру псевдонимов.<br/>Пожалуйста, свяжитесь с администратором для устранения этой проблемы.",
+        "configuration-error": "Ошибка в настройках",
+        "alias-resolution-error-title": "Ошибка в настройках псевдонимов дашборда",
+        "invalid-aliases-config": "Не удалось найти устройство, соответствующее фильтру псевдонимов.<br/>Пожалуйста, обратитесь к администратору для устранения неполадки.",
         "select-devices": "Выберите устройства",
-        "assignedToCustomer": "Прикреплен к клиенту",
-        "public": "Общедоступный",
-        "public-link": "Общедоступная ссылка",
-        "copy-public-link": "Скопировать общедоступную ссылку",
-        "public-link-copied-message": "Общедоступная ссылка на дашборд скопирована в буфер обмена",
-        "manage-states": "Manage dashboard states",
-        "states": "Dashboard states",
-        "search-states": "Search dashboard states",
-        "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected",
-        "edit-state": "Edit dashboard state",
-        "delete-state": "Delete dashboard state",
-        "add-state": "Add dashboard state",
-        "state": "Dashboard state",
-        "state-name": "Name",
-        "state-name-required": "Dashboard state name is required.",
-        "state-id": "State Id",
-        "state-id-required": "Dashboard state id is required.",
-        "state-id-exists": "Dashboard state with the same id is already exists.",
-        "is-root-state": "Root state",
-        "delete-state-title": "Delete dashboard state",
-        "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",
-        "show-details": "Show details",
-        "hide-details": "Hide details",
-        "select-state": "Select target state",
-        "state-controller": "State controller"
+        "assignedToCustomer": "Присвоенные клиенту",
+        "assignedToCustomers": "Присвоенные клиентам",
+        "public": "Публичный",
+        "public-link": "Публичная ссылка",
+        "copy-public-link": "Копировать публичную ссылку",
+        "public-link-copied-message": "Публичная ссылка на дашборд скопирована в буфер обмена.",
+        "manage-states": "Управление состоянием дашборда",
+        "states": "Состояния дашборда",
+        "search-states": "Поиск состояния дашборда",
+        "selected-states": "Выбрано { count, plural, 1 {1 состояние} few {# состояния} other {# состояний} } дашборда",
+        "edit-state": "Изменить состояние дашборда",
+        "delete-state": "Удалить состояние дашборда",
+        "add-state": "Добавить состояние дашборда",
+        "state": "Состояние дашборда",
+        "state-name": "Название",
+        "state-name-required": "Название состояния дашборда обязательно.",
+        "state-id": "ИД состояния",
+        "state-id-required": "ИД состояния дашборда обязателен.",
+        "state-id-exists": "Состояния дашборда с таким именем уже существует.",
+        "is-root-state": "Корневое состояние",
+        "delete-state-title": "Удалить состояние дашборда",
+        "delete-state-text": "Вы точно хотите удалить состояние дашборда '{{stateName}}'?",
+        "show-details": "Показать подробности",
+        "hide-details": "Скрыть подробности",
+        "select-state": "Выбрать состояние",
+        "state-controller": "Контроллер состояния"
     },
     "datakey": {
         "settings": "Настройки",
         "advanced": "Дополнительно",
         "label": "Метка",
         "color": "Цвет",
-        "units": "Special symbol to show next to value",
-        "decimals": "Number of digits after floating point",
+        "units": "Укажите символы, которые нужно указывать после значения",
+        "decimals": "Число знаков после запятой",
         "data-generation-func": "Функция генерации данных",
         "use-data-post-processing-func": "Использовать функцию пост-обработки данных",
         "configuration": "Конфигурация ключа данных",
-        "timeseries": "Выборка по времени",
+        "timeseries": "Телеметрия",
         "attributes": "Атрибуты",
-        "timeseries-required": "Выборка по времени обязательна.",
-        "timeseries-or-attributes-required": "Выборка по времени/атрибуты обязательны.",
-        "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }",
-        "alarm-fields-required": "Alarm fields are required.",
+        "alarm": "Параметры оповещения",
+        "timeseries-required": "Телеметрия объекта обязательна.",
+        "timeseries-or-attributes-required": "Телеметрия/атрибуты обязательны.",
+        "maximum-timeseries-or-attributes": "Максимальное количество параметров телеметрии или атрибутов равно {{count}}",
+        "alarm-fields-required": "Параметры оповещения обязательны.",
         "function-types": "Тип функции",
         "function-types-required": "Тип функции обязателен.",
-        "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }"
+        "maximum-function-types": "Максимальное количество типов функции равно {{count}}",
+        "time-description": "время текущего значения;",
+        "value-description": "текущее значение;",
+        "prev-value-description": "результат предыдущего вызова функции;",
+        "time-prev-description": "время предыдущего значения;",
+        "prev-orig-value-description": "исходное предыдущее значение;"
     },
     "datasource": {
         "type": "Тип источника данных",
+        "name": "Название",
         "add-datasource-prompt": "Пожалуйста, добавьте источник данных"
     },
     "details": {
@@ -537,7 +586,7 @@
         "create-new-alias": "Создать новый!",
         "create-new-key": "Создать новый!",
         "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.",
-        "configure-alias": "Конфигурировать '{{alias}}' псевдоним",
+        "configure-alias": "Настроить '{{alias}}' псевдоним",
         "no-devices-matching": "Устройство '{{entity}}' не найдено.",
         "alias": "Псевдоним",
         "alias-required": "Псевдоним устройства обязателен.",
@@ -565,9 +614,9 @@
         "assign-devices": "Присвоить устройство",
         "assign-devices-text": "Присвоить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } клиенту",
         "delete-devices": "Удалить устройства",
-        "unassign-from-customer": "Отменить присвоение клиенту",
-        "unassign-devices": "Отменить присвоение устройств",
-        "unassign-devices-action-title": "Отменить присвоение { count, plural, one {1 устройства} few {# устройств} other {# устройств} } клиенту",
+        "unassign-from-customer": "Отозвать у клиенту",
+        "unassign-devices": "Отозвать устройства",
+        "unassign-devices-action-title": "Отозвать у клиента { count, plural, one {1 устройство} few {# устройства} other {# устройств} }",
         "assign-new-device": "Присвоить новое устройство",
         "make-public-device-title": "Вы точно хотите открыть общий доступ к устройству '{{deviceName}}'?",
         "make-public-device-text": "После подтверждения устройство и все связанные с ним данные будут общедоступными.",
@@ -579,10 +628,10 @@
         "delete-devices-title": "Вы точно хотите удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?",
         "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } }",
         "delete-devices-text": "Внимание, после подтверждения выбранные устройства и все связанные с ними данные будут безвозвратно утеряны..",
-        "unassign-device-title": "Вы точно хотите отменить присвоение устройства '{{deviceName}}'?",
+        "unassign-device-title": "Вы точно хотите отозвать устройство '{{deviceName}}'?",
         "unassign-device-text": "После подтверждения устройство будет недоступно клиенту.",
-        "unassign-device": "Отменить присвоение устройства",
-        "unassign-devices-title": "Вы точно хотите отменить присвоение { count, plural, one {1 устройство} few {# устройства} other {# устройств} } }?",
+        "unassign-device": "Отозвать устройство",
+        "unassign-devices-title": "Вы точно хотите отозвать { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?",
         "unassign-devices-text": "После подтверждения выбранные устройства будут недоступны клиенту.",
         "device-credentials": "Учетные данные устройства",
         "credentials-type": "Тип учетных данных",
@@ -593,6 +642,14 @@
         "rsa-key-required": "Открытый ключ RSA обязателен.",
         "secret": "Секрет",
         "secret-required": "Секрет обязателен.",
+        "device-type": "Тип устройства",
+        "device-type-required": "Тип устройства обязатеен.",
+        "select-device-type": "Выберите тип устройства",
+        "enter-device-type": "Введите тип устройства",
+        "any-device": "Любое устройство",
+        "no-device-types-matching": "Тип устройства, соответствующий '{{entitySubtype}}', не найден.",
+        "device-type-list-empty": "Не выбран тип устройства.",
+        "device-types": "Типы устройств",
         "name": "Название",
         "name-required": "Название обязательно.",
         "description": "Описание",
@@ -607,7 +664,8 @@
         "unable-delete-device-alias-text": "Не удалось удалить псевдоним '{{deviceAlias}}' устройства, т.к. он используется следующими виджетами:<br/>{{widgetsList}}",
         "is-gateway": "Гейтвей",
         "public": "Общедоступный",
-        "device-public": "Устройство общедоступно"
+        "device-public": "Устройство общедоступно",
+        "select-device": "Выбрать устройство"
     },
     "dialog": {
         "close": "Закрыть диалог"
@@ -618,100 +676,200 @@
         "unknown-error": "Неизвестная ошибка"
     },
     "entity": {
-        "entity": "Entity",
-        "entities": "Entities",
-        "aliases": "Entity aliases",
-        "entity-alias": "Entity alias",
-        "unable-delete-entity-alias-title": "Unable to delete entity alias",
-        "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
-        "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
-        "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
-        "configure-alias": "Configure '{{alias}}' alias",
-        "alias": "Alias",
-        "alias-required": "Entity alias is required.",
-        "remove-alias": "Remove entity alias",
-        "add-alias": "Add entity alias",
-        "entity-list": "Entity list",
-        "entity-type": "Entity type",
-        "entity-types": "Entity types",
-        "entity-type-list": "Entity type list",
-        "any-entity": "Any entity",
-        "enter-entity-type": "Enter entity type",
-        "no-entities-matching": "No entities matching '{{entity}}' were found.",
-        "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
-        "name-starts-with": "Name starts with",
-        "use-entity-name-filter": "Use filter",
-        "entity-list-empty": "No entities selected.",
-        "entity-type-list-empty": "No entity types selected.",
-        "entity-name-filter-required": "Entity name filter is required.",
-        "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
-        "all-subtypes": "All",
-        "select-entities": "Select entities",
-        "no-aliases-found": "No aliases found.",
-        "no-alias-matching": "'{{alias}}' not found.",
-        "create-new-alias": "Create a new one!",
-        "key": "Key",
-        "key-name": "Key name",
-        "no-keys-found": "No keys found.",
-        "no-key-matching": "'{{key}}' not found.",
-        "create-new-key": "Create a new one!",
-        "type": "Type",
-        "type-required": "Entity type is required.",
-        "type-device": "Device",
-        "type-devices": "Devices",
-        "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }",
-        "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
-        "type-asset": "Asset",
-        "type-assets": "Assets",
-        "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }",
-        "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
-        "type-rule": "Rule",
-        "type-rules": "Rules",
-        "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }",
-        "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
-        "type-plugin": "Plugin",
-        "type-plugins": "Plugins",
-        "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }",
-        "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
-        "type-tenant": "Tenant",
-        "type-tenants": "Tenants",
-        "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }",
-        "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
-        "type-customer": "Customer",
-        "type-customers": "Customers",
-        "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }",
-        "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
-        "type-user": "User",
-        "type-users": "Users",
-        "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }",
-        "user-name-starts-with": "Users whose names start with '{{prefix}}'",
-        "type-dashboard": "Dashboard",
-        "type-dashboards": "Dashboards",
-        "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }",
-        "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
-        "type-alarm": "Alarm",
-        "type-alarms": "Alarms",
-        "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }",
-        "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
-        "type-rulechain": "Rule chain",
-        "type-rulechains": "Rule chains",
-        "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }",
-        "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
-        "type-current-customer": "Current Customer",
-        "search": "Search entities",
-        "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
-        "entity-name": "Entity name",
-        "details": "Entity details",
-        "no-entities-prompt": "No entities found",
-        "no-data": "No data to display"
+        "entity": "Объект",
+        "entities": "Объекты",
+        "aliases": "Псевдонимы объекта",
+        "entity-alias": "Псевдоним объекта",
+        "unable-delete-entity-alias-title": "Не удалось удалить псевдоним объекта",
+        "unable-delete-entity-alias-text": "Псевдоним объекта '{{entityAlias}}' не может быть удален, так как используется следующим(и) виджетом(ами):<br/>{{widgetsList}}",
+        "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.<br>В рамках одного дашборда псевдонимы объектов должны быть уникальными.",
+        "missing-entity-filter-error": "Отсутствует фильтр для псевдонима '{{alias}}'.",
+        "configure-alias": "Настроить псевдоним '{{alias}}'",
+        "alias": "Псевдоним",
+        "alias-required": "Псевдоним объекта обязателен.",
+        "remove-alias": "Убрать псевдоним объекта",
+        "add-alias": "Добавить псевдоним объекта",
+        "entity-list": "Список объектов",
+        "entity-type": "Тип объекта",
+        "entity-types": "Типы объекта",
+        "entity-type-list": "Список типов объекта",
+        "any-entity": "Любой объект",
+        "enter-entity-type": "Введите тип объекта",
+        "no-entities-matching": "Объекты, соответствующие '{{entity}}', не найдены.",
+        "no-entity-types-matching": "Типы объектов, соответствующие '{{entityType}}', не найдены.",
+        "name-starts-with": "Название, начинающееся с",
+        "use-entity-name-filter": "Использовать фильтр",
+        "entity-list-empty": "Не выбраны объекты.",
+        "entity-type-list-empty": "Не выбраны типы объекта.",
+        "entity-name-filter-required": "Фильтр по названию объекта обязателен.",
+        "entity-name-filter-no-entity-matched": "Объекты, чье название начинается с '{{entity}}', не найдены.",
+        "all-subtypes": "Все",
+        "select-entities": "Выберите объекты",
+        "no-aliases-found": "Псевдонимы не найдены.",
+        "no-alias-matching": "Псевдоним '{{alias}}' не найден.",
+        "create-new-alias": "Создать новый!",
+        "key": "Ключ",
+        "key-name": "Название ключа",
+        "no-keys-found": "Ключ не найден.",
+        "no-key-matching": "Ключ '{{key}}' не найден.",
+        "create-new-key": "Создать новый!",
+        "type": "Тип",
+        "type-required": "Тип объекта обязателен.",
+        "type-device": "Устройство",
+        "type-devices": "Устройства",
+        "list-of-devices": "{ count, plural, 1 {Одно устройство} other {Список из # устройств} }",
+        "device-name-starts-with": "Устройства, чьи название начинается с '{{prefix}}'",
+        "type-asset": "Актив",
+        "type-assets": "Активы",
+        "list-of-assets": "{ count, plural, 1 {Один актив} other {Список из # активов} }",
+        "asset-name-starts-with": "Активы, чьи название начинается с '{{prefix}}'",
+        "type-entity-view": "Представление Объекта",
+        "type-entity-views": "Представления Объекта",
+        "list-of-entity-views": "{ count, plural, 1 {Одно представление объекта} other {Список из # представлений объекта} }",
+        "entity-view-name-starts-with": "Представления Объекта, чьи название начинается с '{{prefix}}'",
+        "type-rule": "Правило",
+        "type-rules": "Правила",
+        "list-of-rules": "{ count, plural, 1 {Одно правило} other {Список из # правил} }",
+        "rule-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'",
+        "type-plugin": "Плагин",
+        "type-plugins": "Плагины",
+        "list-of-plugins": "{ count, plural, 1 {Один плагин} other {Список из # плагинов} }",
+        "plugin-name-starts-with": "Плагины, чьи имена начинаются с '{{prefix}}'",
+        "type-tenant": "Владелец",
+        "type-tenants": "Владельцы",
+        "list-of-tenants": "{ count, plural, 1 {Один владелец} other {Список из # владельцев} }",
+        "tenant-name-starts-with": "Владельцы, чьи имена начинаются с '{{prefix}}'",
+        "type-customer": "Клиент",
+        "type-customers": "Клиенты",
+        "list-of-customers": "{ count, plural, 1 {Один клиент} other {Список из # клиентов} }",
+        "customer-name-starts-with": "Клиенты, чьи имена начинаются с '{{prefix}}'",
+        "type-user": "Пользователь",
+        "type-users": "Пользователи",
+        "list-of-users": "{ count, plural, 1 {Один пользователь} other {Список из # пользователей} }",
+        "user-name-starts-with": "Пользователи, чьи имена начинаются с '{{prefix}}'",
+        "type-dashboard": "Дашборд",
+        "type-dashboards": "Дашборды",
+        "list-of-dashboards": "{ count, plural, 1 {Один дашборд} other {Список из # дашбордов} }",
+        "dashboard-name-starts-with": "Дашборды, чьи названия начинаются с '{{prefix}}'",
+        "type-alarm": "Оповещение",
+        "type-alarms": "Оповещения",
+        "list-of-alarms": "{ count, plural, 1 {Одно оповещение} other {Список из # оповещений} }",
+        "alarm-name-starts-with": "Оповещения, чьи названия начинаются с '{{prefix}}'",
+        "type-rulechain": "Цепочка правил",
+        "type-rulechains": "Цепочки правил",
+        "list-of-rulechains": "{ count, plural, 1 {Одна цепочка правил} other {Список из # цепочек правил} }",
+        "rulechain-name-starts-with": "Цепочки правил, чьи названия начинаются с '{{prefix}}'",
+        "type-rulenode": "Правило",
+        "type-rulenodes": "Правила",
+        "list-of-rulenodes": "{ count, plural, 1 {Одно правило} other {Список из # правил} }",
+        "rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'",
+        "type-current-customer": "Текущий клиент",
+        "search": "Поиск объектов",
+        "selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }",
+        "entity-name": "Название объекта",
+        "details": "Подробности об объекте",
+        "no-entities-prompt": "Объекты не найдены",
+        "no-data": "Нет данных для отображения",
+        "columns-to-display": "Отобразить следующие колонки"
+    },
+    "entity-view": {
+        "entity-view": "Представление Объекта",
+        "entity-view-required": "Представление объекта обязательно.",
+        "entity-views": "Представления Объектов",
+        "management": "Управление представлениями объектов",
+        "view-entity-views": "Просмотр представлений объектов",
+        "entity-view-alias": "Псевдоним Представления Объекта",
+        "aliases": "Псевдонимы Представления Объекта",
+        "no-alias-matching": "Псевдоним '{{alias}}' не найден.",
+        "no-aliases-found": "Псевдонимы не найдены.",
+        "no-key-matching": "Ключ '{{key}}' не найден.",
+        "no-keys-found": "Ключи не найдены.",
+        "create-new-alias": "Создать новый!",
+        "create-new-key": "Создать новый!",
+        "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.<br>В рамках одного дашборда псевдонимы представлений объектов должны быть уникальными.",
+        "configure-alias": "Настроить псевдоним '{{alias}}'",
+        "no-entity-views-matching": "Объекты, соответствующие '{{entity}}', не найдены.",
+        "alias": "Псевдонимы",
+        "alias-required": "Псевдоним представления объекта обязателен.",
+        "remove-alias": "Убрать псевдоним представления объекта",
+        "add-alias": "Добавить псевдоним представления объекта",
+        "name-starts-with": "Представления объектов, чьи название начинается с",
+        "entity-view-list": "Список представлений объектов",
+        "use-entity-view-name-filter": "Использовать фильтр",
+        "entity-view-list-empty": "Не выбраны представления объектов.",
+        "entity-view-name-filter-required": "Для представлений объектов фильтр по названиям обязателен.",
+        "entity-view-name-filter-no-entity-view-matched": "Представление объектов, чьи название начинаются с '{{entityView}}', не найдены.",
+        "add": "Представление объекта",
+        "assign-to-customer": "Назначить клиенту",
+        "assign-entity-view-to-customer": "Назначить представление(я) объекта(ов) клиенту",
+        "assign-entity-view-to-customer-text": "Пожалуйста, выберите представления объектов, которые нужно назначить клиенту",
+        "no-entity-views-text": "Представления объектов не найдены",
+        "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно назначить представления объектов",
+        "entity-view-details": "Подробности о представлении объекта",
+        "add-entity-view-text": "Добавить новое представление объекта",
+        "delete": "Удалить представление объекта",
+        "assign-entity-views": "Назначить представления объектов",
+        "assign-entity-views-text": "Назначить клиенту { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }",
+        "delete-entity-views": "Удалить представления объектов",
+        "unassign-from-customer": "Отозвать у клиента",
+        "unassign-entity-views": "Отозвать представления объектов",
+        "unassign-entity-views-action-title": "Отозвать у клиента { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }",
+        "assign-new-entity-view": "Назначит новое представление объекта",
+        "delete-entity-view-title": "Вы точно хотите удалить представление объекта '{{entityViewName}}'?",
+        "delete-entity-view-text": "Внимание, после подтверждения представление объекта и все связанные с ним данные будут безвозвратно удалены.",
+        "delete-entity-views-title": "Вы точно хотите удалить { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?",
+        "delete-entity-views-action-title": "Удалить { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }",
+        "delete-entity-views-text": "Внимание, после подтверждения выбранные представления объектов и все связанные с ними данные будут безвозвратно удалены.",
+        "unassign-entity-view-title": "Вы точно хотите отозвать представление объекта '{{entityViewName}}'?",
+        "unassign-entity-view-text": "После подтверждение представление объекта будет недоступно клиенту.",
+        "unassign-entity-view": "Отозвать представление объекта",
+        "unassign-entity-views-title": "Вы точно хотите отозвать { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?",
+        "unassign-entity-views-text": "После подтверждение выбранные представления объектов будет недоступно клиенту.",
+        "entity-view-type": "Тип представления объекта",
+        "entity-view-type-required": "Тип представления объекта обязателен.",
+        "select-entity-view-type": "Выберите тип представления объекта",
+        "enter-entity-view-type": "Введите тип представления объекта",
+        "any-entity-view": "Любое представление объекта",
+        "no-entity-view-types-matching": "Типы представления объекта, соответствующие '{{entitySubtype}}', не найдены.",
+        "entity-view-type-list-empty": "Не выбраны типы представления объекта.",
+        "entity-view-types": "Типы представления объекта",
+        "name": "Название",
+        "name-required": "Название обязательно.",
+        "description": "Описание",
+        "events": "События",
+        "details": "Подробности",
+        "copyId": "Копировать ИД представление объекта",
+        "assignedToCustomer": "Назначенные клиенту",
+        "unable-entity-view-device-alias-title": "Не удалось удалить псевдоним представления объекта",
+        "unable-entity-view-device-alias-text": "Не удалось удалить псевдоним устройства '{{entityViewAlias}}', т.к. он используется следующими виджетами:<br/>{{widgetsList}}",
+        "select-entity-view": "Выбрать представление объекта",
+        "make-public": "Открыть общий доступ к представлению объекта",
+        "start-date": "Дата начала",
+        "start-ts": "Время начала",
+        "end-date": "Дата окончания",
+        "end-ts": "Время окончания",
+        "date-limits": "Временной лимит",
+        "client-attributes": "Клиентские атрибуты",
+        "shared-attributes": "Общие атрибуты",
+        "server-attributes": "Серверные атрибуты",
+        "timeseries": "Телеметрия",
+        "client-attributes-placeholder": "Клиентские атрибуты",
+        "shared-attributes-placeholder": "Общие атрибуты",
+        "server-attributes-placeholder": "Серверные атрибуты",
+        "timeseries-placeholder": "Телеметрия",
+        "target-entity": "Целевой объект",
+        "attributes-propagation": "Пробросить атрибуты",
+        "attributes-propagation-hint": "Представление объекта автоматически копирует указанные атрибуты с Целевого Объекта каждый раз, когда вы сохраняете или обновляете это представление. В целях производительности атрибуты целевого объекта не пробрасываются в представление объекта на каждом их изменении. Вы можете включить автоматический проброс, настроив в вашей цепочке правило \"copy to view\" и соединив его с сообщениями типа \"Post attributes\" и \"Attributes Updated\".",
+        "timeseries-data": "Данные телеметрии",
+        "timeseries-data-hint": "Настроить ключи данных телеметрии целевого объекта, которые будут доступны представлению объекта. Эти данные только для чтения."
     },
     "event": {
         "event-type": "Тип события",
         "type-error": "Ошибка",
         "type-lc-event": "Событие жизненного цикла",
         "type-stats": "Статистика",
-        "type-debug-rule-node": "Debug",
-        "type-debug-rule-chain": "Debug",
+        "type-debug-rule-node": "Отладка",
+        "type-debug-rule-chain": "Отладка",
         "no-events-prompt": "События не найдены",
         "error": "Ошибка",
         "alarm": "Аварийное оповещение",
@@ -719,14 +877,14 @@
         "server": "Сервер",
         "body": "Тело",
         "method": "Метод",
-        "type": "Type",
-        "entity": "Entity",
-        "message-id": "Message Id",
-        "message-type": "Message Type",
-        "data-type": "Data Type",
-        "relation-type": "Relation Type",
-        "metadata": "Metadata",
-        "data": "Data",
+        "type": "Тип",
+        "entity": "Объект",
+        "message-id": "ИД сообщения",
+        "message-type": "Тип сообщения",
+        "data-type": "Тип данных",
+        "relation-type": "Тип отношения",
+        "metadata": "Метаданные",
+        "data": "Данные",
         "event": "Событие",
         "status": "Статус",
         "success": "Успех",
@@ -735,161 +893,161 @@
         "errors-occurred": "Возникли ошибки"
     },
     "extension": {
-        "extensions": "Extensions",
-        "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selected",
-        "type": "Type",
-        "key": "Key",
-        "value": "Value",
-        "id": "Id",
-        "extension-id": "Extension id",
-        "extension-type": "Extension type",
+        "extensions": "Расширение",
+        "selected-extensions": "Выбрано { count, plural, 1 {1 расширение} few {# расширения} other {# расширений} }",
+        "type": "Тип",
+        "key": "Ключ",
+        "value": "Значение",
+        "id": "ИД",
+        "extension-id": "ИД расширения",
+        "extension-type": "Тип расширения",
         "transformer-json": "JSON *",
-        "unique-id-required": "Current extension id already exists.",
-        "delete": "Delete extension",
-        "add": "Add extension",
-        "edit": "Edit extension",
-        "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
-        "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
-        "delete-extensions-title": "Are you sure you want to delete { count, plural, 1 {1 extension} other {# extensions} }?",
-        "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
-        "converters": "Converters",
-        "converter-id": "Converter id",
-        "configuration": "Configuration",
-        "converter-configurations": "Converter configurations",
-        "token": "Security token",
-        "add-converter": "Add converter",
-        "add-config": "Add converter configuration",
-        "device-name-expression": "Device name expression",
-        "device-type-expression": "Device type expression",
-        "custom": "Custom",
+        "unique-id-required": "Такое ИД расширения уже существует.",
+        "delete": "Удалить расширение",
+        "add": "Добавить расширение",
+        "edit": "Редактировать расширение",
+        "delete-extension-title": "Вы точно хотите удалить расширение '{{extensionId}}'?",
+        "delete-extension-text": "Внимание, после подтверждения расширение и все связанные с ним данные будут безвозвратно удалены.",
+        "delete-extensions-title": "Вы точно хотите удалить { count, plural, 1 {1 расширение} few {# расширения} other {# расширений} }?",
+        "delete-extensions-text": "Внимание, после подтверждения выбранные расширения будут удалены.",
+        "converters": "Конвертеры",
+        "converter-id": "ИД конвертера",
+        "configuration": "Конфигурация",
+        "converter-configurations": "Конфигурация конвертера",
+        "token": "Токен безопасности",
+        "add-converter": "Добавить конвертер",
+        "add-config": "Добавить конфигурацию конвертера",
+        "device-name-expression": "Маска названия устройства",
+        "device-type-expression": "Маска типа устройства",
+        "custom": "Пользовательский",
         "to-double": "To Double",
-        "transformer": "Transformer",
-        "json-required": "Transformer json is required.",
-        "json-parse": "Unable to parse transformer json.",
-        "attributes": "Attributes",
-        "add-attribute": "Add attribute",
-        "add-map": "Add mapping element",
-        "timeseries": "Timeseries",
-        "add-timeseries": "Add timeseries",
-        "field-required": "Field is required",
-        "brokers": "Brokers",
-        "add-broker": "Add broker",
-        "host": "Host",
-        "port": "Port",
-        "port-range": "Port should be in a range from 1 to 65535.",
-        "ssl": "Ssl",
-        "credentials": "Credentials",
-        "username": "Username",
-        "password": "Password",
-        "retry-interval": "Retry interval in milliseconds",
-        "anonymous": "Anonymous",
-        "basic": "Basic",
+        "transformer": "Преобразователь",
+        "json-required": "JSON преобразователя обязателен.",
+        "json-parse": "Не удалось распознать JSON преобразователя.",
+        "attributes": "Атрибуты",
+        "add-attribute": "Добавить атрибут",
+        "add-map": "Добавить элемент сопоставления",
+        "timeseries": "Телеметрия",
+        "add-timeseries": "Добавить параметр телеметрии",
+        "field-required": "Параметр обязателен",
+        "brokers": "Брокеры",
+        "add-broker": "Добавить брокер",
+        "host": "Хост",
+        "port": "Порт",
+        "port-range": "Значение порта лежит в диапазоне от 1 до 65535.",
+        "ssl": "SSL",
+        "credentials": "Учетные данные",
+        "username": "Имя пользователя",
+        "password": "Пароль",
+        "retry-interval": "Интервал повтора в миллисекундах",
+        "anonymous": "Анонимный",
+        "basic": "Общий",
         "pem": "PEM",
-        "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.",
-        "mapping": "Mapping",
-        "topic-filter": "Topic filter",
-        "converter-type": "Converter type",
-        "converter-json": "Json",
-        "json-name-expression": "Device name json expression",
-        "topic-name-expression": "Device name topic expression",
-        "json-type-expression": "Device type json expression",
-        "topic-type-expression": "Device type topic expression",
-        "attribute-key-expression": "Attribute key expression",
-        "attr-json-key-expression": "Attribute key json expression",
-        "attr-topic-key-expression": "Attribute key topic expression",
-        "request-id-expression": "Request id expression",
-        "request-id-json-expression": "Request id json expression",
-        "request-id-topic-expression": "Request id topic expression",
-        "response-topic-expression": "Response topic expression",
-        "value-expression": "Value expression",
-        "topic": "Topic",
-        "timeout": "Timeout in milliseconds",
-        "converter-json-required": "Converter json is required.",
-        "converter-json-parse": "Unable to parse converter json.",
-        "filter-expression": "Filter expression",
-        "connect-requests": "Connect requests",
-        "add-connect-request": "Add connect request",
-        "disconnect-requests": "Disconnect requests",
-        "add-disconnect-request": "Add disconnect request",
-        "attribute-requests": "Attribute requests",
-        "add-attribute-request": "Add attribute request",
-        "attribute-updates": "Attribute updates",
-        "add-attribute-update": "Add attribute update",
-        "server-side-rpc": "Server side RPC",
-        "add-server-side-rpc-request": "Add server-side RPC request",
-        "device-name-filter": "Device name filter",
-        "attribute-filter": "Attribute filter",
-        "method-filter": "Method filter",
-        "request-topic-expression": "Request topic expression",
-        "response-timeout": "Response timeout in milliseconds",
-        "topic-expression": "Topic expression",
-        "client-scope": "Client scope",
-        "add-device": "Add device",
-        "opc-server": "Servers",
-        "opc-add-server": "Add server",
-        "opc-add-server-prompt": "Please add server",
-        "opc-application-name": "Application name",
-        "opc-application-uri": "Application uri",
-        "opc-scan-period-in-seconds": "Scan period in seconds",
-        "opc-security": "Security",
-        "opc-identity": "Identity",
-        "opc-keystore": "Keystore",
-        "opc-type": "Type",
-        "opc-keystore-type": "Type",
-        "opc-keystore-location": "Location *",
-        "opc-keystore-password": "Password",
-        "opc-keystore-alias": "Alias",
-        "opc-keystore-key-password": "Key password",
-        "opc-device-node-pattern": "Device node pattern",
-        "opc-device-name-pattern": "Device name pattern",
-        "modbus-server": "Servers/slaves",
-        "modbus-add-server": "Add server/slave",
-        "modbus-add-server-prompt": "Please add server/slave",
-        "modbus-transport": "Transport",
-        "modbus-port-name": "Serial port name",
-        "modbus-encoding": "Encoding",
-        "modbus-parity": "Parity",
-        "modbus-baudrate": "Baud rate",
-        "modbus-databits": "Data bits",
-        "modbus-stopbits": "Stop bits",
-        "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
-        "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
-        "modbus-unit-id": "Unit ID",
-        "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
-        "modbus-device-name": "Device name",
-        "modbus-poll-period": "Poll period (ms)",
-        "modbus-attributes-poll-period": "Attributes poll period (ms)",
-        "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
-        "modbus-poll-period-range": "Poll period should be positive value.",
-        "modbus-tag": "Tag",
-        "modbus-function": "Function",
-        "modbus-register-address": "Register address",
-        "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
-        "modbus-register-bit-index": "Bit index",
-        "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
-        "modbus-register-count": "Register count",
-        "modbus-register-count-range": "Register count should be a positive value.",
-        "modbus-byte-order": "Byte order",
+        "ca-cert": "Файл CA сертификата *",
+        "private-key": "Файл приватного ключа *",
+        "cert": "Файл сертификата *",
+        "no-file": "Не выбран файл.",
+        "drop-file": "Перетяните файл или нажмите для выбора файла.",
+        "mapping": "Сопоставление",
+        "topic-filter": "Фильтр тем",
+        "converter-type": "Тип конвертера",
+        "converter-json": "JSON",
+        "json-name-expression": "JSON выражение для названия устройства",
+        "topic-name-expression": "Выражение для названия устройства в названии темы",
+        "json-type-expression": "JSON выражение для типа устройства",
+        "topic-type-expression": "Выражение для типа устройства в названии темы",
+        "attribute-key-expression": "Выражение для атрибута",
+        "attr-json-key-expression": "JSON выражение для атрибута",
+        "attr-topic-key-expression": "Выражение для атрибута в названии темы",
+        "request-id-expression": "Выражение для ИД запроса",
+        "request-id-json-expression": "JSON выражение для ИД запроса",
+        "request-id-topic-expression": "Выражение для ИД запроса в названии темы",
+        "response-topic-expression": "Выражение для темы ответов",
+        "value-expression": "Выражение для значения",
+        "topic": "Тема",
+        "timeout": "Таймаут в миллисекундах",
+        "converter-json-required": "JSON конвертер обязателен.",
+        "converter-json-parse": "Не удалось распознать JSON конвертера.",
+        "filter-expression": "Выражение для фильтрации",
+        "connect-requests": "Запросы о подключении устройства",
+        "add-connect-request": "Добавить запросы о подключении устройства",
+        "disconnect-requests": "Запросы об отсоединении устройства",
+        "add-disconnect-request": "Добавить запрос об отсоединении устройства",
+        "attribute-requests": "Запросы для атрибутов",
+        "add-attribute-request": "Добавить запрос для атрибутов",
+        "attribute-updates": "Обновление атрибутов",
+        "add-attribute-update": "Добавить обновление атрибутов",
+        "server-side-rpc": "Серверный RPC",
+        "add-server-side-rpc-request": "Добавить серверный RPC",
+        "device-name-filter": "Фильтр для названия устройства",
+        "attribute-filter": "Фильтр для атрибутов",
+        "method-filter": "Фильтр для процедур",
+        "request-topic-expression": "Выражение для темы запросов",
+        "response-timeout": "Время ожидания ответа в миллисекундах",
+        "topic-expression": "Выражение для названия темы",
+        "client-scope": "Клиентский",
+        "add-device": "Добавить устройство",
+        "opc-server": "Серверы",
+        "opc-add-server": "Добавить сервер",
+        "opc-add-server-prompt": "Пожалуйста, добавьте сервер",
+        "opc-application-name": "Название приложения",
+        "opc-application-uri": "URI приложения",
+        "opc-scan-period-in-seconds": "Частота сканирования в секундах",
+        "opc-security": "Безопасность",
+        "opc-identity": "Идентификация",
+        "opc-keystore": "Хранилище ключей",
+        "opc-type": "Тип",
+        "opc-keystore-type": "Тип",
+        "opc-keystore-location": "Расположение *",
+        "opc-keystore-password": "Пароль",
+        "opc-keystore-alias": "Псевдоним",
+        "opc-keystore-key-password": "Пароль для ключ",
+        "opc-device-node-pattern": "Паттерн OPC узла устройства",
+        "opc-device-name-pattern": "Паттерн названия устройства",
+        "modbus-server": "Серверы/ведомые устройства",
+        "modbus-add-server": "Добавить сервер/ведомое устройство",
+        "modbus-add-server-prompt": "Пожалуйста, добавить сервер/ведомое устройство",
+        "modbus-transport": "Транспорт",
+        "modbus-port-name": "Название последовательного порта",
+        "modbus-encoding": "Кодирование символов",
+        "modbus-parity": "Паритет",
+        "modbus-baudrate": "Скорость передачи",
+        "modbus-databits": "Биты данных",
+        "modbus-stopbits": "Стоп-биты",
+        "modbus-databits-range": "Параметр \"Биты данных\" может принимать значения 7 или 8.",
+        "modbus-stopbits-range": "Параметр \"Стоп-биты\" может принимать значения 1 или 2.",
+        "modbus-unit-id": "ИД устройства",
+        "modbus-unit-id-range": "ИД устройства должен быть в диапазоне от 1 до 247.",
+        "modbus-device-name": "Название устройства",
+        "modbus-poll-period": "Частота опроса (в миллисекундах)",
+        "modbus-attributes-poll-period": "Частота опроса для атрибутов (в миллисекундах)",
+        "modbus-timeseries-poll-period": "Частота опроса для телеметрии (в миллисекундах)",
+        "modbus-poll-period-range": "Значение параметра \"Частота опроса\" должно быть больше ноля.",
+        "modbus-tag": "Тег",
+        "modbus-function": "Modbus функция",
+        "modbus-register-address": "Адрес регистра",
+        "modbus-register-address-range": "Адрес регистра должен быть в диапазоне от 0 до 65535.",
+        "modbus-register-bit-index": "Номер бита",
+        "modbus-register-bit-index-range": "Номер бита должен быть в диапазоне от 0 до 15.",
+        "modbus-register-count": "Количество регистров",
+        "modbus-register-count-range": "Количество регистров должно быть больше ноля.",
+        "modbus-byte-order": "Порядок байтов",
 
         "sync": {
-            "status": "Status",
-            "sync": "Sync",
-            "not-sync": "Not sync",
-            "last-sync-time": "Last sync time",
-            "not-available": "Not available"
+            "status": "Статус",
+            "sync": "Синхронизирован",
+            "not-sync": "Не синхронизирован",
+            "last-sync-time": "Время последней синхронизации",
+            "not-available": "Не доступен"
         },
 
-        "export-extensions-configuration": "Export extensions configuration",
-        "import-extensions-configuration": "Import extensions configuration",
-        "import-extensions": "Import extensions",
-        "import-extension": "Import extension",
-        "export-extension": "Export extension",
-        "file": "Extensions file",
-        "invalid-file-error": "Invalid extension file"
+        "export-extensions-configuration": "Экспортировать конфигурацию расширений",
+        "import-extensions-configuration": "Импортировать конфигурацию расширений",
+        "import-extensions": "Импортировать расширения",
+        "import-extension": "Импортировать расширение",
+        "export-extension": "Экспортировать расширение",
+        "file": "Файл расширений",
+        "invalid-file-error": "Не правильный формат файла"
     },
     "fullscreen": {
         "expand": "Во весь экран",
@@ -937,20 +1095,20 @@
         "tidy": "Tidy"
     },
     "key-val": {
-        "key": "Key",
-        "value": "Value",
-        "remove-entry": "Remove entry",
-        "add-entry": "Add entry",
-        "no-data": "No entries"
+        "key": "Ключ",
+        "value": "Значение",
+        "remove-entry": "Удалить элемент",
+        "add-entry": "Добавить элемент",
+        "no-data": "Элементы отсутствуют"
     },
     "layout": {
-        "layout": "Layout",
-        "manage": "Manage layouts",
-        "settings": "Layout settings",
-        "color": "Color",
-        "main": "Main",
-        "right": "Right",
-        "select": "Select target layout"
+        "layout": "Макет",
+        "manage": "Управление макетами",
+        "settings": "Настройки макета",
+        "color": "Цвет",
+        "main": "Основной",
+        "right": "Правый",
+        "select": "Выбрать макет"
     },
     "legend": {
         "position": "Расположение легенды",
@@ -993,140 +1151,149 @@
         "current-password": "Текущий пароль"
     },
     "relation": {
-        "relations": "Relations",
-        "direction": "Direction",
+        "relations": "Отношения",
+        "direction": "Направления",
         "search-direction": {
-            "FROM": "From",
-            "TO": "To"
+            "FROM": "От",
+            "TO": "К"
         },
         "direction-type": {
-            "FROM": "from",
-            "TO": "to"
+            "FROM": "от",
+            "TO": "к"
         },
-        "from-relations": "Outbound relations",
-        "to-relations": "Inbound relations",
-        "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected",
-        "type": "Type",
-        "to-entity-type": "To entity type",
-        "to-entity-name": "To entity name",
-        "from-entity-type": "From entity type",
-        "from-entity-name": "From entity name",
-        "to-entity": "To entity",
-        "from-entity": "From entity",
-        "delete": "Delete relation",
-        "relation-type": "Relation type",
-        "relation-type-required": "Relation type is required.",
-        "any-relation-type": "Any type",
-        "add": "Add relation",
-        "edit": "Edit relation",
-        "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
-        "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
-        "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?",
-        "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.",
-        "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
-        "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
-        "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?",
-        "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.",
-        "remove-relation-filter": "Remove relation filter",
-        "add-relation-filter": "Add relation filter",
-        "any-relation": "Any relation",
-        "relation-filters": "Relation filters",
-        "additional-info": "Additional info (JSON)",
-        "invalid-additional-info": "Unable to parse additional info json."
+        "from-relations": "Исходящие отношения",
+        "to-relations": "Входящие отношения",
+        "selected-relations": "Выбрано { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }",
+        "type": "Тип",
+        "to-entity-type": "К типу объекта",
+        "to-entity-name": "К объекта",
+        "from-entity-type": "От типа объекта",
+        "from-entity-name": "От объекта",
+        "to-entity": "К объекту",
+        "from-entity": "От объекта",
+        "delete": "Удалить отношение",
+        "relation-type": "Тип отношения",
+        "relation-type-required": "Тип отношения обязателен.",
+        "any-relation-type": "Любой тип",
+        "add": "Добавить отношение",
+        "edit": "Редактировать отношение",
+        "delete-to-relation-title": "Вы точно хотите удалить отношение, идущее к объекту '{{entityName}}'?",
+        "delete-to-relation-text": "Внимание, после подтверждения объект '{{entityName}}' будет отвязан от текущего объекта.",
+        "delete-to-relations-title": "Вы точно хотите удалить { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }?",
+        "delete-to-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от текущего объекта.",
+        "delete-from-relation-title": "Вы точно хотите удалить отношение, идущее от объекта '{{entityName}}'?",
+        "delete-from-relation-text": "Внимание, после подтверждения текущий объект будет отвязан от объекта '{{entityName}}'.",
+        "delete-from-relations-title": "Вы точно хотите удалить { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }?",
+        "delete-from-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от соответствующих объектов.",
+        "remove-relation-filter": "Удалить фильтр отношений",
+        "add-relation-filter": "Добавить фильтр отношений",
+        "any-relation": "Любое отношение",
+        "relation-filters": "Фильтры отношений",
+        "additional-info": "Дополнительная информация (JSON)",
+        "invalid-additional-info": "Не удалось распознать JSON с дополнительной информацией."
     },
     "rulechain": {
-        "rulechain": "Rule chain",
-        "rulechains": "Rule chains",
-        "root": "Root",
-        "delete": "Delete rule chain",
-        "name": "Name",
-        "name-required": "Name is required.",
-        "description": "Description",
-        "add": "Add Rule Chain",
-        "set-root": "Make rule chain root",
-        "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
-        "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
-        "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
-        "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
-        "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?",
-        "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }",
-        "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
-        "add-rulechain-text": "Add new rule chain",
-        "no-rulechains-text": "No rule chains found",
-        "rulechain-details": "Rule chain details",
-        "details": "Details",
-        "events": "Events",
-        "system": "System",
-        "import": "Import rule chain",
-        "export": "Export rule chain",
-        "export-failed-error": "Unable to export rule chain: {{error}}",
-        "create-new-rulechain": "Create new rule chain",
-        "rulechain-file": "Rule chain file",
-        "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
-        "copyId": "Copy rule chain Id",
-        "idCopiedMessage": "Rule chain Id has been copied to clipboard",
-        "select-rulechain": "Select rule chain",
-        "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
-        "rulechain-required": "Rule chain is required",
-        "management": "Rules management",
-        "debug-mode": "Debug mode"
+        "rulechain": "Цепочка правил",
+        "rulechains": "Цепочки правил",
+        "root": "Корневая",
+        "delete": "Удалить цепочку правил",
+        "name": "Названия",
+        "name-required": "Название необходимо.",
+        "description": "Описание",
+        "add": "Добавить цепочку правил",
+        "set-root": "Сделать цепочку корневой",
+        "set-root-rulechain-title": "Вы точно хотите сделать цепочку правил '{{ruleChainName}}' корневой?",
+        "set-root-rulechain-text": "После подтверждения цепочка правил станет корневой и будет обрабатывать все входящие сообщения.",
+        "delete-rulechain-title": "Вы точно хотите удалить цепочку правил '{{ruleChainName}}'?",
+        "delete-rulechain-text": "Внимание, после подтверждения цепочка правил и все связанные с ней данные будут безвозвратно удалены.",
+        "delete-rulechains-title": "Вы точно хотите удалить { count, plural, 1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }?",
+        "delete-rulechains-action-title": "Удалить { count, plural, 1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }",
+        "delete-rulechains-text": "Внимание, после подтверждения выбранные цепочки правил и все связанные с ними данные будут безвозвратно удалены.",
+        "add-rulechain-text": "Добавить новую цепочку правил",
+        "no-rulechains-text": "Цепочки правил не найдены",
+        "rulechain-details": "Подробности о цепочке правил",
+        "details": "Подробности",
+        "events": "События",
+        "system": "Системная",
+        "import": "Импортировать цепочку правил",
+        "export": "Экспортировать цепочку правил",
+        "export-failed-error": "Не удалось экспортировать цепочку правил: {{error}}",
+        "create-new-rulechain": "Создать новую цепочку правил",
+        "rulechain-file": "Файл цепочки правил",
+        "invalid-rulechain-file-error": "Не удалось импортировать цепочку правил: неправильный формат.",
+        "copyId": "Копировать ИД цепочки правил",
+        "idCopiedMessage": "ИД цепочки правил скопирован в буфер обмена",
+        "select-rulechain": "Выбрать цепочку правил",
+        "no-rulechains-matching": "Цепочки правил, соответствующие '{{entity}}', не найдены.",
+        "rulechain-required": "Цепочка правил обязательна",
+        "management": "Управление цепочками правил",
+        "debug-mode": "Режим отладки"
     },
     "rulenode": {
-        "details": "Details",
-        "events": "Events",
-        "search": "Search nodes",
-        "open-node-library": "Open node library",
-        "add": "Add rule node",
-        "name": "Name",
-        "name-required": "Name is required.",
-        "type": "Type",
-        "description": "Description",
-        "delete": "Delete rule node",
-        "select-all-objects": "Select all nodes and connections",
-        "deselect-all-objects": "Deselect all nodes and connections",
-        "delete-selected-objects": "Delete selected nodes and connections",
-        "delete-selected": "Delete selected",
-        "select-all": "Select all",
-        "copy-selected": "Copy selected",
-        "deselect-all": "Deselect all",
-        "rulenode-details": "Rule node details",
-        "debug-mode": "Debug mode",
-        "configuration": "Configuration",
-        "link": "Link",
-        "link-details": "Rule node link details",
-        "add-link": "Add link",
-        "link-label": "Link label",
-        "link-label-required": "Link label is required.",
-        "custom-link-label": "Custom link label",
-        "custom-link-label-required": "Custom link label is required.",
-        "type-filter": "Filter",
-        "type-filter-details": "Filter incoming messages with configured conditions",
-        "type-enrichment": "Enrichment",
-        "type-enrichment-details": "Add additional information into Message Metadata",
-        "type-transformation": "Transformation",
-        "type-transformation-details": "Change Message payload and Metadata",
-        "type-action": "Action",
-        "type-action-details": "Perform special action",
-        "type-external": "External",
-        "type-external-details": "Interacts with external system",
-        "type-rule-chain": "Rule Chain",
-        "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
-        "type-input": "Input",
-        "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
-        "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
-        "ui-resources-load-error": "Failed to load configuration ui resources.",
-        "invalid-target-rulechain": "Unable to resolve target rule chain!",
-        "test-script-function": "Test script function",
-        "message": "Message",
-        "message-type": "Message type",
-        "message-type-required": "Message type is required",
-        "metadata": "Metadata",
-        "metadata-required": "Metadata entries can't be empty.",
-        "output": "Output",
-        "test": "Test",
-        "help": "Help"
+        "details": "Подробности",
+        "events": "События",
+        "search": "Поиск правил",
+        "open-node-library": "Открыть библиотеку правил",
+        "add": "Добавить правило",
+        "name": "Название",
+        "name-required": "Название обязательно.",
+        "type": "Тип",
+        "description": "Описание",
+        "delete": "Удалить правило",
+        "select-all-objects": "Выделить все правила и связи",
+        "deselect-all-objects": "Отменить выделение правил и связей",
+        "delete-selected-objects": "Удалить выделенные правила и связи",
+        "delete-selected": "Удалить выделенные",
+        "select-all": "Выделить всё",
+        "copy-selected": "Копировать выделенное",
+        "deselect-all": "Отменить выделение",
+        "rulenode-details": "Подробности о правиле",
+        "debug-mode": "Режим отладки",
+        "configuration": "Настройки",
+        "link": "Связь",
+        "link-details": "Подробности о связи правила",
+        "add-link": "Добавить связь",
+        "link-label": "Метка связи",
+        "link-label-required": "Метка связи обязателен.",
+        "custom-link-label": "Пользовательская метка связи",
+        "custom-link-label-required": "Пользовательская метка связи обязателен.",
+        "link-labels": "Метки связи",
+        "link-labels-required": "Метки связи обязательны.",
+        "no-link-labels-found": "Метки связи не найдены",
+        "no-link-label-matching": "Метка '{{label}}' не найдена.",
+        "create-new-link-label": "Создать новую!",
+        "type-filter": "Фильтр",
+        "type-filter-details": "Фильтр входящих сообщений с заданными условиями",
+        "type-enrichment": "Насыщение",
+        "type-enrichment-details": "Добавить данные в метадату сообщения",
+        "type-transformation": "Преобразование",
+        "type-transformation-details": "Изменить содержимое сообщение и его метадату",
+        "type-action": "Действие",
+        "type-action-details": "Выполнить заданное действие",
+        "type-external": "Сторонние",
+        "type-external-details": "Взаимодействовать со сторонними системами",
+        "type-rule-chain": "Цепочка правил",
+        "type-rule-chain-details": "Перенаправить входящее сообщение в другую цепочку правил",
+        "type-input": "Вход",
+        "type-input-details": "Логический вход цепочки правил перенаправляет входящие сообщения в следующее правило",
+        "type-unknown": "Неизвестный",
+        "type-unknown-details": "Неопределенное правило",
+        "directive-is-not-loaded": "Указанная директива конфигурации '{{directiveName}}' не доступна.",
+        "ui-resources-load-error": "Не удалось загрузить UI ресурсы.",
+        "invalid-target-rulechain": "Не удалось определить целевую цепочку правил!",
+        "test-script-function": "Протестировать скрипт",
+        "message": "Сообщение",
+        "message-type": "Тип сообщения",
+        "select-message-type": "Выбрать тип сообщения",
+        "message-type-required": "Тип сообщения обязателен",
+        "metadata": "Метаданные",
+        "metadata-required": "Метаданные объекта не могут быть пустыми.",
+        "output": "Выход",
+        "test": "Протестировать",
+        "help": "Помощь"
     },
     "tenant": {
+        "tenant": "Владелец",
         "tenants": "Владельцы",
         "management": "Управление владельцами",
         "add": "Добавить владельца",
@@ -1143,7 +1310,14 @@
         "delete-tenants-text": "Внимание, после подтверждения выбранные Владельцы и все связанные с ними данные будут безвозвратно утеряны.",
         "title": "Имя",
         "title-required": "Имя обязательно.",
-        "description": "Описание"
+        "description": "Описание",
+        "details": "Подробности",
+        "events": "События",
+        "copyId": "Копировать ИД владельца",
+        "idCopiedMessage": "ИД владельца скопирован в буфер обмена",
+        "select-tenant": "Выбрать владельца",
+        "no-tenants-matching": "Владельцы, соответствующие '{{entity}}', не найдены.",
+        "tenant-required": "Владелец обязателен"
     },
     "timeinterval": {
         "seconds-interval": "{ seconds, plural, one {1 секунда} few {# секунды} other {# секунд} }",
@@ -1171,6 +1345,7 @@
         "time-period": "Период времени"
     },
     "user": {
+        "user": "Пользователь",
         "users": "Пользователи",
         "customer-users": "Пользователи клиента",
         "tenant-admins": "Администраторы владельца",
@@ -1192,11 +1367,25 @@
         "resend-activation": "Повторить отправку активационного письма",
         "email": "Эл. адрес",
         "email-required": "Эл. адрес обязателен.",
+        "invalid-email-format": "Не правильный формат письма.",
         "first-name": "Имя",
         "last-name": "Фамилия",
         "description": "Описание",
         "default-dashboard": "Дашборд по умолчанию",
-        "always-fullscreen": "Всегда во весь экран"
+        "always-fullscreen": "Всегда в полноэкранном режиме",
+        "select-user": "Выбрать пользователя",
+        "no-users-matching": "Пользователи, соответствующие '{{entity}}', не найдены.",
+        "user-required": "Пользователь обязателен",
+        "activation-method": "Метод активации",
+        "display-activation-link": "Отобразить ссылку для активации",
+        "send-activation-mail": "Отправить активационное письмо",
+        "activation-link": "Активационная ссылка для пользователя",
+        "activation-link-text": "Для активации пользователя используйте <a href='{{activationLink}}' target='_blank'>ссылку</a> :",
+        "copy-activation-link": "Копировать активационную ссылку",
+        "activation-link-copied-message": "Ссылка для активации пользователя скопировано в буфер обмена",
+        "details": "Подробности",
+        "login-as-tenant-admin": "Войти как администратор владельца",
+        "login-as-customer-user": "Войти как пользователь клиента"
     },
     "value": {
         "type": "Тип значения",
@@ -1210,7 +1399,8 @@
         "boolean": "Логический тип",
         "boolean-value": "Логическое значение",
         "false": "Ложь",
-        "true": "Правда"
+        "true": "Правда",
+        "long": "Целое число"
     },
     "widget": {
         "widget-library": "Галерея виджетов",
@@ -1225,8 +1415,11 @@
         "remove-widget-title": "Вы точно хотите удалить виджет '{{widgetTitle}}'?",
         "remove-widget-text": "Внимание, после подтверждения виджет и все связанные с ним данные будут безвозвратно утеряны.",
         "timeseries": "Выборка по времени",
+        "search-data": "Search data",
+        "no-data-found": "No data found",
         "latest-values": "Последние значения",
         "rpc": "Управляющий виджет",
+        "alarm": "Alarm widget",
         "static": "Статический виджет",
         "select-widget-type": "Выберите тип виджета",
         "missing-widget-title-error": "Укажите название виджета!",
@@ -1262,16 +1455,16 @@
         "export": "Экспортировать виджет"
     },
     "widget-action": {
-        "header-button": "Widget header button",
-        "open-dashboard-state": "Navigate to new dashboard state",
-        "update-dashboard-state": "Update current dashboard state",
-        "open-dashboard": "Navigate to other dashboard",
-        "custom": "Custom action",
-        "target-dashboard-state": "Target dashboard state",
-        "target-dashboard-state-required": "Target dashboard state is required",
-        "set-entity-from-widget": "Set entity from widget",
-        "target-dashboard": "Target dashboard",
-        "open-right-layout": "Open right dashboard layout (mobile view)"
+        "header-button": "Кнопка заголовка виджета",
+        "open-dashboard-state": "Перейти к новомум состоянию дашборда",
+        "update-dashboard-state": "Обновить текущее состояние дашборда",
+        "open-dashboard": "Перейти к другому дашборду",
+        "custom": "Пользовательское действие",
+        "target-dashboard-state": "Целевое состояние дашборда",
+        "target-dashboard-state-required": "Целевое состояние дашборда обязательно",
+        "set-entity-from-widget": "Установить объект из виджета",
+        "target-dashboard": "Целевой дашборд",
+        "open-right-layout": "Открыть мобильный режим дашборда"
     },
     "widgets-bundle": {
         "current": "Текущий набор",
@@ -1312,6 +1505,8 @@
         "background-color": "Цвет фона",
         "text-color": "Цвет текста",
         "padding": "Отступ",
+        "margin": "Margin",
+        "widget-style": "Стиль виджета",
         "title-style": "Стиль названия",
         "mobile-mode-settings": "Настройки мобильного режима",
         "order": "Порядок",
@@ -1322,11 +1517,29 @@
         "use-dashboard-timewindow": "Использовать временное окно дашборда",
         "display-legend": "Показать легенду",
         "datasources": "Источники данных",
+        "maximum-datasources": "Максимальной количество источников данных равно {{count}}",
         "datasource-type": "Тип",
         "datasource-parameters": "Параметры",
         "remove-datasource": "Удалить источник данных",
         "add-datasource": "Добавить источник данных",
-        "target-device": "Целевое устройство"
+        "target-device": "Целевое устройство",
+        "alarm-source": "Источник оповещения",
+        "actions": "Действия",
+        "action": "Действие",
+        "add-action": "Добавить действие",
+        "search-actions": "Поиск действий",
+        "action-source": "Источник действий",
+        "action-source-required": "Источник действий обязателен.",
+        "action-name": "Название",
+        "action-name-required": "Название действия обязательно.",
+        "action-name-not-unique": "Действие с таким именем уже существует.<br/>Название должно быть уникально в рамках одного источника действий.",
+        "action-icon": "Иконка",
+        "action-type": "Тип",
+        "action-type-required": "Тип действий обязателен.",
+        "edit-action": "Редактировать действие",
+        "delete-action": "Удалить действие",
+        "delete-action-title": "Удалить действие виджета",
+        "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?"
     },
     "widget-type": {
         "import": "Импортировать тип виджета",
@@ -1337,32 +1550,31 @@
         "invalid-widget-type-file-error": "Не удалось импортировать виджет: неизвестная схема данных типа виджета."
     },
     "icon": {
-        "icon": "Icon",
-        "select-icon": "Select icon",
-        "material-icons": "Material icons",
-        "show-all": "Show all icons"
+        "icon": "Иконка",
+        "select-icon": "Выбрать иконку",
+        "material-icons": "Иконки в стиле Material",
+        "show-all": "Показать все иконки"
     },
     "custom": {
         "widget-action": {
-            "action-cell-button": "Action cell button",
-            "row-click": "On row click",
-            "marker-click": "On marker click",
-            "tooltip-tag-action": "Tooltip tag action"
+            "action-cell-button": "Кнопка действия ячейки",
+            "row-click": "Действий при щелчке на строку",
+            "marker-click": "Действия при щелчке на указателе",
+            "tooltip-tag-action": "Действие при подсказке"
         }
     },
     "language": {
         "language": "Язык",
         "locales": {
             "en_US": "Английский",
-			"fr_FR": "Французский",
             "zh_CN": "Китайский",
             "ko_KR": "Корейский",
             "es_ES": "Испанский",
             "it_IT": "Итальянский",
             "ru_RU": "Русский",
-            "ja_JA": "Японский",
-            "tr_TR": "Турецкий"
+            "tr_TR": "Турецкий",
+            "fr_FR": "Французский",
+            "ja_JA": "Японский"
         }
-
     }
 }
diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js
index 0ed70c5..d969c48 100644
--- a/ui/src/app/widget/lib/map-widget2.js
+++ b/ui/src/app/widget/lib/map-widget2.js
@@ -60,6 +60,9 @@ export default class TbMapWidgetV2 {
 
         var minZoomLevel = this.drawRoutes ? 18 : 15;
 
+
+
+
         var initCallback = function() {
             tbMap.update();
             tbMap.resize();
@@ -87,6 +90,9 @@ export default class TbMapWidgetV2 {
         } else if (mapProvider === 'tencent-map') {
             this.map = new TbTencentMap($element,this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.tmApiKey, settings.tmDefaultMapType);
         }
+
+
+        tbMap.initBounds = true;
     }
 
     setCallbacks(callbacks) {
@@ -442,6 +448,7 @@ export default class TbMapWidgetV2 {
         }
 
         function updateLocations(data, datasources) {
+
             var locationsChanged = false;
             var bounds = tbMap.map.createBounds();
             var dataMap = toLabelValueMap(data, datasources);
@@ -454,9 +461,13 @@ export default class TbMapWidgetV2 {
                     tbMap.map.extendBoundsWithMarker(bounds, location.marker);
                 }
             }
-            if (locationsChanged) {
+             if (locationsChanged && tbMap.initBounds) {
+                tbMap.initBounds = !datasources.every(
+                    function (ds) {
+                        return ds.dataReceived === true;
+                    });
                 tbMap.map.fitBounds(bounds);
-            }
+             }
         }
 
         function createTooltipContent(tooltip, data, datasources) {
@@ -477,7 +488,6 @@ export default class TbMapWidgetV2 {
             content = fillPattern(settings.tooltipPattern, settings.tooltipReplaceInfo, data);
             return fillPatternWithActions(content, 'onTooltipAction', tooltip.markerArgs);
         }
-
         if (this.map && this.map.inited() && this.subscription) {
             if (this.subscription.data) {
                 if (!this.locations) {