thingsboard-aplcache

Changes

ui/.stylelintrc 292(+292 -0)

ui/package.json 12(+9 -3)

ui/src/scss/main.scss 309(+181 -128)

Details

diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
index 8e7a9ae..6a78f78 100644
--- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
@@ -116,6 +116,7 @@ public class AppActor extends RuleChainManagerActor {
                 break;
             case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
                 onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
+                break;
             default:
                 return false;
         }
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 7279347..d5260da 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -154,7 +154,7 @@ class DefaultTbContext implements TbContext {
 
     @Override
     public ScriptEngine createJsScriptEngine(String script, String... argNames) {
-        return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames);
+        return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames);
     }
 
     @Override
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index 586e8c3..00885e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -83,7 +83,7 @@ public class AlarmController extends BaseController {
             Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
             logEntityAction(savedAlarm.getId(), savedAlarm,
                     getCurrentUser().getCustomerId(),
-                    savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
+                    alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
             return savedAlarm;
         } catch (Exception e) {
             logEntityAction(emptyId(EntityType.ALARM), alarm,
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index a6daeec..24608cb 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -76,7 +76,7 @@ public class DeviceController extends BaseController {
             device.setTenantId(getCurrentUser().getTenantId());
             if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
                 if (device.getId() == null || device.getId().isNullUid() ||
-                    device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
+                        device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
                     throw new ThingsboardException("You don't have permission to perform this operation!",
                             ThingsboardErrorCode.PERMISSION_DENIED);
                 } else {
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 86b8fda..82ab170 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -276,7 +276,7 @@ public class RuleChainController extends BaseController {
             String errorText = "";
             ScriptEngine engine = null;
             try {
-                engine = new RuleNodeJsScriptEngine(jsSandboxService, script, argNames);
+                engine = new RuleNodeJsScriptEngine(jsSandboxService, getCurrentUser().getId(), script, argNames);
                 TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
                 switch (scriptType) {
                     case "update":
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 10a16ec..7062ca9 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -49,15 +49,15 @@ import org.thingsboard.server.common.data.kv.Aggregation;
 import org.thingsboard.server.common.data.kv.AttributeKey;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 import org.thingsboard.server.common.data.kv.KvEntry;
 import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@@ -81,7 +81,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
@@ -201,7 +200,7 @@ public class TelemetryController extends BaseController {
                 (result, entityId) -> {
                     // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
                     Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
-                    List<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
+                    List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
                             .collect(Collectors.toList());
 
                     Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
index c3ffbab..6f51cee 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
@@ -24,6 +24,8 @@ import org.apache.curator.framework.recipes.cache.ChildData;
 import org.apache.curator.framework.recipes.cache.PathChildrenCache;
 import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
 import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
+import org.apache.curator.framework.state.ConnectionState;
+import org.apache.curator.framework.state.ConnectionStateListener;
 import org.apache.curator.retry.RetryForever;
 import org.apache.curator.utils.CloseableUtils;
 import org.apache.zookeeper.CreateMode;
@@ -127,12 +129,38 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
                     .creatingParentsIfNeeded()
                     .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress()));
             log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
+            client.getConnectionStateListenable().addListener(checkReconnect(self));
         } catch (Exception e) {
             log.error("Failed to create ZK node", e);
             throw new RuntimeException(e);
         }
     }
 
+    private ConnectionStateListener checkReconnect(ServerInstance self) {
+        return (client, newState) -> {
+            log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
+            if (newState == ConnectionState.LOST) {
+                reconnect();
+            }
+        };
+    }
+
+    private boolean reconnectInProgress = false;
+
+    private synchronized void reconnect() {
+        if (!reconnectInProgress) {
+            reconnectInProgress = true;
+            try {
+                client.blockUntilConnected();
+                publishCurrentServer();
+            } catch (InterruptedException e) {
+                log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
+            } finally {
+                reconnectInProgress = false;
+            }
+        }
+    }
+
     @Override
     public void unpublishCurrentServer() {
         try {
@@ -156,7 +184,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
                 .filter(cd -> !cd.getPath().equals(nodePath))
                 .map(cd -> {
                     try {
-                        return new ServerInstance( (ServerAddress) SerializationUtils.deserialize(cd.getData()));
+                        return new ServerInstance((ServerAddress) SerializationUtils.deserialize(cd.getData()));
                     } catch (NoSuchElementException e) {
                         log.error("Failed to decode ZK node", e);
                         throw new RuntimeException(e);
@@ -198,7 +226,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
         }
         ServerInstance instance;
         try {
-            ServerAddress serverAddress  = SerializationUtils.deserialize(data.getData());
+            ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
             instance = new ServerInstance(serverAddress);
         } catch (SerializationException e) {
             log.error("Failed to decode server instance for node {}", data.getPath(), e);
diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
index 7f274ec..1d89c9d 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.thingsboard.server.service.script;
 
 import com.google.common.util.concurrent.Futures;
@@ -21,7 +20,12 @@ import com.google.common.util.concurrent.ListenableFuture;
 import delight.nashornsandbox.NashornSandbox;
 import delight.nashornsandbox.NashornSandboxes;
 import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.id.EntityId;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
@@ -42,9 +46,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
     private ScriptEngine engine;
     private ExecutorService monitorExecutorService;
 
-    private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
+    private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
+    private final Map<BlackListKey, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>();
 
-    private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
+    private final Map<String, ScriptInfo> scriptKeyToInfo = new ConcurrentHashMap<>();
+    private final Map<UUID, ScriptInfo> scriptIdToInfo = new ConcurrentHashMap<>();
 
     @PostConstruct
     public void init() {
@@ -63,7 +69,7 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
 
     @PreDestroy
     public void stop() {
-        if  (monitorExecutorService != null) {
+        if (monitorExecutorService != null) {
             monitorExecutorService.shutdownNow();
         }
     }
@@ -78,75 +84,107 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
 
     @Override
     public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
-        UUID scriptId = UUID.randomUUID();
-        String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
-        String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
-        try {
-            if (useJsSandbox()) {
-                sandbox.eval(jsScript);
+        ScriptInfo scriptInfo = deduplicate(scriptType, scriptBody);
+        UUID scriptId = scriptInfo.getId();
+        AtomicInteger duplicateCount = scriptInfo.getCount();
+
+        synchronized (scriptInfo.getLock()) {
+            if (duplicateCount.compareAndSet(0, 1)) {
+                try {
+                    evaluate(scriptId, scriptType, scriptBody, argNames);
+                } catch (Exception e) {
+                    duplicateCount.decrementAndGet();
+                    log.warn("Failed to compile JS script: {}", e.getMessage(), e);
+                    return Futures.immediateFailedFuture(e);
+                }
             } else {
-                engine.eval(jsScript);
+                duplicateCount.incrementAndGet();
             }
-            functionsMap.put(scriptId, functionName);
-        } catch (Exception e) {
-            log.warn("Failed to compile JS script: {}", e.getMessage(), e);
-            return Futures.immediateFailedFuture(e);
         }
         return Futures.immediateFuture(scriptId);
     }
 
+    private void evaluate(UUID scriptId, JsScriptType scriptType, String scriptBody, String... argNames) throws ScriptException {
+        String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
+        String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
+        if (useJsSandbox()) {
+            sandbox.eval(jsScript);
+        } else {
+            engine.eval(jsScript);
+        }
+        functionsMap.put(scriptId, functionName);
+    }
+
     @Override
-    public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
+    public ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args) {
         String functionName = functionsMap.get(scriptId);
         if (functionName == null) {
-            return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
-        }
-        if (!isBlackListed(scriptId)) {
-            try {
-                Object result;
-                if (useJsSandbox()) {
-                    result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
-                } else {
-                    result = ((Invocable)engine).invokeFunction(functionName, args);
-                }
-                return Futures.immediateFuture(result);
-            } catch (Exception e) {
-                blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
-                return Futures.immediateFailedFuture(e);
-            }
+            String message = "No compiled script found for scriptId: [" + scriptId + "]!";
+            log.warn(message);
+            return Futures.immediateFailedFuture(new RuntimeException(message));
+        }
+
+        BlackListInfo blackListInfo = blackListedFunctions.get(new BlackListKey(scriptId, entityId));
+        if (blackListInfo != null && blackListInfo.getCount() >= getMaxErrors()) {
+            RuntimeException throwable = new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!", blackListInfo.getCause());
+            throwable.printStackTrace();
+            return Futures.immediateFailedFuture(throwable);
+        }
+
+        try {
+            return invoke(functionName, args);
+        } catch (Exception e) {
+            BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
+            blackListedFunctions.computeIfAbsent(blackListKey, key -> new BlackListInfo()).incrementWithReason(e);
+            return Futures.immediateFailedFuture(e);
+        }
+    }
+
+    private ListenableFuture<Object> invoke(String functionName, Object... args) throws ScriptException, NoSuchMethodException {
+        Object result;
+        if (useJsSandbox()) {
+            result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
         } else {
-            return Futures.immediateFailedFuture(
-                    new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
+            result = ((Invocable) engine).invokeFunction(functionName, args);
         }
+        return Futures.immediateFuture(result);
     }
 
     @Override
-    public ListenableFuture<Void> release(UUID scriptId) {
-        String functionName = functionsMap.get(scriptId);
-        if (functionName != null) {
-            try {
-                if (useJsSandbox()) {
-                    sandbox.eval(functionName + " = undefined;");
-                } else {
-                    engine.eval(functionName + " = undefined;");
+    public ListenableFuture<Void> release(UUID scriptId, EntityId entityId) {
+        ScriptInfo scriptInfo = scriptIdToInfo.get(scriptId);
+        if (scriptInfo == null) {
+            log.warn("Script release called for not existing script id [{}]", scriptId);
+            return Futures.immediateFuture(null);
+        }
+
+        synchronized (scriptInfo.getLock()) {
+            int remainingDuplicates = scriptInfo.getCount().decrementAndGet();
+            if (remainingDuplicates > 0) {
+                return Futures.immediateFuture(null);
+            }
+
+            String functionName = functionsMap.get(scriptId);
+            if (functionName != null) {
+                try {
+                    if (useJsSandbox()) {
+                        sandbox.eval(functionName + " = undefined;");
+                    } else {
+                        engine.eval(functionName + " = undefined;");
+                    }
+                    functionsMap.remove(scriptId);
+                    blackListedFunctions.remove(new BlackListKey(scriptId, entityId));
+                } catch (ScriptException e) {
+                    log.error("Could not release script [{}] [{}]", scriptId, remainingDuplicates);
+                    return Futures.immediateFailedFuture(e);
                 }
-                functionsMap.remove(scriptId);
-                blackListedFunctions.remove(scriptId);
-            } catch (ScriptException e) {
-                return Futures.immediateFailedFuture(e);
+            } else {
+                log.warn("Function name do not exist for script [{}] [{}]", scriptId, remainingDuplicates);
             }
         }
         return Futures.immediateFuture(null);
     }
 
-    private boolean isBlackListed(UUID scriptId) {
-        if (blackListedFunctions.containsKey(scriptId)) {
-            AtomicInteger errorCount = blackListedFunctions.get(scriptId);
-            return errorCount.get() >= getMaxErrors();
-        } else {
-            return false;
-        }
-    }
 
     private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
         switch (scriptType) {
@@ -156,4 +194,67 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
                 throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
         }
     }
+
+    private ScriptInfo deduplicate(JsScriptType scriptType, String scriptBody) {
+        ScriptInfo meta = ScriptInfo.preInit();
+        String key = deduplicateKey(scriptType, scriptBody);
+        ScriptInfo latestMeta = scriptKeyToInfo.computeIfAbsent(key, i -> meta);
+        return scriptIdToInfo.computeIfAbsent(latestMeta.getId(), i -> latestMeta);
+    }
+
+    private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
+        return scriptType + "_" + scriptBody;
+    }
+
+    @Getter
+    private static class ScriptInfo {
+        private final UUID id;
+        private final Object lock;
+        private final AtomicInteger count;
+
+        ScriptInfo(UUID id, Object lock, AtomicInteger count) {
+            this.id = id;
+            this.lock = lock;
+            this.count = count;
+        }
+
+        static ScriptInfo preInit() {
+            UUID preId = UUID.randomUUID();
+            AtomicInteger preCount = new AtomicInteger();
+            Object preLock = new Object();
+            return new ScriptInfo(preId, preLock, preCount);
+        }
+    }
+
+    @EqualsAndHashCode
+    @Getter
+    @RequiredArgsConstructor
+    private static class BlackListKey {
+        private final UUID scriptId;
+        private final EntityId entityId;
+
+    }
+
+    @Data
+    private static class BlackListInfo {
+        private final AtomicInteger count;
+        private Exception ex;
+
+        BlackListInfo() {
+            this.count = new AtomicInteger(0);
+        }
+
+        void incrementWithReason(Exception e) {
+            count.incrementAndGet();
+            ex = e;
+        }
+
+        int getCount() {
+            return count.get();
+        }
+
+        Exception getCause() {
+            return ex;
+        }
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
index ee86c62..5e1c676 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
@@ -17,6 +17,7 @@
 package org.thingsboard.server.service.script;
 
 import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.id.EntityId;
 
 import java.util.UUID;
 
@@ -24,8 +25,8 @@ public interface JsSandboxService {
 
     ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames);
 
-    ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args);
+    ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args);
 
-    ListenableFuture<Void> release(UUID scriptId);
+    ListenableFuture<Void> release(UUID scriptId, EntityId entityId);
 
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
index 98b0e77..767dc05 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.Sets;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
@@ -39,13 +40,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
     private final JsSandboxService sandboxService;
 
     private final UUID scriptId;
+    private final EntityId entityId;
 
-    public RuleNodeJsScriptEngine(JsSandboxService sandboxService, String script, String... argNames) {
+    public RuleNodeJsScriptEngine(JsSandboxService sandboxService, EntityId entityId, String script, String... argNames) {
         this.sandboxService = sandboxService;
+        this.entityId = entityId;
         try {
             this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
         } catch (Exception e) {
-            throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
+            throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
         }
     }
 
@@ -162,20 +165,20 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
     private JsonNode executeScript(TbMsg msg) throws ScriptException {
         try {
             String[] inArgs = prepareArgs(msg);
-            String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
+            String eval = sandboxService.invokeFunction(this.scriptId, this.entityId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
             return mapper.readTree(eval);
         } catch (ExecutionException e) {
             if (e.getCause() instanceof ScriptException) {
                 throw (ScriptException)e.getCause();
             } else {
-                throw new ScriptException("Failed to execute js script: " + e.getMessage());
+                throw new ScriptException(e);
             }
         } catch (Exception e) {
-            throw new ScriptException("Failed to execute js script: " + e.getMessage());
+            throw new ScriptException(e);
         }
     }
 
     public void destroy() {
-        sandboxService.release(this.scriptId);
+        sandboxService.release(this.scriptId, this.entityId);
     }
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index 263b484..548c417 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -24,24 +24,28 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
 import org.thingsboard.rule.engine.api.util.DonAsynchron;
+import org.thingsboard.server.actors.service.ActorService;
 import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 import org.thingsboard.server.common.data.kv.DataType;
 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 import org.thingsboard.server.common.data.kv.KvEntry;
 import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
+import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.timeseries.TimeseriesService;
@@ -101,6 +105,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
     @Lazy
     private DeviceStateService stateService;
 
+    @Autowired
+    @Lazy
+    private ActorService actorService;
+
     private ExecutorService tsCallBackExecutor;
     private ExecutorService wsCallBackExecutor;
 
@@ -204,6 +212,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
     }
 
     @Override
+    public void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes) {
+        DeviceAttributesEventNotificationMsg notificationMsg = DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
+                deviceId, DataConstants.SHARED_SCOPE, new ArrayList<>(attributes));
+        actorService.onMsg(new SendToClusterMsg(deviceId, notificationMsg));
+    }
+
+    @Override
     public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) {
         ClusterAPIProtos.SubscriptionProto proto;
         try {
@@ -355,9 +370,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
                     e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
         } else if (subscription.getType() == TelemetryFeature.TIMESERIES) {
             long curTs = System.currentTimeMillis();
-            List<TsKvQuery> queries = new ArrayList<>();
+            List<ReadTsKvQuery> queries = new ArrayList<>();
             subscription.getKeyStates().entrySet().forEach(e -> {
-                queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
+                queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
             });
 
             DonAsynchron.withCallback(tsService.findAll(entityId, queries),
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
index 3b2d690..2ff8e89 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
@@ -30,10 +30,10 @@ import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.EntityIdFactory;
 import org.thingsboard.server.common.data.kv.Aggregation;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.timeseries.TimeseriesService;
 import org.thingsboard.server.service.security.AccessValidator;
@@ -251,7 +251,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
         }
         EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
         List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
-        List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
+        List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
                 .collect(Collectors.toList());
 
         FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@@ -337,7 +337,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
             log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
             startTs = cmd.getStartTs();
             long endTs = cmd.getStartTs() + cmd.getTimeWindow();
-            List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(),
+            List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, cmd.getInterval(),
                     getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
 
             final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);
diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml
index 978a570..dcfc930 100644
--- a/application/src/main/resources/logback.xml
+++ b/application/src/main/resources/logback.xml
@@ -17,7 +17,7 @@
 
 -->
 <!DOCTYPE configuration>
-<configuration>
+<configuration scan="true" scanPeriod="10 seconds">
 
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
diff --git a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
index ea70384..88961dc 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
@@ -21,12 +21,18 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.thingsboard.rule.engine.api.ScriptEngine;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
 import javax.script.ScriptException;
-
+import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.junit.Assert.*;
 
@@ -35,6 +41,8 @@ public class RuleNodeJsScriptEngineTest {
     private ScriptEngine scriptEngine;
     private TestNashornJsSandboxService jsSandboxService;
 
+    private EntityId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
     @Before
     public void beforeTest() throws Exception {
         jsSandboxService = new TestNashornJsSandboxService(false, 1, 100, 3);
@@ -48,7 +56,7 @@ public class RuleNodeJsScriptEngineTest {
     @Test
     public void msgCanBeUpdated() throws ScriptException {
         String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
 
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
@@ -65,7 +73,7 @@ public class RuleNodeJsScriptEngineTest {
     @Test
     public void newAttributesCanBeAddedInMsg() throws ScriptException {
         String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
         metaData.putValue("humidity", "99");
@@ -81,7 +89,7 @@ public class RuleNodeJsScriptEngineTest {
     @Test
     public void payloadCanBeUpdated() throws ScriptException {
         String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
         metaData.putValue("humidity", "99");
@@ -99,7 +107,7 @@ public class RuleNodeJsScriptEngineTest {
     @Test
     public void metadataAccessibleForFilter() throws ScriptException {
         String function = "return metadata.humidity < 15;";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
         metaData.putValue("humidity", "99");
@@ -113,7 +121,7 @@ public class RuleNodeJsScriptEngineTest {
     @Test
     public void dataAccessibleForFilter() throws ScriptException {
         String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
         metaData.putValue("humidity", "99");
@@ -134,7 +142,7 @@ public class RuleNodeJsScriptEngineTest {
                 "};\n" +
                 "\n" +
                 "return nextRelation(metadata, msg);";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "10");
         metaData.putValue("humidity", "99");
@@ -156,7 +164,7 @@ public class RuleNodeJsScriptEngineTest {
                 "};\n" +
                 "\n" +
                 "return nextRelation(metadata, msg);";
-        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
+        scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "10");
         metaData.putValue("humidity", "99");
@@ -168,4 +176,75 @@ public class RuleNodeJsScriptEngineTest {
         scriptEngine.destroy();
     }
 
+    @Test
+    public void concurrentReleasedCorrectly() throws InterruptedException, ExecutionException {
+        String code = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
+
+        int repeat = 1000;
+        ExecutorService service = Executors.newFixedThreadPool(repeat);
+        Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
+        CountDownLatch startLatch = new CountDownLatch(repeat);
+        CountDownLatch finishLatch = new CountDownLatch(repeat);
+        AtomicInteger failedCount = new AtomicInteger(0);
+
+        for (int i = 0; i < repeat; i++) {
+            service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
+        }
+
+        finishLatch.await();
+        assertTrue(scriptIds.size() == 1);
+        assertTrue(failedCount.get() == 0);
+
+        CountDownLatch nextStart = new CountDownLatch(repeat);
+        CountDownLatch nextFinish = new CountDownLatch(repeat);
+        for (int i = 0; i < repeat; i++) {
+            service.submit(() -> runScript(nextStart, nextFinish, failedCount, scriptIds, code));
+        }
+
+        nextFinish.await();
+        assertTrue(scriptIds.size() == 1);
+        assertTrue(failedCount.get() == 0);
+        service.shutdownNow();
+    }
+
+    @Test
+    public void concurrentFailedEvaluationShouldThrowException() throws InterruptedException {
+        String code = "metadata.temp = metadata.temp * 10; urn {metadata: metadata};";
+
+        int repeat = 10000;
+        ExecutorService service = Executors.newFixedThreadPool(repeat);
+        Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
+        CountDownLatch startLatch = new CountDownLatch(repeat);
+        CountDownLatch finishLatch = new CountDownLatch(repeat);
+        AtomicInteger failedCount = new AtomicInteger(0);
+        for (int i = 0; i < repeat; i++) {
+            service.submit(() -> {
+                service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
+            });
+        }
+
+        finishLatch.await();
+        assertTrue(scriptIds.isEmpty());
+        assertEquals(repeat, failedCount.get());
+        service.shutdownNow();
+    }
+
+    private void runScript(CountDownLatch startLatch, CountDownLatch finishLatch, AtomicInteger failedCount,
+                           Map<UUID, Object> scriptIds, String code) {
+        try {
+            for (int k = 0; k < 10; k++) {
+                startLatch.countDown();
+                startLatch.await();
+                UUID scriptId = jsSandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, code).get();
+                scriptIds.put(scriptId, new Object());
+                jsSandboxService.invokeFunction(scriptId, ruleNodeId, "{}", "{}", "TEXT").get();
+                jsSandboxService.release(scriptId, ruleNodeId).get();
+            }
+        } catch (Throwable th) {
+            failedCount.incrementAndGet();
+        } finally {
+            finishLatch.countDown();
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java
new file mode 100644
index 0000000..11b95ca
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.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.server.common.data.kv;
+
+import lombok.Data;
+
+@Data
+public class BaseDeleteTsKvQuery extends BaseTsKvQuery implements DeleteTsKvQuery {
+
+    private final Boolean rewriteLatestIfDeleted;
+
+    public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted) {
+        super(key, startTs, endTs);
+        this.rewriteLatestIfDeleted = rewriteLatestIfDeleted;
+    }
+
+    public BaseDeleteTsKvQuery(String key, long startTs, long endTs) {
+        this(key, startTs, endTs, false);
+    }
+
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
new file mode 100644
index 0000000..3c48adf
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
@@ -0,0 +1,45 @@
+/**
+ * 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.server.common.data.kv;
+
+import lombok.Data;
+
+@Data
+public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
+
+    private final long interval;
+    private final int limit;
+    private final Aggregation aggregation;
+    private final String orderBy;
+
+    public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
+        this(key, startTs, endTs, interval, limit, aggregation, "DESC");
+    }
+
+    public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation,
+                             String orderBy) {
+        super(key, startTs, endTs);
+        this.interval = interval;
+        this.limit = limit;
+        this.aggregation = aggregation;
+        this.orderBy = orderBy;
+    }
+
+    public BaseReadTsKvQuery(String key, long startTs, long endTs) {
+        this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC");
+    }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
index 0afe00b..c4fa69c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
@@ -23,21 +23,11 @@ public class BaseTsKvQuery implements TsKvQuery {
     private final String key;
     private final long startTs;
     private final long endTs;
-    private final long interval;
-    private final int limit;
-    private final Aggregation aggregation;
 
-    public BaseTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
+    public BaseTsKvQuery(String key, long startTs, long endTs) {
         this.key = key;
         this.startTs = startTs;
         this.endTs = endTs;
-        this.interval = interval;
-        this.limit = limit;
-        this.aggregation = aggregation;
-    }
-
-    public BaseTsKvQuery(String key, long startTs, long endTs) {
-        this(key, startTs, endTs, endTs-startTs, 1, Aggregation.AVG);
     }
 
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java
new file mode 100644
index 0000000..7607a59
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java
@@ -0,0 +1,22 @@
+/**
+ * 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.server.common.data.kv;
+
+public interface DeleteTsKvQuery extends TsKvQuery {
+
+    Boolean getRewriteLatestIfDeleted();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java
new file mode 100644
index 0000000..7d54745
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.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.server.common.data.kv;
+
+public interface ReadTsKvQuery extends TsKvQuery {
+
+    long getInterval();
+
+    int getLimit();
+
+    Aggregation getAggregation();
+
+    String getOrderBy();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
index ca9f90c..9df514e 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
@@ -23,10 +23,4 @@ public interface TsKvQuery {
 
     long getEndTs();
 
-    long getInterval();
-
-    int getLimit();
-
-    Aggregation getAggregation();
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
index d88a1c4..55c5987 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
@@ -39,7 +39,6 @@ import org.thingsboard.server.dao.exception.DataValidationException;
 
 import javax.annotation.Nullable;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -92,10 +91,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
     })
     @Override
     public boolean saveRelation(EntityRelation relation) {
@@ -106,10 +105,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
     })
     @Override
     public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
@@ -120,10 +119,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
     })
     @Override
     public boolean deleteRelation(EntityRelation relation) {
@@ -134,10 +133,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
     })
     @Override
     public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
@@ -148,10 +147,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
     })
     @Override
     public boolean deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@@ -162,10 +161,10 @@ public class BaseRelationService implements RelationService {
 
     @Caching(evict = {
             @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
-            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
+            @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
     })
     @Override
     public ListenableFuture<Boolean> deleteRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@@ -248,26 +247,30 @@ public class BaseRelationService implements RelationService {
         fromTypeAndTypeGroup.add(relation.getFrom());
         fromTypeAndTypeGroup.add(relation.getType());
         fromTypeAndTypeGroup.add(relation.getTypeGroup());
+        fromTypeAndTypeGroup.add(EntitySearchDirection.FROM.name());
         cache.evict(fromTypeAndTypeGroup);
 
         List<Object> fromAndTypeGroup = new ArrayList<>();
         fromAndTypeGroup.add(relation.getFrom());
         fromAndTypeGroup.add(relation.getTypeGroup());
+        fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
         cache.evict(fromAndTypeGroup);
 
         List<Object> toAndTypeGroup = new ArrayList<>();
         toAndTypeGroup.add(relation.getTo());
         toAndTypeGroup.add(relation.getTypeGroup());
+        toAndTypeGroup.add(EntitySearchDirection.TO.name());
         cache.evict(toAndTypeGroup);
 
         List<Object> toTypeAndTypeGroup = new ArrayList<>();
-        fromTypeAndTypeGroup.add(relation.getTo());
-        fromTypeAndTypeGroup.add(relation.getType());
-        fromTypeAndTypeGroup.add(relation.getTypeGroup());
+        toTypeAndTypeGroup.add(relation.getTo());
+        toTypeAndTypeGroup.add(relation.getType());
+        toTypeAndTypeGroup.add(relation.getTypeGroup());
+        toTypeAndTypeGroup.add(EntitySearchDirection.TO.name());
         cache.evict(toTypeAndTypeGroup);
     }
 
-    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
+    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}")
     @Override
     public List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
         validate(from);
@@ -288,6 +291,7 @@ public class BaseRelationService implements RelationService {
         List<Object> fromAndTypeGroup = new ArrayList<>();
         fromAndTypeGroup.add(from);
         fromAndTypeGroup.add(typeGroup);
+        fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
 
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         List<EntityRelation> fromCache = cache.get(fromAndTypeGroup, List.class);
@@ -326,7 +330,7 @@ public class BaseRelationService implements RelationService {
                 });
     }
 
-    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}")
+    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}")
     @Override
     public List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
         try {
@@ -345,7 +349,7 @@ public class BaseRelationService implements RelationService {
         return relationDao.findAllByFromAndType(from, relationType, typeGroup);
     }
 
-    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}")
+    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}")
     @Override
     public List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup) {
         validate(to);
@@ -366,6 +370,7 @@ public class BaseRelationService implements RelationService {
         List<Object> toAndTypeGroup = new ArrayList<>();
         toAndTypeGroup.add(to);
         toAndTypeGroup.add(typeGroup);
+        toAndTypeGroup.add(EntitySearchDirection.TO.name());
 
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         List<EntityRelation> fromCache = cache.get(toAndTypeGroup, List.class);
@@ -415,7 +420,7 @@ public class BaseRelationService implements RelationService {
         });
     }
 
-    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
+    @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
     @Override
     public List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
         try {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
index df72943..5bd9175 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
@@ -26,14 +26,16 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.UUIDConverter;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.kv.Aggregation;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.model.sql.TsKvEntity;
 import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey;
@@ -103,7 +105,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
     }
 
     @Override
-    public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
+    public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
         List<ListenableFuture<List<TsKvEntry>>> futures = queries
                 .stream()
                 .map(query -> findAllAsync(entityId, query))
@@ -122,7 +124,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
         }, service);
     }
 
-    private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
+    private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
         if (query.getAggregation() == Aggregation.NONE) {
             return findAllAsyncWithLimit(entityId, query);
         } else {
@@ -229,7 +231,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
         });
     }
 
-    private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
+    private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
         return Futures.immediateFuture(
                 DaoUtil.convertDataList(
                         tsKvRepository.findAllWithLimit(
@@ -238,7 +240,9 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
                                 query.getKey(),
                                 query.getStartTs(),
                                 query.getEndTs(),
-                                new PageRequest(0, query.getLimit()))));
+                                new PageRequest(0, query.getLimit(),
+                                        new Sort(Sort.Direction.fromString(
+                                                query.getOrderBy()), "ts")))));
     }
 
     @Override
@@ -307,6 +311,36 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
         });
     }
 
+    @Override
+    public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
+        return service.submit(() -> {
+            tsKvRepository.delete(
+                    fromTimeUUID(entityId.getId()),
+                    entityId.getEntityType(),
+                    query.getKey(),
+                    query.getStartTs(),
+                    query.getEndTs());
+            return null;
+        });
+    }
+
+    @Override
+    public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
+        TsKvLatestEntity latestEntity = new TsKvLatestEntity();
+        latestEntity.setEntityType(entityId.getEntityType());
+        latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
+        latestEntity.setKey(query.getKey());
+        return service.submit(() -> {
+            tsKvLatestRepository.delete(latestEntity);
+            return null;
+        });
+    }
+
+    @Override
+    public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
+        return service.submit(() -> null);
+    }
+
     @PreDestroy
     void onDestroy() {
         if (insertService != null) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
index a1d1920..4c743e5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
@@ -16,10 +16,12 @@
 package org.thingsboard.server.dao.sql.timeseries;
 
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.data.repository.query.Param;
 import org.springframework.scheduling.annotation.Async;
+import org.springframework.transaction.annotation.Transactional;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.dao.model.sql.TsKvCompositeKey;
 import org.thingsboard.server.dao.model.sql.TsKvEntity;
@@ -33,7 +35,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
 
     @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
             "AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
-            "AND tskv.ts > :startTs AND tskv.ts < :endTs ORDER BY tskv.ts DESC")
+            "AND tskv.ts > :startTs AND tskv.ts < :endTs")
     List<TsKvEntity> findAllWithLimit(@Param("entityId") String entityId,
                                       @Param("entityType") EntityType entityType,
                                       @Param("entityKey") String key,
@@ -41,6 +43,17 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
                                       @Param("endTs") long endTs,
                                       Pageable pageable);
 
+    @Transactional
+    @Modifying
+    @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
+            "AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
+            "AND tskv.ts > :startTs AND tskv.ts < :endTs")
+    void delete(@Param("entityId") String entityId,
+                @Param("entityType") EntityType entityType,
+                @Param("entityKey") String key,
+                @Param("startTs") long startTs,
+                @Param("endTs") long endTs);
+
     @Async
     @Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
             "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
@@ -56,30 +69,30 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
             "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
             "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
     CompletableFuture<TsKvEntity> findMin(@Param("entityId") String entityId,
-                       @Param("entityType") EntityType entityType,
-                       @Param("entityKey") String entityKey,
-                       @Param("startTs") long startTs,
-                       @Param("endTs") long endTs);
+                                          @Param("entityType") EntityType entityType,
+                                          @Param("entityKey") String entityKey,
+                                          @Param("startTs") long startTs,
+                                          @Param("endTs") long endTs);
 
     @Async
     @Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " +
             "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
             "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
     CompletableFuture<TsKvEntity> findCount(@Param("entityId") String entityId,
-                                 @Param("entityType") EntityType entityType,
-                                 @Param("entityKey") String entityKey,
-                                 @Param("startTs") long startTs,
-                                 @Param("endTs") long endTs);
+                                            @Param("entityType") EntityType entityType,
+                                            @Param("entityKey") String entityKey,
+                                            @Param("startTs") long startTs,
+                                            @Param("endTs") long endTs);
 
     @Async
     @Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(tskv.doubleValue)) FROM TsKvEntity tskv " +
             "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
             "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
     CompletableFuture<TsKvEntity> findAvg(@Param("entityId") String entityId,
-                       @Param("entityType") EntityType entityType,
-                       @Param("entityKey") String entityKey,
-                       @Param("startTs") long startTs,
-                       @Param("endTs") long endTs);
+                                          @Param("entityType") EntityType entityType,
+                                          @Param("entityKey") String entityKey,
+                                          @Param("startTs") long startTs,
+                                          @Param("endTs") long endTs);
 
 
     @Async
@@ -87,8 +100,8 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
             "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
             "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
     CompletableFuture<TsKvEntity> findSum(@Param("entityId") String entityId,
-                       @Param("entityType") EntityType entityType,
-                       @Param("entityKey") String entityKey,
-                       @Param("startTs") long startTs,
-                       @Param("endTs") long endTs);
+                                          @Param("entityType") EntityType entityType,
+                                          @Param("entityKey") String entityKey,
+                                          @Param("startTs") long startTs,
+                                          @Param("endTs") long endTs);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
index c981378..98e859f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
@@ -22,8 +22,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 import org.thingsboard.server.dao.service.Validator;
 
@@ -40,14 +41,15 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
 public class BaseTimeseriesService implements TimeseriesService {
 
     public static final int INSERTS_PER_ENTRY = 3;
+    public static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY;
 
     @Autowired
     private TimeseriesDao timeseriesDao;
 
     @Override
-    public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
+    public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
         validate(entityId);
-        queries.forEach(query -> validate(query));
+        queries.forEach(BaseTimeseriesService::validate);
         return timeseriesDao.findAllAsync(entityId, queries);
     }
 
@@ -95,17 +97,42 @@ public class BaseTimeseriesService implements TimeseriesService {
         futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
     }
 
+    @Override
+    public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
+        validate(entityId);
+        deleteTsKvQueries.forEach(BaseTimeseriesService::validate);
+        List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
+        for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) {
+            deleteAndRegisterFutures(futures, entityId, tsKvQuery);
+        }
+        return Futures.allAsList(futures);
+    }
+
+    private void deleteAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, DeleteTsKvQuery query) {
+        futures.add(timeseriesDao.remove(entityId, query));
+        futures.add(timeseriesDao.removeLatest(entityId, query));
+        futures.add(timeseriesDao.removePartition(entityId, query));
+    }
+
     private static void validate(EntityId entityId) {
         Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
     }
 
-    private static void validate(TsKvQuery query) {
+    private static void validate(ReadTsKvQuery query) {
         if (query == null) {
-            throw new IncorrectParameterException("TsKvQuery can't be null");
+            throw new IncorrectParameterException("ReadTsKvQuery can't be null");
         } else if (isBlank(query.getKey())) {
-            throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty");
+            throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Key can't be empty");
         } else if (query.getAggregation() == null) {
-            throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty");
+            throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty");
+        }
+    }
+
+    private static void validate(DeleteTsKvQuery query) {
+        if (query == null) {
+            throw new IncorrectParameterException("DeleteTsKvQuery can't be null");
+        } else if (isBlank(query.getKey())) {
+            throw new IncorrectParameterException("Incorrect DeleteTsKvQuery. Key can't be empty");
         }
     }
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
index be36d3b..709bfd5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
@@ -20,6 +20,7 @@ import com.datastax.driver.core.PreparedStatement;
 import com.datastax.driver.core.ResultSet;
 import com.datastax.driver.core.ResultSetFuture;
 import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Statement;
 import com.datastax.driver.core.querybuilder.QueryBuilder;
 import com.datastax.driver.core.querybuilder.Select;
 import com.google.common.base.Function;
@@ -34,16 +35,17 @@ import org.springframework.core.env.Environment;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.kv.Aggregation;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 import org.thingsboard.server.common.data.kv.DataType;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 import org.thingsboard.server.common.data.kv.KvEntry;
 import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.dao.model.ModelConstants;
 import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
 import org.thingsboard.server.dao.util.NoSqlDao;
@@ -55,11 +57,11 @@ import javax.annotation.PreDestroy;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-import java.util.ArrayList;
 import java.util.Optional;
-import java.util.Collections;
 import java.util.stream.Collectors;
 
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@@ -77,8 +79,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     public static final String GENERATED_QUERY_FOR_ENTITY_TYPE_AND_ENTITY_ID = "Generated query [{}] for entityType {} and entityId {}";
     public static final String SELECT_PREFIX = "SELECT ";
     public static final String EQUALS_PARAM = " = ? ";
-
-
+    public static final String ASC_ORDER = "ASC";
+    public static final String DESC_ORDER = "DESC";
     private static List<Long> FIXED_PARTITION = Arrays.asList(new Long[]{0L});
 
     @Autowired
@@ -97,9 +99,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     private PreparedStatement latestInsertStmt;
     private PreparedStatement[] saveStmts;
     private PreparedStatement[] saveTtlStmts;
-    private PreparedStatement[] fetchStmts;
+    private PreparedStatement[] fetchStmtsAsc;
+    private PreparedStatement[] fetchStmtsDesc;
     private PreparedStatement findLatestStmt;
     private PreparedStatement findAllLatestStmt;
+    private PreparedStatement deleteStmt;
+    private PreparedStatement deletePartitionStmt;
 
     private boolean isInstall() {
         return environment.acceptsProfiles("install");
@@ -109,7 +114,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     public void init() {
         super.startExecutor();
         if (!isInstall()) {
-            getFetchStmt(Aggregation.NONE);
+            getFetchStmt(Aggregation.NONE, DESC_ORDER);
             Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
             if (partition.isPresent()) {
                 tsFormat = partition.get();
@@ -126,7 +131,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     }
 
     @Override
-    public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
+    public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
         List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList());
         return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
             @Nullable
@@ -143,7 +148,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     }
 
 
-    private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
+    private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
         if (query.getAggregation() == Aggregation.NONE) {
             return findAllAsyncWithLimit(entityId, query);
         } else {
@@ -153,7 +158,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
             while (stepTs < query.getEndTs()) {
                 long startTs = stepTs;
                 long endTs = stepTs + step;
-                TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation());
+                ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation(), query.getOrderBy());
                 futures.add(findAndAggregateAsync(entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs)));
                 stepTs = endTs;
             }
@@ -172,7 +177,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         return tsFormat.getTruncateUnit().equals(TsPartitionDate.EPOCH_START);
     }
 
-    private ListenableFuture<List<Long>> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
+    private ListenableFuture<List<Long>> getPartitionsFuture(ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
         if (isFixedPartitioning()) { //no need to fetch partitions from DB
             return Futures.immediateFuture(FIXED_PARTITION);
         }
@@ -180,11 +185,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         return Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
     }
 
-    private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
-
+    private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
         long minPartition = toPartitionTs(query.getStartTs());
         long maxPartition = toPartitionTs(query.getEndTs());
-
         final ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
         final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>();
 
@@ -213,7 +216,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         if (cursor.isFull() || !cursor.hasNextPartition()) {
             resultFuture.set(cursor.getData());
         } else {
-            PreparedStatement proto = getFetchStmt(Aggregation.NONE);
+            PreparedStatement proto = getFetchStmt(Aggregation.NONE, cursor.getOrderBy());
             BoundStatement stmt = proto.bind();
             stmt.setString(0, cursor.getEntityType());
             stmt.setUUID(1, cursor.getEntityId());
@@ -238,14 +241,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         }
     }
 
-    private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) {
+    private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) {
         final Aggregation aggregation = query.getAggregation();
         final String key = query.getKey();
         final long startTs = query.getStartTs();
         final long endTs = query.getEndTs();
         final long ts = startTs + (endTs - startTs) / 2;
-
-
         ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
         ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transformAsync(partitionsListFuture,
                 getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
@@ -261,7 +262,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
     private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) {
         return partitions -> {
             try {
-                PreparedStatement proto = getFetchStmt(aggregation);
+                PreparedStatement proto = getFetchStmt(aggregation, DESC_ORDER);
                 List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
                 for (Long partition : partitions) {
                     log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityId.getEntityType(), entityId.getId());
@@ -364,6 +365,204 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         return getFuture(executeAsyncWrite(stmt), rs -> null);
     }
 
+    @Override
+    public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
+        long minPartition = toPartitionTs(query.getStartTs());
+        long maxPartition = toPartitionTs(query.getEndTs());
+
+        ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
+
+        final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+        final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
+
+        Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
+            @Override
+            public void onSuccess(@Nullable List<Long> partitions) {
+                QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions);
+                deleteAsync(cursor, resultFuture);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
+            }
+        }, readResultsProcessingExecutor);
+        return resultFuture;
+    }
+
+    private void deleteAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
+        if (!cursor.hasNextPartition()) {
+            resultFuture.set(null);
+        } else {
+            PreparedStatement proto = getDeleteStmt();
+            BoundStatement stmt = proto.bind();
+            stmt.setString(0, cursor.getEntityType());
+            stmt.setUUID(1, cursor.getEntityId());
+            stmt.setString(2, cursor.getKey());
+            stmt.setLong(3, cursor.getNextPartition());
+            stmt.setLong(4, cursor.getStartTs());
+            stmt.setLong(5, cursor.getEndTs());
+
+            Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
+                @Override
+                public void onSuccess(@Nullable ResultSet result) {
+                    deleteAsync(cursor, resultFuture);
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
+                }
+            }, readResultsProcessingExecutor);
+        }
+    }
+
+    private PreparedStatement getDeleteStmt() {
+        if (deleteStmt == null) {
+            deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
+                    " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.TS_COLUMN + " > ? "
+                    + "AND " + ModelConstants.TS_COLUMN + " <= ?");
+        }
+        return deleteStmt;
+    }
+
+    @Override
+    public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
+        ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey());
+
+        ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
+            long ts = latestEntry.getTs();
+            if (ts >= query.getStartTs() && ts <= query.getEndTs()) {
+                return Futures.immediateFuture(true);
+            } else {
+                log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey());
+            }
+            return Futures.immediateFuture(false);
+        }, readResultsProcessingExecutor);
+
+        ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+            if (isRemove) {
+                return deleteLatest(entityId, query.getKey());
+            }
+            return Futures.immediateFuture(null);
+        }, readResultsProcessingExecutor);
+
+        if (query.getRewriteLatestIfDeleted()) {
+            ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+                if (isRemove) {
+                    return getNewLatestEntryFuture(entityId, query);
+                }
+                return Futures.immediateFuture(null);
+            }, readResultsProcessingExecutor);
+
+            return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)),
+                    list -> Futures.immediateFuture(null), readResultsProcessingExecutor);
+        }
+        return removedLatestFuture;
+    }
+
+    private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
+        long startTs = 0;
+        long endTs = query.getStartTs() - 1;
+        ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
+                Aggregation.NONE, DESC_ORDER);
+        ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery);
+
+        return Futures.transformAsync(future, entryList -> {
+            if (entryList.size() == 1) {
+                return saveLatest(entityId, entryList.get(0));
+            } else {
+                log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
+            }
+            return Futures.immediateFuture(null);
+        }, readResultsProcessingExecutor);
+    }
+
+    private ListenableFuture<Void> deleteLatest(EntityId entityId, String key) {
+        Statement delete = QueryBuilder.delete().all().from(ModelConstants.TS_KV_LATEST_CF)
+                .where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityId.getEntityType()))
+                .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId.getId()))
+                .and(eq(ModelConstants.KEY_COLUMN, key));
+        log.debug("Remove request: {}", delete.toString());
+        return getFuture(executeAsyncWrite(delete), rs -> null);
+    }
+
+    @Override
+    public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
+        long minPartition = toPartitionTs(query.getStartTs());
+        long maxPartition = toPartitionTs(query.getEndTs());
+        if (minPartition == maxPartition) {
+            return Futures.immediateFuture(null);
+        } else {
+            ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
+
+            final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+            final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
+
+            Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
+                @Override
+                public void onSuccess(@Nullable List<Long> partitions) {
+                    int index = 0;
+                    if (minPartition != query.getStartTs()) {
+                        index = 1;
+                    }
+                    List<Long> partitionsToDelete = new ArrayList<>();
+                    for (int i = index; i < partitions.size() - 1; i++) {
+                        partitionsToDelete.add(partitions.get(i));
+                    }
+                    QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete);
+                    deletePartitionAsync(cursor, resultFuture);
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
+                }
+            }, readResultsProcessingExecutor);
+            return resultFuture;
+        }
+    }
+
+    private void deletePartitionAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
+        if (!cursor.hasNextPartition()) {
+            resultFuture.set(null);
+        } else {
+            PreparedStatement proto = getDeletePartitionStmt();
+            BoundStatement stmt = proto.bind();
+            stmt.setString(0, cursor.getEntityType());
+            stmt.setUUID(1, cursor.getEntityId());
+            stmt.setLong(2, cursor.getNextPartition());
+            stmt.setString(3, cursor.getKey());
+
+            Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
+                @Override
+                public void onSuccess(@Nullable ResultSet result) {
+                    deletePartitionAsync(cursor, resultFuture);
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
+                }
+            }, readResultsProcessingExecutor);
+        }
+    }
+
+    private PreparedStatement getDeletePartitionStmt() {
+        if (deletePartitionStmt == null) {
+            deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF +
+                    " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+                    + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM);
+        }
+        return deletePartitionStmt;
+    }
+
     private List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows) {
         List<TsKvEntry> entries = new ArrayList<>(rows.size());
         if (!rows.isEmpty()) {
@@ -459,28 +658,43 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
         return saveTtlStmts[dataType.ordinal()];
     }
 
-    private PreparedStatement getFetchStmt(Aggregation aggType) {
-        if (fetchStmts == null) {
-            fetchStmts = new PreparedStatement[Aggregation.values().length];
-            for (Aggregation type : Aggregation.values()) {
-                if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
-                    fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
-                } else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
-                    fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
-                } else {
-                    fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
-                            String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
-                            + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
-                            + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
-                            + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
-                            + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
-                            + "AND " + ModelConstants.TS_COLUMN + " > ? "
-                            + "AND " + ModelConstants.TS_COLUMN + " <= ?"
-                            + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " DESC LIMIT ?" : ""));
+    private PreparedStatement getFetchStmt(Aggregation aggType, String orderBy) {
+        switch (orderBy) {
+            case ASC_ORDER:
+                if (fetchStmtsAsc == null) {
+                    fetchStmtsAsc = initFetchStmt(orderBy);
+                }
+                return fetchStmtsAsc[aggType.ordinal()];
+            case DESC_ORDER:
+                if (fetchStmtsDesc == null) {
+                    fetchStmtsDesc = initFetchStmt(orderBy);
                 }
+                return fetchStmtsDesc[aggType.ordinal()];
+            default:
+                throw new RuntimeException("Not supported" + orderBy + "order!");
+        }
+    }
+
+    private PreparedStatement[] initFetchStmt(String orderBy) {
+        PreparedStatement[] fetchStmts = new PreparedStatement[Aggregation.values().length];
+        for (Aggregation type : Aggregation.values()) {
+            if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
+                fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
+            } else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
+                fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
+            } else {
+                fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
+                        String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
+                        + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+                        + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+                        + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+                        + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+                        + "AND " + ModelConstants.TS_COLUMN + " > ? "
+                        + "AND " + ModelConstants.TS_COLUMN + " <= ?"
+                        + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
             }
         }
-        return fetchStmts[aggType.ordinal()];
+        return fetchStmts;
     }
 
     private PreparedStatement getLatestStmt() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java
new file mode 100644
index 0000000..f210608
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java
@@ -0,0 +1,60 @@
+/**
+ * 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.server.dao.timeseries;
+
+import lombok.Getter;
+import org.thingsboard.server.common.data.kv.TsKvQuery;
+
+import java.util.List;
+import java.util.UUID;
+
+public class QueryCursor {
+
+    @Getter
+    protected final String entityType;
+    @Getter
+    protected final UUID entityId;
+    @Getter
+    protected final String key;
+    @Getter
+    private final long startTs;
+    @Getter
+    private final long endTs;
+
+    final List<Long> partitions;
+    private int partitionIndex;
+
+    public QueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
+        this.entityType = entityType;
+        this.entityId = entityId;
+        this.key = baseQuery.getKey();
+        this.startTs = baseQuery.getStartTs();
+        this.endTs = baseQuery.getEndTs();
+        this.partitions = partitions;
+        this.partitionIndex = partitions.size() - 1;
+    }
+
+    public boolean hasNextPartition() {
+        return partitionIndex >= 0;
+    }
+
+    public long getNextPartition() {
+        long partition = partitions.get(partitionIndex);
+        partitionIndex--;
+        return partition;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
index 1e3f4ce..a8cb547 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
@@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
 
 import com.google.common.util.concurrent.ListenableFuture;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 
 import java.util.List;
 
@@ -27,7 +28,7 @@ import java.util.List;
  */
 public interface TimeseriesDao {
 
-    ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries);
+    ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries);
 
     ListenableFuture<TsKvEntry> findLatest(EntityId entityId, String key);
 
@@ -38,4 +39,10 @@ public interface TimeseriesDao {
     ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl);
 
     ListenableFuture<Void> saveLatest(EntityId entityId, TsKvEntry tsKvEntry);
+
+    ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query);
+
+    ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query);
+
+    ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
index 2cd2d8d..c5076d2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
@@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
 
 import com.google.common.util.concurrent.ListenableFuture;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 
 import java.util.Collection;
 import java.util.List;
@@ -28,7 +29,7 @@ import java.util.List;
  */
 public interface TimeseriesService {
 
-    ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries);
+    ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries);
 
     ListenableFuture<List<TsKvEntry>> findLatest(EntityId entityId, Collection<String> keys);
 
@@ -37,4 +38,6 @@ public interface TimeseriesService {
     ListenableFuture<List<Void>> save(EntityId entityId, TsKvEntry tsKvEntry);
 
     ListenableFuture<List<Void>> save(EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
+
+    ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> queries);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java
index d6b6bbd..b56be56 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java
@@ -16,57 +16,53 @@
 package org.thingsboard.server.dao.timeseries;
 
 import lombok.Getter;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import static org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao.DESC_ORDER;
+
 /**
  * Created by ashvayka on 21.02.17.
  */
-public class TsKvQueryCursor {
-    @Getter
-    private final String entityType;
-    @Getter
-    private final UUID entityId;
-    @Getter
-    private final String key;
-    @Getter
-    private final long startTs;
-    @Getter
-    private final long endTs;
-    private final List<Long> partitions;
+public class TsKvQueryCursor extends QueryCursor {
+
     @Getter
     private final List<TsKvEntry> data;
+    @Getter
+    private String orderBy;
 
     private int partitionIndex;
     private int currentLimit;
 
-    public TsKvQueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
-        this.entityType = entityType;
-        this.entityId = entityId;
-        this.key = baseQuery.getKey();
-        this.startTs = baseQuery.getStartTs();
-        this.endTs = baseQuery.getEndTs();
-        this.partitions = partitions;
-        this.partitionIndex = partitions.size() - 1;
+    public TsKvQueryCursor(String entityType, UUID entityId, ReadTsKvQuery baseQuery, List<Long> partitions) {
+        super(entityType, entityId, baseQuery, partitions);
+        this.orderBy = baseQuery.getOrderBy();
+        this.partitionIndex = isDesc() ? partitions.size() - 1 : 0;
         this.data = new ArrayList<>();
         this.currentLimit = baseQuery.getLimit();
     }
 
+    @Override
     public boolean hasNextPartition() {
-        return partitionIndex >= 0;
+        return isDesc() ? partitionIndex >= 0 : partitionIndex <= partitions.size() - 1;
     }
 
     public boolean isFull() {
         return currentLimit <= 0;
     }
 
+    @Override
     public long getNextPartition() {
         long partition = partitions.get(partitionIndex);
-        partitionIndex--;
+        if (isDesc()) {
+            partitionIndex--;
+        } else {
+            partitionIndex++;
+        }
         return partition;
     }
 
@@ -78,4 +74,8 @@ public class TsKvQueryCursor {
         currentLimit -= newData.size();
         data.addAll(newData);
     }
+
+    private boolean isDesc() {
+        return orderBy.equals(DESC_ORDER);
+    }
 }
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
index f045bf1..4528d9a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
@@ -21,7 +21,8 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.kv.Aggregation;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
@@ -53,6 +54,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
     private static final String BOOLEAN_KEY = "booleanKey";
 
     private static final long TS = 42L;
+    private static final String DESC_ORDER = "DESC";
 
     KvEntry stringKvEntry = new StringDataEntry(STRING_KEY, "value");
     KvEntry longKvEntry = new LongDataEntry(LONG_KEY, Long.MAX_VALUE);
@@ -101,6 +103,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
     }
 
     @Test
+    public void testDeleteDeviceTsData() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+        saveEntries(deviceId, 10000);
+        saveEntries(deviceId, 20000);
+        saveEntries(deviceId, 30000);
+        saveEntries(deviceId, 40000);
+
+        tsService.remove(deviceId, Collections.singletonList(
+                new BaseDeleteTsKvQuery(STRING_KEY, 15000, 45000))).get();
+
+        List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(
+                new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
+        Assert.assertEquals(1, list.size());
+
+        List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get();
+        Assert.assertEquals(null, latest.get(0).getValueAsString());
+    }
+
+    @Test
     public void testFindDeviceTsData() throws Exception {
         DeviceId deviceId = new DeviceId(UUIDs.timeBased());
         List<TsKvEntry> entries = new ArrayList<>();
@@ -114,7 +136,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         entries.add(save(deviceId, 45000, 500));
         entries.add(save(deviceId, 55000, 600));
 
-        List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.NONE))).get();
         assertEquals(3, list.size());
         assertEquals(55000, list.get(0).getTs());
@@ -126,7 +148,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(35000, list.get(2).getTs());
         assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
 
-        list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.AVG))).get();
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
@@ -138,7 +160,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(50000, list.get(2).getTs());
         assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
 
-        list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.SUM))).get();
 
         assertEquals(3, list.size());
@@ -151,7 +173,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(50000, list.get(2).getTs());
         assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
 
-        list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.MIN))).get();
 
         assertEquals(3, list.size());
@@ -164,7 +186,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(50000, list.get(2).getTs());
         assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
 
-        list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.MAX))).get();
 
         assertEquals(3, list.size());
@@ -177,7 +199,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(50000, list.get(2).getTs());
         assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
 
-        list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+        list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
                 60000, 20000, 3, Aggregation.COUNT))).get();
 
         assertEquals(3, list.size());
diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
index a59d83b..bcb8bd1 100644
--- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
+++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
@@ -40,6 +40,8 @@ public final class MqttClientConfig {
     private Class<? extends Channel> channelClass = NioSocketChannel.class;
 
     private boolean reconnect = true;
+    private long reconnectDelay = 1L;
+    private int maxBytesInMessage = 8092;
 
     public MqttClientConfig() {
         this(null);
@@ -146,4 +148,38 @@ public final class MqttClientConfig {
     public void setReconnect(boolean reconnect) {
         this.reconnect = reconnect;
     }
+
+    public long getReconnectDelay() {
+        return reconnectDelay;
+    }
+
+    /**
+     * Sets the reconnect delay in seconds. Defaults to 1 second.
+     * @param reconnectDelay
+     * @throws IllegalArgumentException if reconnectDelay is smaller than 1.
+     */
+    public void setReconnectDelay(long reconnectDelay) {
+        if (reconnectDelay <= 0) {
+            throw new IllegalArgumentException("reconnectDelay must be > 0");
+        }
+        this.reconnectDelay = reconnectDelay;
+    }
+
+    public int getMaxBytesInMessage() {
+        return maxBytesInMessage;
+    }
+
+    /**
+     * Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}.
+     * Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec.
+     *
+     * @param maxBytesInMessage
+     * @throws IllegalArgumentException if maxBytesInMessage is smaller than 1 or greater than 256_000_000.
+     */
+    public void setMaxBytesInMessage(int maxBytesInMessage) {
+        if (maxBytesInMessage <= 0 || maxBytesInMessage > 256_000_000) {
+            throw new IllegalArgumentException("maxBytesInMessage must be > 0 or < 256_000_000");
+        }
+        this.maxBytesInMessage = maxBytesInMessage;
+    }
 }
diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
index a5df846..b9460b3 100644
--- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
+++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
@@ -155,7 +155,7 @@ final class MqttClientImpl implements MqttClient {
             if (reconnect) {
                 this.reconnect = true;
             }
-            eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), 1L, TimeUnit.SECONDS);
+            eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), clientConfig.getReconnectDelay(), TimeUnit.SECONDS);
         }
     }
 
@@ -512,7 +512,7 @@ final class MqttClientImpl implements MqttClient {
                 ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port));
             }
 
-            ch.pipeline().addLast("mqttDecoder", new MqttDecoder());
+            ch.pipeline().addLast("mqttDecoder", new MqttDecoder(clientConfig.getMaxBytesInMessage()));
             ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE);
             ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0));
             ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
index aa57a02..c70b906 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
@@ -16,11 +16,14 @@
 package org.thingsboard.rule.engine.api;
 
 import com.google.common.util.concurrent.FutureCallback;
+import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Created by ashvayka on 02.04.18.
@@ -41,4 +44,6 @@ public interface RuleEngineTelemetryService {
 
     void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
 
+    void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes);
+
 }
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
new file mode 100644
index 0000000..260669f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
@@ -0,0 +1,169 @@
+/**
+ * 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.metadata;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+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.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.rule.engine.api.util.DonAsynchron;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+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.server.common.data.kv.Aggregation.NONE;
+
+/**
+ * Created by mshvayka on 04.09.18.
+ */
+@Slf4j
+@RuleNode(type = ComponentType.ENRICHMENT,
+        name = "originator telemetry",
+        configClazz = TbGetTelemetryNodeConfiguration.class,
+        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. " +
+                "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")
+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;
+
+    @Override
+    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();
+        mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
+        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        if (tsKeyNames.isEmpty()) {
+            ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!"));
+        } else {
+            try {
+                List<ReadTsKvQuery> queries = buildQueries();
+                ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(msg.getOriginator(), queries);
+                DonAsynchron.withCallback(list, data -> {
+                    process(data, msg);
+                    TbMsg newMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
+                    ctx.tellNext(newMsg, SUCCESS);
+                }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor());
+            } catch (Exception e) {
+                ctx.tellFailure(msg, e);
+            }
+        }
+    }
+
+    private List<ReadTsKvQuery> buildQueries() {
+        long ts = System.currentTimeMillis();
+        long startTs = ts - startTsOffset;
+        long endTs = ts - endTsOffset;
+        String orderBy;
+        if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
+            orderBy = "ASC";
+        } else {
+            orderBy = "DESC";
+        }
+        return tsKeyNames.stream()
+                .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
+                .collect(Collectors.toList());
+    }
+
+    private void process(List<TsKvEntry> entries, TbMsg msg) {
+        ObjectNode resultNode = mapper.createObjectNode();
+        if (limit == MAX_FETCH_SIZE) {
+            entries.forEach(entry -> processArray(resultNode, entry));
+        } else {
+            entries.forEach(entry -> processSingle(resultNode, entry));
+        }
+
+        for (String key : tsKeyNames) {
+            if (resultNode.has(key)) {
+                msg.getMetaData().putValue(key, resultNode.get(key).toString());
+            }
+        }
+    }
+
+    private void processSingle(ObjectNode node, TsKvEntry entry) {
+        node.put(entry.getKey(), entry.getValueAsString());
+    }
+
+    private void processArray(ObjectNode node, TsKvEntry entry) {
+        if (node.has(entry.getKey())) {
+            ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
+            ObjectNode obj = buildNode(entry);
+            arrayNode.add(obj);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+            ObjectNode obj = buildNode(entry);
+            arrayNode.add(obj);
+            node.set(entry.getKey(), arrayNode);
+        }
+    }
+
+    private ObjectNode buildNode(TsKvEntry entry) {
+        ObjectNode obj = mapper.createObjectNode()
+                .put("ts", entry.getTs());
+        switch (entry.getDataType()) {
+            case STRING:
+                obj.put("value", entry.getValueAsString());
+                break;
+            case LONG:
+                obj.put("value", entry.getLongValue().get());
+                break;
+            case BOOLEAN:
+                obj.put("value", entry.getBooleanValue().get());
+                break;
+            case DOUBLE:
+                obj.put("value", entry.getDoubleValue().get());
+                break;
+        }
+        return obj;
+    }
+
+    @Override
+    public void destroy() {
+
+    }
+}
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
new file mode 100644
index 0000000..37ce0af
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
@@ -0,0 +1,57 @@
+/**
+ * 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.metadata;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by mshvayka on 04.09.18.
+ */
+@Data
+public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetTelemetryNodeConfiguration> {
+
+    public static final String FETCH_MODE_FIRST = "FIRST";
+    public static final String FETCH_MODE_LAST = "LAST";
+    public static final String FETCH_MODE_ALL = "ALL";
+    public static final int MAX_FETCH_SIZE = 1000;
+
+    private int startInterval;
+    private int endInterval;
+    private String startIntervalTimeUnit;
+    private String endIntervalTimeUnit;
+    private String fetchMode; //FIRST, LAST, LATEST
+
+    private List<String> latestTsKeyNames;
+
+
+
+    @Override
+    public TbGetTelemetryNodeConfiguration defaultConfiguration() {
+        TbGetTelemetryNodeConfiguration configuration = new TbGetTelemetryNodeConfiguration();
+        configuration.setLatestTsKeyNames(Collections.emptyList());
+        configuration.setFetchMode("FIRST");
+        configuration.setStartIntervalTimeUnit(TimeUnit.MINUTES.name());
+        configuration.setStartInterval(2);
+        configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
+        configuration.setEndInterval(1);
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
index 4f82884..5cdb04e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
@@ -17,12 +17,15 @@ package org.thingsboard.rule.engine.telemetry;
 
 import com.google.gson.JsonParser;
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 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.TbNodeUtils;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.common.msg.TbMsg;
@@ -62,6 +65,9 @@ public class TbMsgAttributesNode implements TbNode {
         String src = msg.getData();
         Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
         ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
+        if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) {
+            ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes);
+        }
     }
 
     @Override
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 e06d253..2e0ab78 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,4 @@
-!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(76)},function(e,t){},1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=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 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 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> "},22,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(5),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(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$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(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.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(8),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(54),i=r(a),o=n(38),l=r(o),s=n(41),u=r(s),d=n(40),c=r(d),m=n(39),g=r(m),p=n(44),f=r(p),b=n(49),v=r(b),y=n(50),q=r(y),h=n(48),$=r(h),k=n(43),T=r(k),w=n(52),x=r(w),C=n(53),M=r(C),_=n(47),S=r(_),N=n(45),V=r(N),j=n(51),P=r(j),F=n(46),E=r(F);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",x.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",E.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(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$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(10),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(11),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(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.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(13),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(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.$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(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.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(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,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(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,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(21),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(22),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(23),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(60),i=r(a),o=n(61),l=r(o),s=n(58),u=r(s),d=n(62),c=r(d),m=n(57),g=r(m),p=n(63),f=r(p);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).name},function(e,t,n){"use strict";function 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(24),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){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(25),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(26),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(27),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(28),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(68),i=r(a),o=n(66),l=r(o),s=n(69),u=r(s),d=n(64),c=r(d),m=n(67),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(3);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,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(30),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(31),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(32),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(33),o=r(i);n(4)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(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.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(35),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(72),i=r(a),o=n(74),l=r(o),s=n(75),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(36),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(37),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(79),i=r(a),o=n(65),l=r(o),s=n(59),u=r(s),d=n(73),c=r(d),m=n(42),g=r(m),p=n(56),f=r(p),b=n(71),v=r(b),y=n(55),q=r(y),h=n(70),$=r(h),k=n(78),T=r(k);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:{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"},"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(77),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"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}]));
+!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(79)},function(e,t){},1,1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=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 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> "},23,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,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$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.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(9),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(56),i=a(r),o=n(40),l=a(o),s=n(43),u=a(s),d=n(42),c=a(d),m=n(41),g=a(m),p=n(46),f=a(p),b=n(51),v=a(b),y=n(52),q=a(y),h=n(50),$=a(h),T=n(45),k=a(T),w=n(54),x=a(w),C=n(55),M=a(C),S=n(49),_=a(S),N=n(47),V=a(N),E=n(53),P=a(E),j=n(48),F=a(j);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",x.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",F.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(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$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(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){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(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.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(14),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(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.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(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.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(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,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(22),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(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);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(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s);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(25),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(63),i=a(r),o=n(64),l=a(o),s=n(60),u=a(s),d=n(65),c=a(d),m=n(59),g=a(m),p=n(66),f=a(p),b=n(61),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(26),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(27),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(28),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(29),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(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(71),i=a(r),o=n(69),l=a(o),s=n(72),u=a(s),d=n(67),c=a(d),m=n(70),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(31),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(32),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(33),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(34),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){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(35),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(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.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(37),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(75),i=a(r),o=n(77),l=a(o),s=n(78),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(38),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(39),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(82),i=a(r),o=n(68),l=a(o),s=n(62),u=a(s),d=n(76),c=a(d),m=n(44),g=a(m),p=n(58),f=a(p),b=n(74),v=a(b),y=n(57),q=a(y),h=n(73),$=a(h),T=n(81),k=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"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"},"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(80),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
diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
index bac68ff..8a596d7 100644
--- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
+++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -113,6 +113,12 @@ public class CoapTransportResource extends CoapResource {
 
     @Override
     public void handlePOST(CoapExchange exchange) {
+        if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
+            log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
+            exchange.respond(ResponseCode.BAD_REQUEST);
+            return;
+        }
+
         Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
         if (!featureType.isPresent()) {
             log.trace("Missing feature type parameter");

ui/.stylelintrc 292(+292 -0)

diff --git a/ui/.stylelintrc b/ui/.stylelintrc
new file mode 100644
index 0000000..c145c8b
--- /dev/null
+++ b/ui/.stylelintrc
@@ -0,0 +1,292 @@
+{
+  "extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"],
+  "plugins": [
+    "stylelint-order"
+  ],
+  "rules": {
+    "at-rule-empty-line-before": ["always", {
+      "except": ["first-nested"],
+      "ignore": ["after-comment"]
+    }],
+    "at-rule-name-space-after": "always",
+    "at-rule-no-vendor-prefix": true,
+    "at-rule-semicolon-space-before": "never",
+    "block-closing-brace-empty-line-before": "never",
+    "block-closing-brace-newline-after": null,
+    "block-opening-brace-space-before": null,
+    "color-named": "never",
+    "declaration-block-semicolon-newline-after": "always-multi-line",
+    "declaration-block-semicolon-newline-before": "never-multi-line",
+    "declaration-block-semicolon-space-after": "always-single-line",
+    "declaration-empty-line-before": null,
+    "declaration-no-important": null,
+    "font-family-name-quotes": "always-where-recommended",
+    "font-weight-notation": [
+      "numeric", {
+        "ignore": ["relative"]
+    }],
+    "function-url-no-scheme-relative": true,
+    "function-url-quotes": "always",
+    "length-zero-no-unit": true,
+    "max-empty-lines": 2,
+    "max-line-length": null,
+    "media-feature-name-no-vendor-prefix": true,
+    "media-feature-parentheses-space-inside": "never",
+    "media-feature-range-operator-space-after": "always",
+    "media-feature-range-operator-space-before": "never",
+    "no-descending-specificity": null,
+    "no-duplicate-selectors": true,
+    "number-leading-zero": "never",
+    "media-feature-name-no-unknown": [true, {
+      "ignoreMediaFeatureNames": ["prefers-reduced-motion"]
+    }],
+    "order/properties-order": [
+      "position",
+      "top",
+      "right",
+      "bottom",
+      "left",
+      "z-index",
+      "box-sizing",
+      "display",
+      "flex",
+      "flex-align",
+      "flex-basis",
+      "flex-direction",
+      "flex-wrap",
+      "flex-flow",
+      "flex-shrink",
+      "flex-grow",
+      "flex-order",
+      "flex-pack",
+      "align-content",
+      "align-items",
+      "align-self",
+      "justify-content",
+      "order",
+      "float",
+      "width",
+      "min-width",
+      "max-width",
+      "height",
+      "min-height",
+      "max-height",
+      "padding",
+      "padding-top",
+      "padding-right",
+      "padding-bottom",
+      "padding-left",
+      "margin",
+      "margin-top",
+      "margin-right",
+      "margin-bottom",
+      "margin-left",
+      "overflow",
+      "overflow-x",
+      "overflow-y",
+      "-webkit-overflow-scrolling",
+      "-ms-overflow-x",
+      "-ms-overflow-y",
+      "-ms-overflow-style",
+      "columns",
+      "column-count",
+      "column-fill",
+      "column-gap",
+      "column-rule",
+      "column-rule-width",
+      "column-rule-style",
+      "column-rule-color",
+      "column-span",
+      "column-width",
+      "orphans",
+      "widows",
+      "clip",
+      "clear",
+      "font",
+      "font-family",
+      "font-size",
+      "font-style",
+      "font-weight",
+      "font-variant",
+      "font-size-adjust",
+      "font-stretch",
+      "font-effect",
+      "font-emphasize",
+      "font-emphasize-position",
+      "font-emphasize-style",
+      "font-smooth",
+      "src",
+      "hyphens",
+      "line-height",
+      "color",
+      "text-align",
+      "text-align-last",
+      "text-emphasis",
+      "text-emphasis-color",
+      "text-emphasis-style",
+      "text-emphasis-position",
+      "text-decoration",
+      "text-indent",
+      "text-justify",
+      "text-outline",
+      "-ms-text-overflow",
+      "text-overflow",
+      "text-overflow-ellipsis",
+      "text-overflow-mode",
+      "text-shadow",
+      "text-transform",
+      "text-wrap",
+      "-webkit-text-size-adjust",
+      "-ms-text-size-adjust",
+      "letter-spacing",
+      "-ms-word-break",
+      "word-break",
+      "word-spacing",
+      "-ms-word-wrap",
+      "word-wrap",
+      "overflow-wrap",
+      "tab-size",
+      "white-space",
+      "vertical-align",
+      "direction",
+      "unicode-bidi",
+      "list-style",
+      "list-style-position",
+      "list-style-type",
+      "list-style-image",
+      "pointer-events",
+      "-ms-touch-action",
+      "touch-action",
+      "cursor",
+      "visibility",
+      "zoom",
+      "table-layout",
+      "empty-cells",
+      "caption-side",
+      "border-spacing",
+      "border-collapse",
+      "content",
+      "quotes",
+      "counter-reset",
+      "counter-increment",
+      "resize",
+      "user-select",
+      "nav-index",
+      "nav-up",
+      "nav-right",
+      "nav-down",
+      "nav-left",
+      "background",
+      "background-color",
+      "background-image",
+      "filter",
+      "background-repeat",
+      "background-attachment",
+      "background-position",
+      "background-position-x",
+      "background-position-y",
+      "background-clip",
+      "background-origin",
+      "background-size",
+      "border",
+      "border-color",
+      "border-style",
+      "border-width",
+      "border-top",
+      "border-top-color",
+      "border-top-style",
+      "border-top-width",
+      "border-right",
+      "border-right-color",
+      "border-right-style",
+      "border-right-width",
+      "border-bottom",
+      "border-bottom-color",
+      "border-bottom-style",
+      "border-bottom-width",
+      "border-left",
+      "border-left-color",
+      "border-left-style",
+      "border-left-width",
+      "border-radius",
+      "border-top-left-radius",
+      "border-top-right-radius",
+      "border-bottom-right-radius",
+      "border-bottom-left-radius",
+      "border-image",
+      "border-image-source",
+      "border-image-slice",
+      "border-image-width",
+      "border-image-outset",
+      "border-image-repeat",
+      "outline",
+      "outline-width",
+      "outline-style",
+      "outline-color",
+      "outline-offset",
+      "box-shadow",
+      "opacity",
+      "-ms-interpolation-mode",
+      "page-break-after",
+      "page-break-before",
+      "page-break-inside",
+      "transition",
+      "transition-delay",
+      "transition-timing-function",
+      "transition-duration",
+      "transition-property",
+      "transform",
+      "transform-origin",
+      "perspective",
+      "appearance",
+      "animation",
+      "animation-name",
+      "animation-duration",
+      "animation-play-state",
+      "animation-timing-function",
+      "animation-delay",
+      "animation-iteration-count",
+      "animation-direction",
+      "animation-fill-mode",
+      "fill",
+      "stroke"
+    ],
+    "property-no-vendor-prefix": null,
+    "rule-empty-line-before": ["always", {
+      "except": ["first-nested"],
+      "ignore": ["after-comment"]
+    }],
+    "scss/dollar-variable-default": [true, { "ignore": "local" }],
+    "selector-attribute-quotes": "always",
+    "selector-list-comma-newline-after": "always",
+    "selector-list-comma-newline-before": "never-multi-line",
+    "selector-list-comma-space-after": "always-single-line",
+    "selector-list-comma-space-before": "never-single-line",
+    "selector-max-attribute": 2,
+    "selector-max-class": 6,
+    "selector-max-combinators": 8,
+    "selector-max-compound-selectors": 9,
+    "selector-max-empty-lines": 1,
+    "selector-max-id": 1,
+    "selector-max-specificity": null,
+    "selector-max-type": 5,
+    "selector-max-universal": 1,
+    "selector-no-qualifying-type": null,
+    "selector-no-vendor-prefix": null,
+    "selector-type-no-unknown": [true, {
+      "ignoreTypes": [
+        "/^md-/",
+        "/^mdp-/",
+        "/^ng-/",
+        "/^tb-/",
+        "/^v-pane/"
+      ]
+    }],
+    "string-quotes": "double",
+    "value-keyword-case": "lower",
+    "value-list-comma-newline-after": "always-multi-line",
+    "value-list-comma-newline-before": "never-multi-line",
+    "value-list-comma-space-after": "always-single-line",
+    "value-no-vendor-prefix": null
+  }
+}

ui/package.json 12(+9 -3)

diff --git a/ui/package.json b/ui/package.json
index 1b8ceda..498de06 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -100,6 +100,7 @@
     "copy-webpack-plugin": "^3.0.1",
     "cross-env": "^3.2.4",
     "css-loader": "^0.25.0",
+    "directory-tree": "^2.1.0",
     "eslint": "^3.4.0",
     "eslint-config-angular": "^0.5.0",
     "eslint-loader": "^1.5.0",
@@ -112,6 +113,7 @@
     "html-minifier-loader": "^1.3.4",
     "html-webpack-plugin": "^2.30.1",
     "img-loader": "^1.3.1",
+    "jsonminify": "^0.4.1",
     "less": "^2.7.1",
     "less-loader": "^2.2.3",
     "ng-annotate-loader": "^0.1.1",
@@ -122,14 +124,18 @@
     "react-hot-loader": "^3.0.0-beta.6",
     "sass-loader": "^4.0.2",
     "style-loader": "^0.13.1",
+    "stylelint": "^9.5.0",
+    "stylelint-config-recommended-scss": "^3.2.0",
+    "stylelint-config-standard": "^18.2.0",
+    "stylelint-order": "^1.0.0",
+    "stylelint-scss": "^3.3.0",
+    "stylelint-webpack-plugin": "^0.10.5",
     "url-loader": "^0.5.7",
     "webpack": "^1.13.2",
     "webpack-dev-middleware": "^1.6.1",
     "webpack-dev-server": "^1.15.1",
     "webpack-hot-middleware": "^2.12.2",
-    "webpack-material-design-icons": "^0.1.0",
-    "directory-tree": "^2.1.0",
-    "jsonminify": "^0.4.1"
+    "webpack-material-design-icons": "^0.1.0"
   },
   "engine": "node >= 5.9.0",
   "nyc": {
diff --git a/ui/src/app/alarm/alarm.scss b/ui/src/app/alarm/alarm.scss
index faf7feb..2f6f459 100644
--- a/ui/src/app/alarm/alarm.scss
+++ b/ui/src/app/alarm/alarm.scss
@@ -13,26 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-alarm-container {
   overflow-x: auto;
 }
 
 md-list.tb-alarm-table {
-  padding: 0px;
   min-width: 700px;
+  padding: 0;
 
   md-list-item {
-    padding: 0px;
+    padding: 0;
   }
 
   .tb-row {
     height: 48px;
-    padding: 0px;
+    padding: 0;
     overflow: hidden;
   }
 
   .tb-row:hover {
-    background-color: #EEEEEE;
+    background-color: #eee;
   }
 
   .tb-header:hover {
@@ -41,9 +42,9 @@ md-list.tb-alarm-table {
 
   .tb-header {
     .tb-cell {
-      color: rgba(0,0,0,.54);
       font-size: 12px;
       font-weight: 700;
+      color: rgba(0, 0, 0, .54);
       white-space: nowrap;
       background: none;
     }
@@ -52,11 +53,12 @@ md-list.tb-alarm-table {
   .tb-cell {
     padding: 0 24px;
     margin: auto 0;
-    color: rgba(0,0,0,.87);
+    overflow: hidden;
     font-size: 13px;
-    vertical-align: middle;
+    color: rgba(0, 0, 0, .87);
     text-align: left;
-    overflow: hidden;
+    vertical-align: middle;
+
     .md-button {
       padding: 0;
       margin: 0;
@@ -66,12 +68,11 @@ md-list.tb-alarm-table {
   .tb-cell.tb-number {
     text-align: right;
   }
-
 }
 
 #tb-alarm-content {
-  min-width: 400px;
-  min-height: 50px;
   width: 100%;
+  min-width: 400px;
   height: 100%;
+  min-height: 50px;
 }
diff --git a/ui/src/app/alarm/alarm-details-dialog.scss b/ui/src/app/alarm/alarm-details-dialog.scss
index b7d3cd0..ddae1f1 100644
--- a/ui/src/app/alarm/alarm-details-dialog.scss
+++ b/ui/src/app/alarm/alarm-details-dialog.scss
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-alarm-details-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: 100%;
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   #tb-alarm-details {
-    min-width: 600px;
-    min-height: 200px;
     width: 100%;
+    min-width: 600px;
     height: 100%;
+    min-height: 200px;
   }
 }
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 350b1ef..a66c4b1 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -267,6 +267,14 @@ export default class Subscription {
         } else {
             this.startWatchingTimewindow();
         }
+        registration = this.ctx.$scope.$watch(function () {
+            return subscription.alarmSearchStatus;
+        }, function (newAlarmSearchStatus, prevAlarmSearchStatus) {
+            if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) {
+                subscription.update();
+            }
+        }, true);
+        this.registrations.push(registration);
     }
 
     initDataSubscription() {
diff --git a/ui/src/app/audit/audit-log.scss b/ui/src/app/audit/audit-log.scss
index 7cae107..671cdc2 100644
--- a/ui/src/app/audit/audit-log.scss
+++ b/ui/src/app/audit/audit-log.scss
@@ -13,17 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-audit-logs {
   background-color: #fff;
+
   .tb-audit-log-margin-18px {
     margin-bottom: 18px;
   }
+
   .tb-audit-log-toolbar {
     font-size: 20px;
   }
+
   md-input-container.tb-audit-log-search-input {
     .md-errors-spacer {
-      min-height: 0px;
+      min-height: 0;
     }
   }
 }
@@ -32,27 +36,26 @@
   overflow-x: auto;
 }
 
-
-
 md-list.tb-audit-log-table {
-  padding: 0px;
   min-width: 700px;
+  padding: 0;
+
   &.tb-audit-log-table-full {
     min-width: 900px;
   }
 
   md-list-item {
-    padding: 0px;
+    padding: 0;
   }
 
   .tb-row {
     height: 48px;
-    padding: 0px;
+    padding: 0;
     overflow: hidden;
   }
 
   .tb-row:hover {
-    background-color: #EEEEEE;
+    background-color: #eee;
   }
 
   .tb-header:hover {
@@ -61,9 +64,9 @@ md-list.tb-audit-log-table {
 
   .tb-header {
     .tb-cell {
-      color: rgba(0,0,0,.54);
       font-size: 12px;
       font-weight: 700;
+      color: rgba(0, 0, 0, .54);
       white-space: nowrap;
       background: none;
     }
@@ -72,11 +75,12 @@ md-list.tb-audit-log-table {
   .tb-cell {
     padding: 0 24px;
     margin: auto 0;
-    color: rgba(0,0,0,.87);
+    overflow: hidden;
     font-size: 13px;
-    vertical-align: middle;
+    color: rgba(0, 0, 0, .87);
     text-align: left;
-    overflow: hidden;
+    vertical-align: middle;
+
     .md-button {
       padding: 0;
       margin: 0;
@@ -86,5 +90,4 @@ md-list.tb-audit-log-table {
   .tb-cell.tb-number {
     text-align: right;
   }
-
 }
diff --git a/ui/src/app/audit/audit-log-details-dialog.scss b/ui/src/app/audit/audit-log-details-dialog.scss
index 1a1507c..e15fafe 100644
--- a/ui/src/app/audit/audit-log-details-dialog.scss
+++ b/ui/src/app/audit/audit-log-details-dialog.scss
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#tb-audit-log-action-data, #tb-audit-log-failure-details {
-  min-width: 400px;
-  min-height: 50px;
+
+#tb-audit-log-action-data,
+#tb-audit-log-failure-details {
   width: 100%;
+  min-width: 400px;
   height: 100%;
-  border: 1px solid #C0C0C0;
-}
\ No newline at end of file
+  min-height: 50px;
+  border: 1px solid #c0c0c0;
+}
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
index ca99fcc..e0a9afd 100644
--- a/ui/src/app/components/dashboard.scss
+++ b/ui/src/app/components/dashboard.scss
@@ -21,6 +21,7 @@ div.tb-widget {
   margin: 0;
   overflow: hidden;
   outline: none;
+
   @include transition(all .2s ease-in-out);
 
   .tb-widget-title {
@@ -32,7 +33,7 @@ div.tb-widget {
 
     tb-timewindow {
       font-size: 14px;
-      opacity: 0.85;
+      opacity: .85;
     }
   }
 
@@ -44,17 +45,19 @@ div.tb-widget {
     margin: 0;
 
     .md-button.md-icon-button {
-      margin: 0 !important;
-      padding: 0 !important;
-      line-height: 20px;
       width: 32px;
-      height: 32px;
       min-width: 32px;
+      height: 32px;
       min-height: 32px;
-      md-icon, ng-md-icon {
+      padding: 0 !important;
+      margin: 0 !important;
+      line-height: 20px;
+
+      md-icon,
+      ng-md-icon {
         width: 20px;
-        height: 20px;
         min-width: 20px;
+        height: 20px;
         min-height: 20px;
         font-size: 20px;
       }
@@ -63,8 +66,8 @@ div.tb-widget {
 
   .tb-widget-content {
     tb-widget {
-      width: 100%;
       position: relative;
+      width: 100%;
     }
   }
 }
@@ -75,40 +78,41 @@ div.tb-widget.tb-highlighted {
 }
 
 div.tb-widget.tb-not-highlighted {
-  opacity: 0.5;
+  opacity: .5;
 }
 
 tb-dashboard {
   position: absolute;
   top: 0;
-  left: 0;
   right: 0;
   bottom: 0;
+  left: 0;
 }
 
 md-content.tb-dashboard-content {
   position: absolute;
   top: 0;
-  left: 0;
   right: 0;
   bottom: 0;
-  outline: none;
+  left: 0;
   background: none;
+  outline: none;
+
   .gridster-item {
-      @include transition(none);
+    @include transition(none);
   }
 }
 
 .tb-widget-error-container {
   position: absolute;
-  background-color: #fff;
   width: 100%;
   height: 100%;
+  background-color: #fff;
 }
 
 .tb-widget-error-msg {
-  color: red;
+  padding: 5px;
   font-size: 16px;
+  color: #f00;
   word-wrap: break-word;
-  padding: 5px;
 }
diff --git a/ui/src/app/components/dashboard-autocomplete.scss b/ui/src/app/components/dashboard-autocomplete.scss
index 9541fe1..8727fa6 100644
--- a/ui/src/app/components/dashboard-autocomplete.scss
+++ b/ui/src/app/components/dashboard-autocomplete.scss
@@ -13,16 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-dashboard-autocomplete {
   .tb-not-found {
     display: block;
-    line-height: 1.5;
     height: 48px;
+    line-height: 1.5;
   }
+
   .tb-dashboard-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/components/dashboard-select.scss b/ui/src/app/components/dashboard-select.scss
index 1dee53d..9ca36b6 100644
--- a/ui/src/app/components/dashboard-select.scss
+++ b/ui/src/app/components/dashboard-select.scss
@@ -13,18 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 tb-dashboard-select {
   min-width: 52px;
+
   md-select {
-    pointer-events: all;
     max-width: 300px;
+    pointer-events: all;
   }
 }
 
 .tb-dashboard-select {
   min-height: 32px;
+
   span {
     pointer-events: all;
     cursor: pointer;
@@ -38,23 +40,27 @@ tb-dashboard-select {
 }
 
 .tb-dashboard-select-panel {
+  min-width: 300px;
+  max-width: 320px;
   max-height: 150px;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
   @media (min-height: 350px) {
     max-height: 250px;
   }
-  max-width: 320px;
+
   @media (min-width: $layout-breakpoint-xs) {
     max-width: 100%;
   }
-  min-width: 300px;
-  background: white;
-  border-radius: 4px;
-  box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
-  0 13px 19px 2px rgba(0, 0, 0, 0.14),
-  0 5px 24px 4px rgba(0, 0, 0, 0.12);
-  overflow-x: hidden;
-  overflow-y: auto;
+
   md-content {
     background-color: #fff;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/datakey-config.scss b/ui/src/app/components/datakey-config.scss
index 0de8b63..646a18a 100644
--- a/ui/src/app/components/datakey-config.scss
+++ b/ui/src/app/components/datakey-config.scss
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-datakey-config {
   min-width: 500px !important;
   min-height: 500px !important;
+
   md-content {
     background-color: #fff;
   }
diff --git a/ui/src/app/components/datasource.scss b/ui/src/app/components/datasource.scss
index bb5f827..d8096bd 100644
--- a/ui/src/app/components/datasource.scss
+++ b/ui/src/app/components/datasource.scss
@@ -13,39 +13,43 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-datasource {
-   #entity-autocomplete {
-     height: 30px;
-     margin-top: 18px;
-     md-autocomplete-wrap {
-       height: 30px;
-     }
-     input, input:not(.md-input) {
-       height: 30px;
-     }
-   }
-   #datasourceType {
+  #entity-autocomplete {
+    height: 30px;
+    margin-top: 18px;
+
+    md-autocomplete-wrap {
+      height: 30px;
+    }
+
+    input,
+    input:not(.md-input) {
+      height: 30px;
+    }
   }
 }
 
 @mixin tb-checkered-bg() {
   background-color: #fff;
-  background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
-  linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
-  background-size: 8px 8px;
+  background-image:
+    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
+    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
   background-position: 0 0, 4px 4px;
+  background-size: 8px 8px;
 }
 
 .tb-color-preview {
-  content: '';
-  min-width: 24px;
+  position: relative;
   width: 24px;
+  min-width: 24px;
   height: 24px;
+  overflow: hidden;
+  content: "";
   border: 2px solid #fff;
   border-radius: 50%;
   box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
-  position: relative;
-  overflow: hidden;
+
   @include tb-checkered-bg();
 
   .tb-color-result {
@@ -60,6 +64,7 @@
     text-overflow: ellipsis;
     white-space: nowrap;
   }
+
   .tb-chip-separator {
     white-space: pre;
   }
diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss
index f054cab..681fed1 100644
--- a/ui/src/app/components/datasource-entity.scss
+++ b/ui/src/app/components/datasource-entity.scss
@@ -13,17 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
-.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
+.tb-entity-alias-autocomplete,
+.tb-timeseries-datakey-autocomplete,
+.tb-attribute-datakey-autocomplete,
+.tb-alarm-datakey-autocomplete {
   .tb-not-found {
     display: block;
-    line-height: 1.5;
     height: 48px;
+    line-height: 1.5;
+
     .tb-no-entries {
       line-height: 48px;
     }
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
@@ -32,13 +37,14 @@
 
 tb-datasource-entity {
   @media (min-width: $layout-breakpoint-sm) {
-    padding-left: 4px;
     padding-right: 4px;
+    padding-left: 4px;
   }
+
   tb-entity-alias-select {
     @media (min-width: $layout-breakpoint-sm) {
       width: 200px;
       max-width: 200px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss
index 4fc4281..ce4ca2b 100644
--- a/ui/src/app/components/datasource-func.scss
+++ b/ui/src/app/components/datasource-func.scss
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 .tb-datasource-func {
   @media (min-width: $layout-breakpoint-sm) {
@@ -22,19 +22,22 @@
 
   md-input-container.tb-datasource-name {
     .md-errors-spacer {
-        display: none;
+      display: none;
     }
   }
 
-  .tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
+  .tb-func-datakey-autocomplete,
+  .tb-alarm-datakey-autocomplete {
     .tb-not-found {
       display: block;
-      line-height: 1.5;
       height: 48px;
+      line-height: 1.5;
+
       .tb-no-entries {
         line-height: 48px;
       }
     }
+
     li {
       height: auto !important;
       white-space: normal !important;
diff --git a/ui/src/app/components/datetime-period.scss b/ui/src/app/components/datetime-period.scss
index 61009ad..8a690e4 100644
--- a/ui/src/app/components/datetime-period.scss
+++ b/ui/src/app/components/datetime-period.scss
@@ -13,13 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-datetime-period {
   md-input-container {
-    margin-bottom: 0px;
+    margin-bottom: 0;
+
     .md-errors-spacer {
-      min-height: 0px;
+      min-height: 0;
     }
   }
+
   mdp-date-picker {
     .md-input {
       width: 150px !important;
diff --git a/ui/src/app/components/datetime-period.tpl.html b/ui/src/app/components/datetime-period.tpl.html
index 605c3a8..7479576 100644
--- a/ui/src/app/components/datetime-period.tpl.html
+++ b/ui/src/app/components/datetime-period.tpl.html
@@ -18,14 +18,14 @@
 <section layout="column" layout-align="start start">
 	<section layout="row" layout-align="start start">
 	    <mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}"
-	       	mdp-max-date="maxStartDate"></mdp-date-picker>
+	       	></mdp-date-picker>
 	    <mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}"
-	    	mdp-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker>   	
+	    	mdp-auto-switch="true"></mdp-time-picker>
     </section>
 	<section layout="row" layout-align="start start">
 	    <mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}"
-	       	mdp-min-date="minEndDate" mdp-max-date="maxEndDate"></mdp-date-picker>
+	       	></mdp-date-picker>
 	    <mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}"
-	    	mdp-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker>   	
+	    	mdp-auto-switch="true"></mdp-time-picker>
     </section>
 </section>
\ No newline at end of file
diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss
index c7e9919..0873cfd 100644
--- a/ui/src/app/components/details-sidenav.scss
+++ b/ui/src/app/components/details-sidenav.scss
@@ -13,47 +13,51 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 .tb-details-title {
-  font-size: 1.000rem;
-  @media (min-width: $layout-breakpoint-gt-sm) {
-    font-size: 1.600rem;
-  }
-  font-weight: 400;
-  text-transform: uppercase;
+  width: inherit;
   margin: 20px 8px 0 0;
   overflow: hidden;
+  font-size: 1rem;
+  font-weight: 400;
   text-overflow: ellipsis;
+  text-transform: uppercase;
   white-space: nowrap;
-  width: inherit;
+
+  @media (min-width: $layout-breakpoint-gt-sm) {
+    font-size: 1.6rem;
+  }
 }
 
 .tb-details-subtitle {
-  font-size: 1.000rem;
+  width: inherit;
   margin: 10px 0;
-  opacity: 0.8;
   overflow: hidden;
+  font-size: 1rem;
   text-overflow: ellipsis;
   white-space: nowrap;
-  width: inherit;
+  opacity: .8;
 }
 
 md-sidenav.tb-sidenav-details {
   .md-toolbar-tools {
-      min-height: 100px;
-      max-height: 120px;
-      height: 100%;
+    height: 100%;
+    min-height: 100px;
+    max-height: 120px;
   }
+  z-index: 59 !important;
   width: 100% !important;
   max-width: 100% !important;
-  z-index: 59 !important;
+
   @media (min-width: $layout-breakpoint-gt-sm) {
     width: 80% !important;
   }
+
   @media (min-width: $layout-breakpoint-gt-md) {
     width: 65% !important;
   }
+
   tb-dashboard {
     md-content {
       background-color: $primary-hue-3;
diff --git a/ui/src/app/components/entity-alias-select.scss b/ui/src/app/components/entity-alias-select.scss
index d143933..d7f6278 100644
--- a/ui/src/app/components/entity-alias-select.scss
+++ b/ui/src/app/components/entity-alias-select.scss
@@ -13,15 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-entity-alias-autocomplete {
   .tb-not-found {
     display: block;
-    line-height: 1.5;
     height: 48px;
+    line-height: 1.5;
+
     .tb-no-entries {
       line-height: 48px;
     }
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/components/expand-fullscreen.scss b/ui/src/app/components/expand-fullscreen.scss
index eef599c..ae7e8b8 100644
--- a/ui/src/app/components/expand-fullscreen.scss
+++ b/ui/src/app/components/expand-fullscreen.scss
@@ -16,37 +16,38 @@
 @import "../../scss/constants";
 
 .tb-fullscreen {
-  section.header-buttons {
-    top: 25px;
-  }
-}
-
-.tb-fullscreen {
-  width: 100% !important;
-  height: 100% !important;
   position: fixed !important;
   top: 0;
   left: 0;
+  width: 100% !important;
+  height: 100% !important;
+
+  section.header-buttons {
+    top: 25px;
+  }
 }
 
 .tb-fullscreen-parent {
-  background-color: $gray;
-  width: 100%;
-  height: 100%;
   position: fixed;
   top: 0;
   left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: $gray;
 }
 
-.md-button.tb-fullscreen-button-style, .tb-fullscreen-button-style {
+.md-button.tb-fullscreen-button-style,
+.tb-fullscreen-button-style {
   background: #ccc;
-  opacity: 0.85;
+  opacity: .85;
+
   ng-md-icon {
     color: #666;
   }
 }
 
-.md-button.tb-fullscreen-button-pos, .tb-fullscreen-button-pos {
+.md-button.tb-fullscreen-button-pos,
+.tb-fullscreen-button-pos {
   position: absolute;
   top: 10px;
   right: 10px;
diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss
index 17054d5..4313848 100644
--- a/ui/src/app/components/grid.scss
+++ b/ui/src/app/components/grid.scss
@@ -21,15 +21,19 @@
 
 .tb-card-item {
   @include transition(all .2s ease-in-out);
+
   md-card-content {
-    padding-top: 0px;
     max-height: 53px;
+    padding-top: 0;
   }
+
   md-card-title {
     width: 100%;
+
     md-card-title-text {
-      max-height: 32px;
       min-width: 50%;
+      max-height: 32px;
+
       .md-headline {
         overflow: hidden;
         text-overflow: ellipsis;
@@ -40,14 +44,15 @@
 }
 
 .tb-current-item {
-  opacity: 0.5;
+  opacity: .5;
+
   @include transform(scale(1.05));
 }
 
 #tb-vertical-container {
   position: absolute;
   top: 0;
-  left: 0;
   right: 0;
   bottom: 0;
+  left: 0;
 }
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
index ade2830..affdc5b 100644
--- a/ui/src/app/components/js-func.scss
+++ b/ui/src/app/components/js-func.scss
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-js-func {
   position: relative;
+
   .tb-disabled {
-    color: rgba(0,0,0,0.38);
+    color: rgba(0, 0, 0, .38);
   }
+
   .fill-height {
     height: 100%;
   }
@@ -25,25 +28,27 @@ tb-js-func {
 
 .tb-js-func-toolbar {
   .md-button.tidy {
-    color: #7B7B7B;
     min-width: 32px;
     min-height: 15px;
-    line-height: 15px;
-    font-size: 0.800rem;
-    margin: 0 5px 0 0;
     padding: 4px;
-    background: rgba(220, 220, 220, 0.35);
+    margin: 0 5px 0 0;
+    font-size: .8rem;
+    line-height: 15px;
+    color: #7b7b7b;
+    background: rgba(220, 220, 220, .35);
   }
 }
 
 .tb-js-func-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: calc(100% - 80px);
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   #tb-javascript-input {
-    min-width: 200px;
     width: 100%;
+    min-width: 200px;
     height: 100%;
+
     &:not(.fill-height) {
       min-height: 200px;
     }
diff --git a/ui/src/app/components/json-content.scss b/ui/src/app/components/json-content.scss
index 287c7e3..4f245af 100644
--- a/ui/src/app/components/json-content.scss
+++ b/ui/src/app/components/json-content.scss
@@ -13,8 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-json-content {
   position: relative;
+
   .fill-height {
     height: 100%;
   }
@@ -22,25 +24,27 @@ tb-json-content {
 
 .tb-json-content-toolbar {
   .md-button.tidy {
-    color: #7B7B7B;
     min-width: 32px;
     min-height: 15px;
-    line-height: 15px;
-    font-size: 0.800rem;
-    margin: 0 5px 0 0;
     padding: 4px;
-    background: rgba(220, 220, 220, 0.35);
+    margin: 0 5px 0 0;
+    font-size: .8rem;
+    line-height: 15px;
+    color: #7b7b7b;
+    background: rgba(220, 220, 220, .35);
   }
 }
 
 .tb-json-content-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: 100%;
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   #tb-json-input {
-    min-width: 200px;
     width: 100%;
+    min-width: 200px;
     height: 100%;
+
     &:not(.fill-height) {
       min-height: 200px;
     }
diff --git a/ui/src/app/components/json-form.directive.js b/ui/src/app/components/json-form.directive.js
index 5749271..b016f57 100644
--- a/ui/src/app/components/json-form.directive.js
+++ b/ui/src/app/components/json-form.directive.js
@@ -82,6 +82,7 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
                     val = undefined;
                 }
                 selectOrSet(key, scope.model, val);
+                scope.formProps.model = scope.model;
             },
             onColorClick: function(event, key, val) {
                 scope.showColorPicker(event, val);
diff --git a/ui/src/app/components/json-form.scss b/ui/src/app/components/json-form.scss
index 66a5c1a..f102d4c 100644
--- a/ui/src/app/components/json-form.scss
+++ b/ui/src/app/components/json-form.scss
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-json-form {
-  overflow: auto;
   padding-bottom: 14px !important;
-}
\ No newline at end of file
+  overflow: auto;
+}
diff --git a/ui/src/app/components/json-object-edit.scss b/ui/src/app/components/json-object-edit.scss
index 232d69a..a2bdb6a 100644
--- a/ui/src/app/components/json-object-edit.scss
+++ b/ui/src/app/components/json-object-edit.scss
@@ -13,21 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-json-object-edit {
   position: relative;
+
   .fill-height {
     height: 100%;
   }
 }
 
 .tb-json-object-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: 100%;
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   #tb-json-input {
-    min-width: 200px;
     width: 100%;
+    min-width: 200px;
     height: 100%;
+
     &:not(.fill-height) {
       min-height: 200px;
     }
diff --git a/ui/src/app/components/kv-map.scss b/ui/src/app/components/kv-map.scss
index d51653c..35267d4 100644
--- a/ui/src/app/components/kv-map.scss
+++ b/ui/src/app/components/kv-map.scss
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-kv-map {
   span.no-data-found {
     position: relative;
+    display: flex;
     height: 40px;
     text-transform: uppercase;
-    display: flex;
+
     &.disabled {
-      color: rgba(0,0,0,0.38);
+      color: rgba(0, 0, 0, .38);
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/legend.scss b/ui/src/app/components/legend.scss
index 257a77f..e5e3f04 100644
--- a/ui/src/app/components/legend.scss
+++ b/ui/src/app/components/legend.scss
@@ -13,39 +13,49 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 table.tb-legend {
   width: 100%;
   font-size: 12px;
-  .tb-legend-header, .tb-legend-value {
-     text-align: right;
+
+  .tb-legend-header,
+  .tb-legend-value {
+    text-align: right;
   }
+
   .tb-legend-header {
     th {
-      color: rgb(255,110,64);
-      white-space: nowrap;
       padding: 0 10px 1px 0;
+      color: rgb(255, 110, 64);
+      white-space: nowrap;
     }
   }
+
   .tb-legend-keys {
-    td.tb-legend-label, td.tb-legend-value {
-      white-space: nowrap;
+    td.tb-legend-label,
+    td.tb-legend-value {
       padding: 2px 10px;
+      white-space: nowrap;
     }
+
     .tb-legend-line {
+      display: inline-block;
       width: 15px;
       height: 3px;
-      display: inline-block;
       vertical-align: middle;
     }
+
     .tb-legend-label {
       text-align: left;
       outline: none;
+
       &.tb-horizontal {
         width: 95%;
       }
+
       &.tb-hidden-label {
         text-decoration: line-through;
-        opacity: 0.6;
+        opacity: .6;
       }
     }
   }
diff --git a/ui/src/app/components/legend-config.scss b/ui/src/app/components/legend-config.scss
index fb10928..205d67d 100644
--- a/ui/src/app/components/legend-config.scss
+++ b/ui/src/app/components/legend-config.scss
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .md-panel {
   &.tb-legend-config-panel {
     position: absolute;
@@ -20,21 +21,26 @@
 }
 
 .tb-legend-config-panel {
-  max-height: 220px;
   min-width: 220px;
-  background: white;
-  border-radius: 4px;
-  box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
-  0 13px 19px 2px rgba(0, 0, 0, 0.14),
-  0 5px 24px 4px rgba(0, 0, 0, 0.12);
+  max-height: 220px;
   overflow: hidden;
-  form, fieldset {
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  form,
+  fieldset {
     height: 100%;
   }
+
   md-content {
-    background-color: #fff;
     overflow: hidden;
+    background-color: #fff;
   }
+
   .md-padding {
     padding: 0 16px;
   }
diff --git a/ui/src/app/components/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss
index 3048fd1..e50caff 100644
--- a/ui/src/app/components/material-icons-dialog.scss
+++ b/ui/src/app/components/material-icons-dialog.scss
@@ -13,18 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-material-icons-dialog {
   button.md-icon-button.tb-select-icon-button {
-    border: solid 1px orange;
-    border-radius: 0%;
-    padding: 16px;
-    height: 56px;
     width: 56px;
+    height: 56px;
+    padding: 16px;
     margin: 10px;
+    border: solid 1px #ffa500;
+    border-radius: 0%;
   }
+
   .tb-icons-load {
     top: 64px;
-    background: rgba(255,255,255,0.75);
     z-index: 3;
+    background: rgba(255, 255, 255, .75);
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/material-icon-select.scss b/ui/src/app/components/material-icon-select.scss
index e12e1d0..2814bb3 100644
--- a/ui/src/app/components/material-icon-select.scss
+++ b/ui/src/app/components/material-icon-select.scss
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-material-icon-select {
-    md-icon {
-        padding: 4px;
-        margin: 8px 4px 4px;
-        cursor: pointer;
-        border: solid 1px rgba(0,0,0,0.27);
-    }
-    md-input-container {
-        margin-bottom: 0px;
-    }
-}
\ No newline at end of file
+  md-icon {
+    padding: 4px;
+    margin: 8px 4px 4px;
+    cursor: pointer;
+    border: solid 1px rgba(0, 0, 0, .27);
+  }
+
+  md-input-container {
+    margin-bottom: 0;
+  }
+}
diff --git a/ui/src/app/components/menu-link.scss b/ui/src/app/components/menu-link.scss
index b21c0f4..299797c 100644
--- a/ui/src/app/components/menu-link.scss
+++ b/ui/src/app/components/menu-link.scss
@@ -24,9 +24,11 @@
 }
 
 .tb-menu-toggle-list {
-  overflow: hidden;
   position: relative;
   z-index: 1;
-  @include transition(0.75s cubic-bezier(0.35, 0, 0.25, 1));
+  overflow: hidden;
+
+  @include transition(.75s cubic-bezier(.35, 0, .25, 1));
+
   @include transition-property(height);
 }
diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss
index f84fb85..cca9d07 100644
--- a/ui/src/app/components/react/json-form.scss
+++ b/ui/src/app/components/react/json-form.scss
@@ -15,87 +15,92 @@
  */
 @import "~compass-sass-mixins/lib/compass";
 
-$swift-ease-out-duration: 0.4s !default;
-$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
+$swift-ease-out-duration: .4s !default;
+$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
 
 $input-label-float-offset: 6px !default;
-$input-label-float-scale: 0.75 !default;
+$input-label-float-scale: .75 !default;
 
 .json-form-error {
-    position: relative;
-    bottom: -5px;
-    font-size: 12px;
-    line-height: 12px;
-    color: rgb(244, 67, 54);
-    @include transition(all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms);
+  position: relative;
+  bottom: -5px;
+  font-size: 12px;
+  line-height: 12px;
+  color: rgb(244, 67, 54);
+
+  @include transition(all 450ms cubic-bezier(.23, 1, .32, 1) 0ms);
 }
 
 .tb-container {
-    position: relative;
-    margin-top: 32px;
-    padding: 10px 0;
+  position: relative;
+  padding: 10px 0;
+  margin-top: 32px;
 }
 
 .tb-field {
-    &.tb-required {
-        label:after {
-            content: ' *';
-            font-size: 13px;
-            vertical-align: top;
-            color: rgba(0,0,0,0.54);
-        }
+  &.tb-required {
+    label::after {
+      font-size: 13px;
+      color: rgba(0, 0, 0, .54);
+      vertical-align: top;
+      content: " *";
     }
-    &.tb-focused:not(.tb-readonly) {
-        label:after {
-            color: rgb(221,44,0);
-        }
+  }
+
+  &.tb-focused:not(.tb-readonly) {
+    label::after {
+      color: rgb(221, 44, 0);
     }
+  }
 }
 
 .tb-date-field {
-    &.tb-required {
-        div>div:first-child:after {
-            content: ' *';
-            font-size: 13px;
-            vertical-align: top;
-            color: rgba(0,0,0,0.54);
-        }
+  &.tb-required {
+    div > div:first-child::after {
+      font-size: 13px;
+      color: rgba(0, 0, 0, .54);
+      vertical-align: top;
+      content: " *";
     }
-    &.tb-focused:not(.tb-readonly) {
-        div>div:first-child:after {
-            color: rgb(221,44,0);
-        }
+  }
+
+  &.tb-focused:not(.tb-readonly) {
+    div > div:first-child::after {
+      color: rgb(221, 44, 0);
     }
+  }
 }
 
 label.tb-label {
-    color: rgba(0,0,0,0.54);
-    -webkit-font-smoothing: antialiased;
-    position: absolute;
-    bottom: 100%;
-    left: 0;
-    right: auto;
-    @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
-    @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
-                          width $swift-ease-out-timing-function $swift-ease-out-duration);
-    transform-origin: left top;
-
-    &.tb-focused {
-        color: rgb(96,125,139);
-    }
+  position: absolute;
+  right: auto;
+  bottom: 100%;
+  left: 0;
+  color: rgba(0, 0, 0, .54);
+  transform-origin: left top;
+  -webkit-font-smoothing: antialiased;
 
-    &.tb-required:after {
-        content: ' *';
-        font-size: 13px;
-        vertical-align: top;
-        color: rgba(0,0,0,0.54);
-    }
+  @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
 
-    &.tb-focused:not(.tb-readonly):after {
-        color: rgb(221,44,0);
-    }
+  @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
+  width $swift-ease-out-timing-function $swift-ease-out-duration);
+
+  &.tb-focused {
+    color: rgb(96, 125, 139);
+  }
+
+  &.tb-required::after {
+    font-size: 13px;
+    color: rgba(0, 0, 0, .54);
+    vertical-align: top;
+    content: " *";
+  }
+
+  &.tb-focused:not(.tb-readonly)::after {
+    color: rgb(221, 44, 0);
+  }
 }
 
 .tb-head-label {
-    color: rgba(0,0,0,0.54);
+  color: rgba(0, 0, 0, .54);
 }
diff --git a/ui/src/app/components/react/json-form-ace-editor.scss b/ui/src/app/components/react/json-form-ace-editor.scss
index 42ab8c5..c08eba1 100644
--- a/ui/src/app/components/react/json-form-ace-editor.scss
+++ b/ui/src/app/components/react/json-form-ace-editor.scss
@@ -13,30 +13,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .json-form-ace-editor {
-    position: relative;
-    border: 1px solid #C0C0C0;
-    height: 100%;
-    .title-panel {
-        position: absolute;
-        font-size: 0.800rem;
-        font-weight: 500;
-        top: 10px;
-        right: 20px;
-        z-index: 5;
-        label {
-          color: #00acc1;
-          background: rgba(220, 220, 220, 0.35);
-          border-radius: 5px;
-          padding: 4px;
-          text-transform: uppercase;
-        }
-        button.tidy-button {
-          background: rgba(220, 220, 220, 0.35) !important;
-          span {
-             padding: 0px !important;
-             font-size: 12px !important;
-          }
-        }
+  position: relative;
+  height: 100%;
+  border: 1px solid #c0c0c0;
+
+  .title-panel {
+    position: absolute;
+    top: 10px;
+    right: 20px;
+    z-index: 5;
+    font-size: .8rem;
+    font-weight: 500;
+
+    label {
+      padding: 4px;
+      color: #00acc1;
+      text-transform: uppercase;
+      background: rgba(220, 220, 220, .35);
+      border-radius: 5px;
     }
-}
\ No newline at end of file
+
+    button.tidy-button {
+      background: rgba(220, 220, 220, .35) !important;
+
+      span {
+        padding: 0 !important;
+        font-size: 12px !important;
+      }
+    }
+  }
+}
diff --git a/ui/src/app/components/react/json-form-color.scss b/ui/src/app/components/react/json-form-color.scss
index ee55ec3..7bc0826 100644
--- a/ui/src/app/components/react/json-form-color.scss
+++ b/ui/src/app/components/react/json-form-color.scss
@@ -15,21 +15,23 @@
  */
 @mixin tb-checkered-bg() {
   background-color: #fff;
-  background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
-  linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
-  background-size: 8px 8px;
+  background-image:
+    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
+    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
   background-position: 0 0, 4px 4px;
+  background-size: 8px 8px;
 }
 
 .tb-color-preview {
-  content: '';
+  position: relative;
   width: 24px;
   height: 24px;
+  overflow: hidden;
+  content: "";
   border: 2px solid #fff;
   border-radius: 50%;
   box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
-  position: relative;
-  overflow: hidden;
+
   @include tb-checkered-bg();
 
   .tb-color-result {
diff --git a/ui/src/app/components/react/json-form-image.scss b/ui/src/app/components/react/json-form-image.scss
index 8872b09..e602f52 100644
--- a/ui/src/app/components/react/json-form-image.scss
+++ b/ui/src/app/components/react/json-form-image.scss
@@ -13,66 +13,69 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$previewSize: 100px;
+$previewSize: 100px !default;
 
 .tb-image-select-container {
   position: relative;
-  height: $previewSize;
   width: 100%;
+  height: $previewSize;
 }
 
 .tb-image-preview {
-   max-width: $previewSize;
-   max-height: $previewSize;
-   width: 100%;
-   height: 100%;
+  width: 100%;
+  max-width: $previewSize;
+  height: 100%;
+  max-height: $previewSize;
 }
 
 .tb-image-preview-container {
-   position: relative;
-   width: $previewSize;
-   height: $previewSize;
-   margin-right: 12px;
-   border: solid 1px;
-   vertical-align: top;
-   float: left;
-   div {
-      width: 100%;
-      font-size: 18px;
-      text-align: center;
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      transform: translate(-50%,-50%);
-   }
+  position: relative;
+  float: left;
+  width: $previewSize;
+  height: $previewSize;
+  margin-right: 12px;
+  vertical-align: top;
+  border: solid 1px;
+
+  div {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 100%;
+    font-size: 18px;
+    text-align: center;
+    transform: translate(-50%, -50%);
+  }
 }
 
 .tb-dropzone {
-   position: relative;
-   border: dashed 2px;
-   height: $previewSize;
-   vertical-align: top;
-   padding: 0 8px;
-   overflow: hidden;
-   div {
-      width: 100%;
-      font-size: 24px;
-      text-align: center;
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      transform: translate(-50%,-50%);
-   }
+  position: relative;
+  height: $previewSize;
+  padding: 0 8px;
+  overflow: hidden;
+  vertical-align: top;
+  border: dashed 2px;
+
+  div {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 100%;
+    font-size: 24px;
+    text-align: center;
+    transform: translate(-50%, -50%);
+  }
 }
 
 .tb-image-clear-container {
-   width: 48px;
-   height: $previewSize;
-   position: relative;
-   float: right;
+  position: relative;
+  float: right;
+  width: 48px;
+  height: $previewSize;
 }
+
 .tb-image-clear-btn {
-   position: absolute !important;
-   top: 50%;
-   transform: translate(0%,-50%) !important;
+  position: absolute !important;
+  top: 50%;
+  transform: translate(0%, -50%) !important;
 }
diff --git a/ui/src/app/components/react/json-form-rc-select.jsx b/ui/src/app/components/react/json-form-rc-select.jsx
index ffa0331..83fa929 100644
--- a/ui/src/app/components/react/json-form-rc-select.jsx
+++ b/ui/src/app/components/react/json-form-rc-select.jsx
@@ -27,39 +27,90 @@ class ThingsboardRcSelect extends React.Component {
         this.onDeselect = this.onDeselect.bind(this);
         this.onBlur = this.onBlur.bind(this);
         this.onFocus = this.onFocus.bind(this);
-        let emptyValue = this.props.form.schema.type === 'array'? [] : null;
         this.state = {
-            currentValue: this.props.value || emptyValue,
+            currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'),
             items: this.props.form.items,
             focused: false
         };
     }
 
+    keyToCurrentValue(key, isArray) {
+        var currentValue = isArray ? [] : null;
+        if (isArray) {
+            var keys = key;
+            if (keys) {
+                for (var i = 0; i < keys.length; i++) {
+                    currentValue.push({key: keys[i], label: this.labelFromKey(keys[i])});
+                }
+            }
+        } else {
+            currentValue = {key: key, label: this.labelFromKey(key)};
+        }
+        return currentValue;
+    }
+
+    labelFromKey(key) {
+        let label = key || '';
+        if (key) {
+            for (var i=0;i<this.props.form.items.length;i++) {
+                var item = this.props.form.items[i];
+                if (item.value === key) {
+                    label = item.label;
+                    break;
+                }
+            }
+        }
+        return label;
+    }
+
+    arrayValues(items) {
+        var v = [];
+        if (items) {
+            for (var i = 0; i < items.length; i++) {
+                v.push(items[i].key);
+            }
+        }
+        return v;
+    }
+
+    keyIndex(values, key) {
+        var index = -1;
+        if (values) {
+            for (var i = 0; i < values.length; i++) {
+                if (values[i].key === key) {
+                    index = i;
+                    break;
+                }
+            }
+        }
+        return index;
+    }
+
     onSelect(value, option) {
         if(this.props.form.schema.type === 'array') {
             let v = this.state.currentValue;
-            v.push(value);
+            v.push(this.keyToCurrentValue(value.key, false));
             this.setState({
                 currentValue: v
             });
-            this.props.onChangeValidate(v);
+            this.props.onChangeValidate(this.arrayValues(v));
         } else {
-            this.setState({currentValue: value});
-            this.props.onChangeValidate({target: {value: value}});
+            this.setState({currentValue: this.keyToCurrentValue(value.key, false)});
+            this.props.onChangeValidate({target: {value: value.key}});
         }
     }
 
     onDeselect(value, option) {
         if (this.props.form.schema.type === 'array') {
             let v = this.state.currentValue;
-            let index = v.indexOf(value);
+            let index = this.keyIndex(v, value.key);
             if (index > -1) {
                 v.splice(index, 1);
             }
             this.setState({
                 currentValue: v
             });
-            this.props.onChangeValidate(v);
+            this.props.onChangeValidate(this.arrayValues(v));
         }
     }
 
@@ -105,6 +156,7 @@ class ThingsboardRcSelect extends React.Component {
                     combobox={this.props.form.combobox}
                     disabled={this.props.form.readonly}
                     value={this.state.currentValue}
+                    labelInValue={true}
                     onSelect={this.onSelect}
                     onDeselect={this.onDeselect}
                     onFocus={this.onFocus}
diff --git a/ui/src/app/components/react/json-form-schema-form.jsx b/ui/src/app/components/react/json-form-schema-form.jsx
index e006eb9..09712f2 100644
--- a/ui/src/app/components/react/json-form-schema-form.jsx
+++ b/ui/src/app/components/react/json-form-schema-form.jsx
@@ -63,11 +63,15 @@ class ThingsboardSchemaForm extends React.Component {
 
         this.onChange = this.onChange.bind(this);
         this.onColorClick = this.onColorClick.bind(this);
+        this.hasConditions = false;
     }
 
     onChange(key, val) {
         //console.log('SchemaForm.onChange', key, val);
         this.props.onModelChange(key, val);
+        if (this.hasConditions) {
+            this.forceUpdate();
+        }
     }
 
     onColorClick(event, key, val) {
@@ -81,8 +85,11 @@ class ThingsboardSchemaForm extends React.Component {
             console.log('Invalid field: \"' + form.key[0] + '\"!');
             return null;
         }
-        if(form.condition && eval(form.condition) === false) {
-            return null;
+        if(form.condition) {
+            this.hasConditions = true;
+            if (eval(form.condition) === false) {
+                return null;
+            }
         }
         return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} mapper={mapper} builder={this.builder}/>
     }
diff --git a/ui/src/app/components/related-entity-autocomplete.scss b/ui/src/app/components/related-entity-autocomplete.scss
index 8d4683a..74f652a 100644
--- a/ui/src/app/components/related-entity-autocomplete.scss
+++ b/ui/src/app/components/related-entity-autocomplete.scss
@@ -13,16 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-related-entity-autocomplete {
   .tb-not-found {
     display: block;
-    line-height: 1.5;
     height: 48px;
+    line-height: 1.5;
   }
+
   .tb-entity-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/components/side-menu.scss b/ui/src/app/components/side-menu.scss
index 469c575..46da8db 100644
--- a/ui/src/app/components/side-menu.scss
+++ b/ui/src/app/components/side-menu.scss
@@ -16,21 +16,22 @@
 @import "~compass-sass-mixins/lib/compass";
 
 .tb-side-menu .md-button.tb-active {
-  background-color: rgba(255, 255, 255, 0.15);
   font-weight: 500;
+  background-color: rgba(255, 255, 255, .15);
 }
 
-.tb-side-menu, .tb-side-menu ul {
-  list-style: none;
+.tb-side-menu,
+.tb-side-menu ul {
   padding: 0;
   margin-top: 0;
+  list-style: none;
 }
 
 .tb-side-menu .tb-menu-toggle-list a.md-button {
   padding: 0 16px 0 32px;
+  font-weight: 500;
   text-transform: none;
   text-rendering: optimizeLegibility;
-  font-weight: 500;
 }
 
 .tb-side-menu .tb-menu-toggle-list .md-button {
@@ -43,36 +44,38 @@
 }
 
 .tb-side-menu > li {
-  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  border-bottom: 1px solid rgba(0, 0, 0, .12);
 }
 
 .tb-side-menu .md-button-toggle .md-toggle-icon {
-  background-size: 100% auto;
   display: inline-block;
-  margin: auto 0 auto auto;
   width: 15px;
+  margin: auto 0 auto auto;
+  background-size: 100% auto;
+
   @include transition(transform .3s, ease-in-out);
 }
 
 .tb-side-menu .md-button {
   display: flex;
-  border-radius: 0;
-  color: inherit;
-  cursor: pointer;
-  line-height: 40px;
-  margin: 0;
+  width: 100%;
   max-height: 40px;
+  padding: 0 16px;
+  margin: 0;
   overflow: hidden;
-  padding: 0px 16px;
+  line-height: 40px;
+  color: inherit;
   text-align: left;
   text-decoration: none;
-  white-space: nowrap;
   text-overflow: ellipsis;
-  width: 100%;
+  white-space: nowrap;
+  cursor: pointer;
+  border-radius: 0;
+
   span {
     overflow: hidden;
-    white-space: nowrap;
     text-overflow: ellipsis;
+    white-space: nowrap;
   }
 }
 
@@ -83,10 +86,10 @@
 
 .tb-side-menu ng-md-icon {
   margin-right: 8px;
-  margin-left: 0px;
+  margin-left: 0;
 }
 
 .tb-side-menu md-icon {
   margin-right: 8px;
-  margin-left: 0px;
+  margin-left: 0;
 }
diff --git a/ui/src/app/components/timeinterval.scss b/ui/src/app/components/timeinterval.scss
index 4bae01d..c5c0683 100644
--- a/ui/src/app/components/timeinterval.scss
+++ b/ui/src/app/components/timeinterval.scss
@@ -13,25 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-timeinterval {
   min-width: 355px;
+
   md-input-container {
-    margin-bottom: 0px;
+    margin-bottom: 0;
+
     .md-errors-spacer {
-      min-height: 0px;
+      min-height: 0;
     }
   }
+
   mdp-date-picker {
     .md-input {
       width: 150px;
     }
   }
+
   .md-input {
     width: 70px !important;
   }
+
   .advanced-switch {
     margin-top: 0;
   }
+
   .advanced-label {
     margin: 5px 0;
   }
diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss
index 01ad5f9..8d4ed89 100644
--- a/ui/src/app/components/timewindow.scss
+++ b/ui/src/app/components/timewindow.scss
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .md-panel {
   &.tb-timewindow-panel {
     position: absolute;
@@ -20,38 +21,48 @@
 }
 
 .tb-timewindow-panel {
-  max-height: 440px;
   min-width: 417px;
-  background: white;
-  border-radius: 4px;
-  box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
-  0 13px 19px 2px rgba(0, 0, 0, 0.14),
-  0 5px 24px 4px rgba(0, 0, 0, 0.12);
+  max-height: 440px;
   overflow: hidden;
-  form, fieldset {
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  form,
+  fieldset {
     height: 100%;
   }
+
   md-content {
-    background-color: #fff;
     overflow: hidden;
+    background-color: #fff;
   }
+
   .md-padding {
     padding: 0 16px;
   }
+
   .md-radio-interactive {
-    md-select, md-switch {
+    md-select,
+    md-switch {
       pointer-events: all;
     }
   }
+
   md-radio-button {
     .md-label {
       width: 100%;
     }
+
     tb-timeinterval {
       width: 355px;
+
       .advanced-switch {
-        min-height: 30px;
         max-width: 44px;
+        min-height: 30px;
       }
     }
   }
@@ -59,15 +70,17 @@
 
 tb-timewindow {
   min-width: 52px;
+
   section.tb-timewindow {
     min-height: 32px;
     padding: 0 6px;
+
     span {
-      pointer-events: all;
-      cursor: pointer;
       overflow: hidden;
       text-overflow: ellipsis;
       white-space: nowrap;
+      pointer-events: all;
+      cursor: pointer;
     }
   }
 }
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.scss b/ui/src/app/components/widget/action/manage-widget-actions.scss
index 91d587b..69891cc 100644
--- a/ui/src/app/components/widget/action/manage-widget-actions.scss
+++ b/ui/src/app/components/widget/action/manage-widget-actions.scss
@@ -13,18 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-manage-widget-actions {
   table.md-table {
     tbody {
       tr {
         td {
           &.tb-action-cell {
+            width: 100px;
+            min-width: 100px;
+            max-width: 100px;
             overflow: hidden;
             text-overflow: ellipsis;
             white-space: nowrap;
-            min-width: 100px;
-            max-width: 100px;
-            width: 100px;
           }
         }
       }
diff --git a/ui/src/app/components/widget/widget.scss b/ui/src/app/components/widget/widget.scss
index 347417b..3d5a9b4 100644
--- a/ui/src/app/components/widget/widget.scss
+++ b/ui/src/app/components/widget/widget.scss
@@ -13,18 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-widget {
   .tb-widget-error {
     display: flex;
-    justify-content: center;
     align-items: center;
-    background: rgba(255,255,255,0.5);
+    justify-content: center;
+    background: rgba(255, 255, 255, .5);
+
     span {
-       color: red;
+      color: #f00;
     }
   }
+
   .tb-widget-loading {
-    background: rgba(255,255,255,0.15);
     z-index: 3;
+    background: rgba(255, 255, 255, .15);
   }
 }
diff --git a/ui/src/app/components/widget/widget-config.scss b/ui/src/app/components/widget/widget-config.scss
index 7ff2051..4ac8561 100644
--- a/ui/src/app/components/widget/widget-config.scss
+++ b/ui/src/app/components/widget/widget-config.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-widget-config {
   md-tab-content.md-active > div {
     height: 100%;
   }
+
   .tb-advanced-widget-config {
     height: 100%;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/widgets-bundle-select.scss b/ui/src/app/components/widgets-bundle-select.scss
index 41c4ee7..f5d1f22 100644
--- a/ui/src/app/components/widgets-bundle-select.scss
+++ b/ui/src/app/components/widgets-bundle-select.scss
@@ -17,9 +17,10 @@
 
 tb-widgets-bundle-select {
   md-select {
-    margin: 0;
     padding: 5px 20px;
+    margin: 0;
   }
+
   .tb-bundle-item {
     height: 24px;
     line-height: 24px;
@@ -33,24 +34,29 @@ tb-widgets-bundle-select {
   }
 }
 
-tb-widgets-bundle-select, .tb-widgets-bundle-select {
+tb-widgets-bundle-select,
+.tb-widgets-bundle-select {
   .md-text {
     display: block;
     width: 100%;
   }
+
   .tb-bundle-item {
     display: inline-block;
     width: 100%;
+
     span {
       display: inline-block;
       vertical-align: middle;
     }
+
     .tb-bundle-system {
-      font-size: 0.8rem;
-      opacity: 0.8;
       float: right;
+      font-size: .8rem;
+      opacity: .8;
     }
   }
+
   md-option {
     height: auto !important;
     white-space: normal !important;
@@ -60,19 +66,23 @@ tb-widgets-bundle-select, .tb-widgets-bundle-select {
 md-toolbar {
   tb-widgets-bundle-select {
     md-select {
-      background: rgba(255,255,255,0.2);
+      background: rgba(255, 255, 255, .2);
+
       .md-select-value {
-        color: #fff;
         font-size: 1.2rem;
-        span:first-child:after {
-            color: #fff;
+        color: #fff;
+
+        span:first-child::after {
+          color: #fff;
         }
       }
+
       .md-select-value.md-select-placeholder {
         color: #fff;
-        opacity: 0.8;
+        opacity: .8;
       }
     }
+
     md-select.ng-invalid.ng-touched {
       .md-select-value {
         color: #fff !important;
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 224dcf7..00a0653 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 @import "~compass-sass-mixins/lib/compass";
-@import '../../scss/constants';
 
-$toolbar-height: 50px;
-$fullscreen-toolbar-height: 64px;
-$mobile-toolbar-height: 84px;
+@import "../../scss/constants";
+
+$toolbar-height: 50px !default;
+$fullscreen-toolbar-height: 64px !default;
+$mobile-toolbar-height: 84px !default;
 
 section.tb-dashboard-title {
   position: absolute;
@@ -27,10 +28,10 @@ section.tb-dashboard-title {
 }
 
 input.tb-dashboard-title {
-  font-size: 2.000rem;
-  font-weight: 500;
-  letter-spacing: 0.005em;
   height: 38px;
+  font-size: 2rem;
+  font-weight: 500;
+  letter-spacing: .005em;
 }
 
 div.tb-padded {
@@ -50,9 +51,11 @@ tb-details-sidenav.tb-widget-details-sidenav {
     @media (min-width: $layout-breakpoint-sm) {
       width: 85% !important;
     }
+
     @media (min-width: $layout-breakpoint-md) {
       width: 75% !important;
     }
+
     @media (min-width: $layout-breakpoint-lg) {
       width: 60% !important;
     }
@@ -65,47 +68,55 @@ tb-details-sidenav.tb-widget-details-sidenav {
 
 section.tb-dashboard-toolbar {
   position: absolute;
-  top: 0px;
-  left: 0px;
+  top: 0;
+  left: 0;
   z-index: 13;
   pointer-events: none;
+
   &.tb-dashboard-toolbar-opened {
-    right: 0px;
-   // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+    right: 0;
+    // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
   }
+
   &.tb-dashboard-toolbar-closed {
     right: 18px;
+
     @include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
   }
 }
 
 .tb-dashboard-container {
-   &.tb-dashboard-toolbar-opened {
-     &.is-fullscreen {
-       margin-top: $mobile-toolbar-height;
-       @media (min-width: $layout-breakpoint-sm) {
-         margin-top: $fullscreen-toolbar-height;
-       }
-     }
-     &:not(.is-fullscreen) {
-       margin-top: $mobile-toolbar-height;
-       @media (min-width: $layout-breakpoint-sm) {
-         margin-top: $toolbar-height;
-       }
-       @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
-     }
-   }
-   &.tb-dashboard-toolbar-closed {
-     margin-top: 0px;
-     @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
+  &.tb-dashboard-toolbar-opened {
+    &.is-fullscreen {
+      margin-top: $mobile-toolbar-height;
+
+      @media (min-width: $layout-breakpoint-sm) {
+        margin-top: $fullscreen-toolbar-height;
+      }
+    }
+
+    &:not(.is-fullscreen) {
+      margin-top: $mobile-toolbar-height;
+
+      @media (min-width: $layout-breakpoint-sm) {
+        margin-top: $toolbar-height;
+      }
+
+      @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+    }
+  }
+
+  &.tb-dashboard-toolbar-closed {
+    margin-top: 0;
+
+    @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
   }
+
   .tb-dashboard-layouts {
     md-backdrop {
       z-index: 1;
     }
-    #tb-main-layout {
 
-    }
     #tb-right-layout {
       md-sidenav {
         z-index: 1;
@@ -124,13 +135,15 @@ section.tb-powered-by-footer {
   bottom: 5px;
   z-index: 3;
   pointer-events: none;
+
   span {
     font-size: 12px;
+
     a {
-      font-weight: bold;
+      font-weight: 700;
       text-decoration: none;
-      border: none;
       pointer-events: all;
+      border: none;
     }
   }
 }
diff --git a/ui/src/app/dashboard/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss
index 0dac213..8e6626c 100644
--- a/ui/src/app/dashboard/dashboard-card.scss
+++ b/ui/src/app/dashboard/dashboard-card.scss
@@ -13,14 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-dashboard-assigned-customers {
   display: block;
   display: -webkit-box;
   height: 34px;
+  margin-bottom: 4px;
   overflow: hidden;
   text-overflow: ellipsis;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;
-  margin-bottom: 4px;
 }
 
diff --git a/ui/src/app/dashboard/dashboard-settings.scss b/ui/src/app/dashboard/dashboard-settings.scss
index 37da008..e278240 100644
--- a/ui/src/app/dashboard/dashboard-settings.scss
+++ b/ui/src/app/dashboard/dashboard-settings.scss
@@ -13,50 +13,54 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$previewSize: 100px;
+$previewSize: 100px !default;
 
 .tb-image-select-container {
   position: relative;
-  height: $previewSize;
   width: 100%;
+  height: $previewSize;
 }
 
 .tb-image-preview {
-  max-width: $previewSize;
-  max-height: $previewSize;
   width: auto;
+  max-width: $previewSize;
   height: auto;
+  max-height: $previewSize;
 }
 
 .tb-image-preview-container {
   position: relative;
+  float: left;
   width: $previewSize;
   height: $previewSize;
   margin-right: 12px;
-  border: solid 1px;
   vertical-align: top;
-  float: left;
+  border: solid 1px;
+
   div {
     width: 100%;
     font-size: 18px;
     text-align: center;
   }
-  div, .tb-image-preview {
+
+  div,
+  .tb-image-preview {
     position: absolute;
     top: 50%;
     left: 50%;
-    transform: translate(-50%,-50%);
+    transform: translate(-50%, -50%);
   }
 }
 
 .tb-image-clear-container {
-  width: 48px;
-  height: $previewSize;
   position: relative;
   float: right;
+  width: 48px;
+  height: $previewSize;
 }
+
 .tb-image-clear-btn {
   position: absolute !important;
   top: 50%;
-  transform: translate(0%,-50%) !important;
+  transform: translate(0%, -50%) !important;
 }
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
index 40c17c4..80fd876 100644
--- a/ui/src/app/dashboard/dashboard-toolbar.scss
+++ b/ui/src/app/dashboard/dashboard-toolbar.scss
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 @import "~compass-sass-mixins/lib/compass";
-@import '../../scss/constants';
 
-$toolbar-height: 50px;
-$fullscreen-toolbar-height: 64px;
-$mobile-toolbar-height: 80px;
-$half-mobile-toolbar-height: 40px;
-$mobile-toolbar-height-total: 84px;
+@import "../../scss/constants";
+
+$toolbar-height: 50px !default;
+$fullscreen-toolbar-height: 64px !default;
+$mobile-toolbar-height: 80px !default;
+$half-mobile-toolbar-height: 40px !default;
+$mobile-toolbar-height-total: 84px !default;
 
 tb-dashboard-toolbar {
   md-fab-toolbar {
@@ -29,126 +30,156 @@ tb-dashboard-toolbar {
         .md-button {
           &.md-fab {
             opacity: 1;
+
             @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
+
             .md-fab-toolbar-background {
-                background-color: $primary-default !important;
+              background-color: $primary-default !important;
             }
           }
         }
       }
     }
+
     md-fab-trigger {
       .md-button {
         &.md-fab {
-          line-height: 36px;
           width: 36px;
           height: 36px;
           margin: 4px 0 0 4px;
-          opacity: 0.5;
+          line-height: 36px;
+          opacity: .5;
+
           @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
+
           md-icon {
             position: absolute;
             top: 25%;
-            margin: 0;
-            line-height: 18px;
-            height: 18px;
             width: 18px;
-            min-height: 18px;
             min-width: 18px;
+            height: 18px;
+            min-height: 18px;
+            margin: 0;
+            line-height: 18px;
           }
         }
       }
     }
+
     &.is-fullscreen {
       &.md-is-open {
         md-fab-trigger {
           .md-button {
             &.md-fab {
               .md-fab-toolbar-background {
-                  transition-delay: 0ms !important;
-                  transition-duration: 0ms !important;
+                transition-delay: 0ms !important;
+                transition-duration: 0ms !important;
               }
             }
           }
         }
       }
+
       .md-fab-toolbar-wrapper {
         height: $mobile-toolbar-height-total;
+
         @media (min-width: $layout-breakpoint-sm) {
           height: $fullscreen-toolbar-height;
         }
+
         md-toolbar {
-          min-height: $mobile-toolbar-height;
           height: $mobile-toolbar-height;
+          min-height: $mobile-toolbar-height;
+
           @media (min-width: $layout-breakpoint-sm) {
-            min-height: $fullscreen-toolbar-height;
             height: $fullscreen-toolbar-height;
+            min-height: $fullscreen-toolbar-height;
           }
         }
       }
     }
+
     .md-fab-toolbar-wrapper {
       height: $mobile-toolbar-height-total;
+
       @media (min-width: $layout-breakpoint-sm) {
         height: $toolbar-height;
       }
+
       md-toolbar {
-        min-height: $mobile-toolbar-height;
         height: $mobile-toolbar-height;
+        min-height: $mobile-toolbar-height;
+
         @media (min-width: $layout-breakpoint-sm) {
-          min-height: $toolbar-height;
           height: $toolbar-height;
+          min-height: $toolbar-height;
         }
+
         md-fab-actions {
+          margin-top: 0;
           font-size: 16px;
-          margin-top: 0px;
+
           @media (max-width: $layout-breakpoint-sm) {
             height: $mobile-toolbar-height;
             max-height: $mobile-toolbar-height;
           }
+
           .close-action {
             margin-right: -18px;
           }
+
           .md-fab-action-item {
             width: 100%;
             height: $mobile-toolbar-height;
+
             @media (min-width: $layout-breakpoint-sm) {
               height: 46px;
             }
+
             .tb-dashboard-action-panels {
+              flex-direction: column-reverse;
               height: $mobile-toolbar-height;
+
               @media (min-width: $layout-breakpoint-sm) {
                 height: 46px;
               }
-              flex-direction: column-reverse;
+
               @media (min-width: $layout-breakpoint-sm) {
                 flex-direction: row-reverse;
               }
+
               .tb-dashboard-action-panel {
-                min-width: 0px;
+                flex-direction: row-reverse;
+                min-width: 0;
                 height: $half-mobile-toolbar-height;
+
                 @media (min-width: $layout-breakpoint-sm) {
                   height: 46px;
                 }
-                flex-direction: row-reverse;
+
                 div {
                   height: $half-mobile-toolbar-height;
+
                   @media (min-width: $layout-breakpoint-sm) {
                     height: 46px;
                   }
                 }
+
                 md-select {
                   pointer-events: all;
                 }
+
                 tb-states-component {
                   pointer-events: all;
                 }
+
                 button.md-icon-button {
                   min-width: 40px;
+
                   @media (max-width: $layout-breakpoint-sm) {
                     min-width: 28px;
-                    margin: 0px;
                     padding: 2px;
+                    margin: 0;
                   }
                 }
               }
@@ -158,4 +189,4 @@ tb-dashboard-toolbar {
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/dashboard/states/default-state-controller.scss b/ui/src/app/dashboard/states/default-state-controller.scss
index 5d6ae97..0fafd3b 100644
--- a/ui/src/app/dashboard/states/default-state-controller.scss
+++ b/ui/src/app/dashboard/states/default-state-controller.scss
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 md-select.default-state-controller {
-  margin: 0px;
-}
\ No newline at end of file
+  margin: 0;
+}
diff --git a/ui/src/app/dashboard/states/entity-state-controller.scss b/ui/src/app/dashboard/states/entity-state-controller.scss
index 4c2d0c3..5756182 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.scss
+++ b/ui/src/app/dashboard/states/entity-state-controller.scss
@@ -13,34 +13,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../../scss/constants';
+@import "../../../scss/constants";
 
 tb-states-component {
-    min-width: 0px;
+  min-width: 0;
 }
 
 .entity-state-controller {
-    .state-divider {
-        font-size: 18px;
-        padding-left: 15px;
-        padding-right: 15px;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        pointer-events: none;
-    }
-    .state-entry {
-        font-size: 18px;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        outline: none;
-    }
-    md-select {
-        margin: 0px;
-        .md-text {
-            font-size: 18px;
-            font-weight: bold;
-        }
+  .state-divider {
+    padding-right: 15px;
+    padding-left: 15px;
+    overflow: hidden;
+    font-size: 18px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    pointer-events: none;
+  }
+
+  .state-entry {
+    overflow: hidden;
+    font-size: 18px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    outline: none;
+  }
+
+  md-select {
+    margin: 0;
+
+    .md-text {
+      font-size: 18px;
+      font-weight: 700;
     }
-}
\ No newline at end of file
+  }
+}
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.scss b/ui/src/app/dashboard/states/manage-dashboard-states.scss
index 2aa1ca2..bf139be 100644
--- a/ui/src/app/dashboard/states/manage-dashboard-states.scss
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.scss
@@ -13,21 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .manage-dashboard-states {
   table.md-table {
     tbody {
       tr {
         td {
           &.tb-action-cell {
+            width: 100px;
+            min-width: 100px;
+            max-width: 100px;
             overflow: hidden;
             text-overflow: ellipsis;
             white-space: nowrap;
-            min-width: 100px;
-            max-width: 100px;
-            width: 100px;
           }
         }
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/alias/aliases-entity-select.scss b/ui/src/app/entity/alias/aliases-entity-select.scss
index 978bb3e..1244437 100644
--- a/ui/src/app/entity/alias/aliases-entity-select.scss
+++ b/ui/src/app/entity/alias/aliases-entity-select.scss
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../../scss/constants';
+@import "../../../scss/constants";
 
 tb-aliases-entity-select {
   min-width: 52px;
@@ -26,18 +26,21 @@ tb-aliases-entity-select {
 }
 
 .tb-aliases-entity-select-panel {
+  min-width: 300px;
   max-height: 150px;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
   @media (min-height: 350px) {
     max-height: 250px;
   }
-  min-width: 300px;
-  background: white;
-  border-radius: 4px;
-  box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
-  0 13px 19px 2px rgba(0, 0, 0, 0.14),
-  0 5px 24px 4px rgba(0, 0, 0, 0.12);
-  overflow-x: hidden;
-  overflow-y: auto;
+
   md-content {
     background-color: #fff;
   }
@@ -46,15 +49,17 @@ tb-aliases-entity-select {
 section.tb-aliases-entity-select {
   min-height: 32px;
   padding: 0 6px;
+
   @media (max-width: $layout-breakpoint-sm) {
-    padding: 0px;
+    padding: 0;
   }
+
   span {
     max-width: 200px;
-    pointer-events: all;
-    cursor: pointer;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
+    pointer-events: all;
+    cursor: pointer;
   }
 }
diff --git a/ui/src/app/entity/alias/entity-alias-dialog.scss b/ui/src/app/entity/alias/entity-alias-dialog.scss
index bd4cd51..c72854f 100644
--- a/ui/src/app/entity/alias/entity-alias-dialog.scss
+++ b/ui/src/app/entity/alias/entity-alias-dialog.scss
@@ -13,14 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-entity-alias-dialog {
   .tb-resolve-multiple-switch {
     padding-left: 10px;
+
     .resolve-multiple-switch {
       margin: 0;
     }
+
     .resolve-multiple-label {
       margin: 5px 0;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/alias/entity-aliases.scss b/ui/src/app/entity/alias/entity-aliases.scss
index 4b1fbeb..0f66d89 100644
--- a/ui/src/app/entity/alias/entity-aliases.scss
+++ b/ui/src/app/entity/alias/entity-aliases.scss
@@ -13,35 +13,43 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-aliases-dialog {
   .md-dialog-content {
-    padding-bottom: 0px;
-    padding-top: 0px;
+    padding-top: 0;
+    padding-bottom: 0;
   }
+
   .tb-aliases-header {
     min-height: 40px;
     padding: 0 34px 0 34px;
     margin: 5px;
+
     .tb-header-label {
       font-size: 14px;
-      color: rgba(0, 0, 0, 0.570588);
+      color: rgba(0, 0, 0, .570588);
     }
   }
+
   .tb-alias {
     padding: 0 0 0 10px;
     margin: 5px;
+
     md-input-container {
-      margin: 0px;
+      margin: 0;
     }
+
     .tb-resolve-multiple-switch {
       padding-left: 10px;
+
       .resolve-multiple-switch {
         margin: 0;
       }
     }
+
     .md-button {
       &.md-icon-button {
-        margin: 0px;
+        margin: 0;
       }
     }
   }
diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss
index 5d5c0be..ef42f4e 100644
--- a/ui/src/app/entity/attribute/attribute-table.scss
+++ b/ui/src/app/entity/attribute/attribute-table.scss
@@ -13,46 +13,48 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../../scss/constants';
+@import "../../../scss/constants";
 
-$md-light: rgba(255, 255, 255, 100%);
-$md-edit-icon-fill: #757575;
+$md-light: rgba(255, 255, 255, 100%) !default;
+$md-edit-icon-fill: #757575 !default;
 
 md-toolbar.md-table-toolbar.alternate {
   .md-toolbar-tools {
-      md-icon {
-        color: $md-light;
-      }
+    md-icon {
+      color: $md-light;
+    }
   }
 }
 
 .md-table {
   &.tb-attribute-table {
-      table-layout: fixed;
-      td.md-cell {
-          &.tb-value-cell {
-            overflow: auto;
-          }
+    table-layout: fixed;
+
+    td.md-cell {
+      &.tb-value-cell {
+        overflow: auto;
       }
+    }
   }
+
   .md-cell {
     ng-md-icon {
-      fill: $md-edit-icon-fill;
       float: right;
       height: 16px;
+      fill: $md-edit-icon-fill;
     }
   }
 }
 
 .widgets-carousel {
   position: relative;
-  margin: 0px;
   height: calc(100% - 100px);
   min-height: 150px !important;
+  margin: 0;
 
   tb-dashboard {
     #gridster-parent {
       padding: 0 7px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/entity-autocomplete.scss b/ui/src/app/entity/entity-autocomplete.scss
index dad3d97..6617fcb 100644
--- a/ui/src/app/entity/entity-autocomplete.scss
+++ b/ui/src/app/entity/entity-autocomplete.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-entity-autocomplete {
   .tb-entity-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss
index bbaa108..950ed0a 100644
--- a/ui/src/app/entity/entity-filter.scss
+++ b/ui/src/app/entity/entity-filter.scss
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-.tb-entity-filter {
 
+.tb-entity-filter {
   #relationsQueryFilter {
     padding-top: 20px;
+
     tb-entity-select {
       min-height: 92px;
     }
@@ -24,12 +25,13 @@
 
   .tb-root-state-entity-switch {
     padding-left: 10px;
+
     .root-state-entity-switch {
       margin: 0;
     }
+
     .root-state-entity-label {
       margin: 5px 0;
     }
   }
-
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/entity-filter-view.scss b/ui/src/app/entity/entity-filter-view.scss
index 69922c8..e85128a 100644
--- a/ui/src/app/entity/entity-filter-view.scss
+++ b/ui/src/app/entity/entity-filter-view.scss
@@ -13,20 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-entity-filter-view {
   .entity-filter-empty {
-    color: rgba(221, 44, 0, 0.87);
     font-size: 14px;
     line-height: 16px;
+    color: rgba(221, 44, 0, .87);
   }
+
   .entity-filter-type {
     font-size: 14px;
     line-height: 16px;
-    color: rgba(0, 0, 0, 0.570588);
+    color: rgba(0, 0, 0, .570588);
   }
+
   .entity-filter-value {
     font-size: 14px;
     line-height: 16px;
-    color: rgba(0, 0, 0, 0.570588);
+    color: rgba(0, 0, 0, .570588);
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/entity-list.scss b/ui/src/app/entity/entity-list.scss
index 6e1c892..a3c1bc0 100644
--- a/ui/src/app/entity/entity-list.scss
+++ b/ui/src/app/entity/entity-list.scss
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/*.tb-entity-list {
+
+/*
+.tb-entity-list {
   #entity_list_chips {
     .md-chips {
       padding-bottom: 1px;
@@ -26,4 +28,5 @@
       padding-left: 1px;
     }
   }
-}*/
\ No newline at end of file
+}
+*/
diff --git a/ui/src/app/entity/entity-select.scss b/ui/src/app/entity/entity-select.scss
index ab7bd3d..5d1b043 100644
--- a/ui/src/app/entity/entity-select.scss
+++ b/ui/src/app/entity/entity-select.scss
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-.tb-entity-select {
 
-}
\ No newline at end of file
+/*
+.tb-entity-select {
+}
+*/
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.scss b/ui/src/app/entity/entity-subtype-autocomplete.scss
index c8e3edc..6dc3ec6 100644
--- a/ui/src/app/entity/entity-subtype-autocomplete.scss
+++ b/ui/src/app/entity/entity-subtype-autocomplete.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-entity-subtype-autocomplete {
   .tb-entity-subtype-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss
index f024956..001ca3a 100644
--- a/ui/src/app/entity/entity-subtype-list.scss
+++ b/ui/src/app/entity/entity-subtype-list.scss
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/*.tb-entity-subtype-list {
+
+/*
+.tb-entity-subtype-list {
   #entity_subtype_list_chips {
     .md-chips {
       padding-bottom: 1px;
@@ -26,4 +28,5 @@
       padding-left: 1px;
     }
   }
-}*/
+}
+*/
diff --git a/ui/src/app/entity/entity-subtype-select.scss b/ui/src/app/entity/entity-subtype-select.scss
index 15b3f2a..9b085e2 100644
--- a/ui/src/app/entity/entity-subtype-select.scss
+++ b/ui/src/app/entity/entity-subtype-select.scss
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 md-select.tb-entity-subtype-select {
   min-width: 200px;
 }
diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss
index 21a88ee..a270f38 100644
--- a/ui/src/app/entity/entity-type-list.scss
+++ b/ui/src/app/entity/entity-type-list.scss
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/*.tb-entity-type-list {
+
+/*
+.tb-entity-type-list {
   #entity_type_list_chips {
     .md-chips {
       padding-bottom: 1px;
@@ -26,4 +28,5 @@
       padding-left: 1px;
     }
   }
-}*/
\ No newline at end of file
+}
+*/
diff --git a/ui/src/app/entity/entity-type-select.scss b/ui/src/app/entity/entity-type-select.scss
index 3a60865..15f2d89 100644
--- a/ui/src/app/entity/entity-type-select.scss
+++ b/ui/src/app/entity/entity-type-select.scss
@@ -13,5 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/*
 md-select.tb-entity-type-select {
 }
+*/
diff --git a/ui/src/app/entity/relation/relation-dialog.scss b/ui/src/app/entity/relation/relation-dialog.scss
index 1c0bf2e..6dcfdfd 100644
--- a/ui/src/app/entity/relation/relation-dialog.scss
+++ b/ui/src/app/entity/relation/relation-dialog.scss
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-relation-additional-info-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: 100%;
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   #tb-relation-additional-info {
-    min-width: 200px;
-    min-height: 200px;
     width: 100%;
+    min-width: 200px;
     height: 100%;
+    min-height: 200px;
   }
 }
diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss
index 34254d0..affea5d 100644
--- a/ui/src/app/entity/relation/relation-filters.scss
+++ b/ui/src/app/entity/relation/relation-filters.scss
@@ -13,32 +13,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-relation-filters {
   .header {
-    padding-left: 5px;
     padding-right: 5px;
     padding-bottom: 5px;
+    padding-left: 5px;
+
     .cell {
-      padding-left: 5px;
       padding-right: 5px;
-      color: rgba(0,0,0,.54);
+      padding-left: 5px;
       font-size: 12px;
       font-weight: 700;
+      color: rgba(0, 0, 0, .54);
       white-space: nowrap;
     }
   }
+
   .body {
-    padding-left: 5px;
-    padding-right: 5px;
     max-height: 300px;
-    overflow: auto;
+    padding-right: 5px;
     padding-bottom: 20px;
+    padding-left: 5px;
+    overflow: auto;
+
     .row {
       padding-top: 5px;
     }
+
     .cell {
-      padding-left: 5px;
       padding-right: 5px;
+      padding-left: 5px;
 
       md-select {
         margin: 0 0 24px;
@@ -49,25 +54,27 @@
       }
 
       md-chips-wrap {
-        padding: 0px;
+        padding: 0;
         margin: 0 0 24px;
+
         md-autocomplete {
           height: 30px;
+
           md-autocomplete-wrap {
             height: 30px;
           }
         }
       }
+
       .md-chips .md-chip-input-container input {
-        padding: 2px 2px 2px;
         height: 26px;
+        padding: 2px 2px 2px;
         line-height: 26px;
       }
-
     }
 
     .md-button {
       margin: 0;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/entity/relation/relation-table.scss b/ui/src/app/entity/relation/relation-table.scss
index 2d618fb..873c9d7 100644
--- a/ui/src/app/entity/relation/relation-table.scss
+++ b/ui/src/app/entity/relation/relation-table.scss
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../../scss/constants';
+@import "../../../scss/constants";
 
-$md-light: rgba(255, 255, 255, 100%);
+$md-light: rgba(255, 255, 255, 100%) !default;
 
 .tb-relation-table {
   md-toolbar.md-table-toolbar.alternate {
diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.scss b/ui/src/app/entity/relation/relation-type-autocomplete.scss
index 2293d79..6a06aa3 100644
--- a/ui/src/app/entity/relation/relation-type-autocomplete.scss
+++ b/ui/src/app/entity/relation/relation-type-autocomplete.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-relation-type-autocomplete {
   .tb-relation-type-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss
index b0fc46f..5a13d1f 100644
--- a/ui/src/app/event/event.scss
+++ b/ui/src/app/event/event.scss
@@ -13,24 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 md-list.tb-event-table {
-    padding: 0px;
+  padding: 0;
 
-    md-list-item {
-      padding: 0px;
-    }
+  md-list-item {
+    padding: 0;
+  }
 
   .tb-row {
     height: 48px;
-    padding: 0px;
+    padding: 0;
     overflow: hidden;
+
     .tb-cell {
       text-overflow: ellipsis;
+
       &.tb-scroll {
-        white-space: nowrap;
-        overflow-y: hidden;
         overflow-x: auto;
+        overflow-y: hidden;
+        white-space: nowrap;
       }
+
       &.tb-nowrap {
         white-space: nowrap;
       }
@@ -38,7 +42,7 @@ md-list.tb-event-table {
   }
 
   .tb-row:hover {
-    background-color: #EEEEEE;
+    background-color: #eee;
   }
 
   .tb-header:hover {
@@ -46,44 +50,45 @@ md-list.tb-event-table {
   }
 
   .tb-header {
-      .tb-cell {
-        color: rgba(0,0,0,.54);
-        font-size: 12px;
-        font-weight: 700;
-        background: none;
-        white-space: nowrap;
-      }
+    .tb-cell {
+      font-size: 12px;
+      font-weight: 700;
+      color: rgba(0, 0, 0, .54);
+      white-space: nowrap;
+      background: none;
+    }
   }
 
   .tb-cell {
-      &:first-child {
-        padding-left: 14px;
-      }
-      &:last-child {
-        padding-right: 14px;
-      }
-      padding: 0 6px;
-      margin: auto 0;
-      color: rgba(0,0,0,.87);
-      font-size: 13px;
-      vertical-align: middle;
-      text-align: left;
-      overflow: hidden;
-      .md-button {
-          padding: 0;
-          margin: 0;
-      }
+    &:first-child {
+      padding-left: 14px;
+    }
+
+    &:last-child {
+      padding-right: 14px;
+    }
+    padding: 0 6px;
+    margin: auto 0;
+    overflow: hidden;
+    font-size: 13px;
+    color: rgba(0, 0, 0, .87);
+    text-align: left;
+    vertical-align: middle;
+
+    .md-button {
+      padding: 0;
+      margin: 0;
+    }
   }
 
   .tb-cell.tb-number {
     text-align: right;
   }
-
 }
 
 #tb-event-content {
-  min-width: 400px;
-  min-height: 50px;
   width: 100%;
+  min-width: 400px;
   height: 100%;
+  min-height: 50px;
 }
diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss
index c7fcbf4..2332cf5 100644
--- a/ui/src/app/extension/extensions-forms/extension-form.scss
+++ b/ui/src/app/extension/extensions-forms/extension-form.scss
@@ -13,28 +13,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .extension-form {
   li > .md-button {
-    color: rgba(0, 0, 0, 0.7);
     margin: 0;
+    color: rgba(0, 0, 0, .7);
   }
+
   .vAccordion--default {
-    margin-top: 0;
     padding-left: 3px;
+    margin-top: 0;
   }
+
   .tb-container {
-    width:100%;
+    width: 100%;
   }
+
   .dropdown-messages {
     .tb-error-message {
       padding: 5px 0 0 0;
     }
   }
+
   .dropdown-section {
     margin-bottom: 30px;
   }
+
   v-pane.inner-invalid > v-pane-header {
-    border-bottom: 2px solid rgb(221,44,0);
+    border-bottom: 2px solid rgb(221, 44, 0);
   }
 }
 
@@ -45,17 +51,19 @@
 }
 
 .tb-extension-custom-transformer-panel {
-  margin-left: 15px;
-  border: 1px solid #C0C0C0;
   height: 100%;
+  margin-left: 15px;
+  border: 1px solid #c0c0c0;
+
   .tb-extension-custom-transformer {
-    min-width: 600px;
-    min-height: 200px;
     width: 100%;
+    min-width: 600px;
     height: 100%;
+    min-height: 200px;
   }
+
   .ace_text-input {
-    position:absolute!important
+    position: absolute !important;
   }
 }
 
@@ -68,9 +76,9 @@
 }
 
 .tb-drop-file-input-hide {
-  height: 200%;
-  display: block;
   position: absolute;
   bottom: 0;
+  display: block;
   width: 100%;
-}
\ No newline at end of file
+  height: 200%;
+}
diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss
index c8946df..b809622 100644
--- a/ui/src/app/extension/extension-table.scss
+++ b/ui/src/app/extension/extension-table.scss
@@ -13,23 +13,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 
 .extension-table {
-
   md-input-container .md-errors-spacer {
     min-height: 0;
   }
 
-  /*&.tb-data-table table.md-table tbody tr td.tb-action-cell,
+  /*
+  &.tb-data-table table.md-table tbody tr td.tb-action-cell,
   &.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell {
     width: 114px;
-  }*/
+  }
+  */
+
   .sync-widget {
     max-height: 90px;
     overflow: hidden;
   }
+
   .toolbar-widget {
     min-height: 39px;
     max-height: 39px;
@@ -37,11 +40,13 @@
 }
 
 .extension__syncStatus--black {
-  color: #000000!important;
+  color: #000 !important;
 }
+
 .extension__syncStatus--green {
-  color: #228634!important;
+  color: #228634 !important;
 }
+
 .extension__syncStatus--red {
-  color: #862222!important;
-}
\ No newline at end of file
+  color: #862222 !important;
+}
diff --git a/ui/src/app/help/help.scss b/ui/src/app/help/help.scss
index b37dc31..0dd2bc3 100644
--- a/ui/src/app/help/help.scss
+++ b/ui/src/app/help/help.scss
@@ -15,8 +15,12 @@
  */
 @import "../../scss/constants";
 
-.md-button.tb-help-button-style, .tb-help-button-style {
+/*
+.md-button.tb-help-button-style,
+.tb-help-button-style {
 }
 
-.md-button.tb-help-button-pos, .tb-help-button-pos {
+.md-button.tb-help-button-pos,
+.tb-help-button-pos {
 }
+*/
diff --git a/ui/src/app/home/home-links.scss b/ui/src/app/home/home-links.scss
index 8476eb4..677620d 100644
--- a/ui/src/app/home/home-links.scss
+++ b/ui/src/app/home/home-links.scss
@@ -16,10 +16,11 @@
 @import "../../scss/constants";
 
 .tb-home-links {
-    .md-headline {
-      font-size: 20px;
-      @media (min-width: $layout-breakpoint-xmd) {
-        font-size: 24px;
-      }
+  .md-headline {
+    font-size: 20px;
+
+    @media (min-width: $layout-breakpoint-xmd) {
+      font-size: 24px;
     }
-}
\ No newline at end of file
+  }
+}
diff --git a/ui/src/app/import-export/import-dialog.scss b/ui/src/app/import-export/import-dialog.scss
index c1561f3..511a910 100644
--- a/ui/src/app/import-export/import-dialog.scss
+++ b/ui/src/app/import-export/import-dialog.scss
@@ -13,29 +13,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$previewSize: 100px;
+$previewSize: 100px !default;
 
 .tb-file-select-container {
   position: relative;
-  height: $previewSize;
   width: 100%;
+  height: $previewSize;
 }
 
 .tb-file-preview {
-  max-width: $previewSize;
-  max-height: $previewSize;
   width: auto;
+  max-width: $previewSize;
   height: auto;
+  max-height: $previewSize;
 }
 
 .tb-file-clear-container {
-  width: 48px;
-  height: $previewSize;
   position: relative;
   float: right;
+  width: 48px;
+  height: $previewSize;
 }
+
 .tb-file-clear-btn {
   position: absolute !important;
   top: 50%;
-  transform: translate(0%,-50%) !important;
+  transform: translate(0%, -50%) !important;
 }
diff --git a/ui/src/app/jsonform/jsonform.scss b/ui/src/app/jsonform/jsonform.scss
index 8205fa4..6b1c258 100644
--- a/ui/src/app/jsonform/jsonform.scss
+++ b/ui/src/app/jsonform/jsonform.scss
@@ -13,5 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-.form {  height: 400px;  }
-.schema {  height: 800px;  }
\ No newline at end of file
+
+.form { height: 400px; }
+
+.schema { height: 800px; }
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index b1bb554..48c1553 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -29,23 +29,31 @@
 .tb-breadcrumb {
   font-size: 18px !important;
   font-weight: 400 !important;
-  h1, a, span {
+
+  h1,
+  a,
+  span {
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
+
   a {
     border: none;
-    opacity: 0.75;
-    @include transition(opacity 0.35s);
+    opacity: .75;
+
+    @include transition(opacity .35s);
   }
-  a:hover, a:focus {
-    opacity: 1;
+
+  a:hover,
+  a:focus {
     text-decoration: none !important;
     border: none;
+    opacity: 1;
   }
+
   .divider {
-    padding: 0px 30px;
+    padding: 0 30px;
   }
 }
 
@@ -54,21 +62,22 @@ md-sidenav.tb-site-sidenav {
 }
 
 md-icon.tb-logo-title {
-  height: 36px;
   width: 200px;
+  height: 36px;
 }
 
 .tb-nav-header {
-  flex-shrink: 0;
   z-index: 2;
+  flex-shrink: 0;
   white-space: nowrap;
 }
 
 .tb-nav-header-toolbar {
-  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
-  flex-shrink: 0;
   z-index: 2;
+  flex-shrink: 0;
   white-space: nowrap;
+  border-bottom: 1px solid rgba(0, 0, 0, .12);
+
   .md-toolbar-tools {
     flex-basis: auto;
   }
diff --git a/ui/src/app/layout/user-menu.scss b/ui/src/app/layout/user-menu.scss
index 9512bca..8b684ee 100644
--- a/ui/src/app/layout/user-menu.scss
+++ b/ui/src/app/layout/user-menu.scss
@@ -13,28 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 div.tb-user-info {
   line-height: 1.5;
+
   span {
-    text-transform: none;
     text-align: left;
+    text-transform: none;
   }
+
   span.tb-user-display-name {
-    font-size: 0.800rem;
+    font-size: .8rem;
     font-weight: 300;
-    letter-spacing: 0.008em;
+    letter-spacing: .008em;
   }
+
   span.tb-user-authority {
-    font-size: 0.800rem;
+    font-size: .8rem;
     font-weight: 300;
-    letter-spacing: 0.005em;
-    opacity: 0.8;
+    letter-spacing: .005em;
+    opacity: .8;
   }
 }
 
 md-icon.tb-mini-avatar {
+  width: 36px;
+  height: 36px;
   margin: auto 8px;
   font-size: 36px;
-  height: 36px;
-  width: 36px;
 }
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 6321d63..2caf013 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -133,8 +133,13 @@
         "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} }?",
+        "aknowledge-alarm-title": "Acknowledge Alarm",
+        "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?",
         "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} }?"
+        "clear-alarms-text": "Are you sure you want to clear { 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"
     },
     "alias": {
         "add": "Add alias",
@@ -748,7 +753,8 @@
         "entity-name": "Entity name",
         "details": "Entity details",
         "no-entities-prompt": "No entities found",
-        "no-data": "No data to display"
+        "no-data": "No data to display",
+        "columns-to-display": "Columns to Display"
     },
     "event": {
         "event-type": "Event type",
diff --git a/ui/src/app/login/login.scss b/ui/src/app/login/login.scss
index 373a68f..2930ab9 100644
--- a/ui/src/app/login/login.scss
+++ b/ui/src/app/login/login.scss
@@ -13,18 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 md-card.tb-login-card {
   width: 330px !important;
+
   @media (min-width: $layout-breakpoint-sm) {
     width: 450px !important;
   }
+
   md-card-title {
     img.tb-login-logo {
       height: 50px;
     }
   }
+
   md-card-content {
     margin-top: -50px;
   }
diff --git a/ui/src/app/rulechain/link.scss b/ui/src/app/rulechain/link.scss
index 3e86619..b37eaf3 100644
--- a/ui/src/app/rulechain/link.scss
+++ b/ui/src/app/rulechain/link.scss
@@ -17,12 +17,14 @@
 .tb-link-label-autocomplete {
   .tb-not-found {
     display: block;
-    line-height: 1.5;
     height: 48px;
+    line-height: 1.5;
+
     .tb-no-entries {
       line-height: 48px;
     }
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/rulechain/message-type-autocomplete.scss b/ui/src/app/rulechain/message-type-autocomplete.scss
index 4cad9cb..fc702d4 100644
--- a/ui/src/app/rulechain/message-type-autocomplete.scss
+++ b/ui/src/app/rulechain/message-type-autocomplete.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-message-type-autocomplete {
   .tb-message-type-item {
     display: block;
     height: 48px;
   }
+
   li {
     height: auto !important;
     white-space: normal !important;
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 5999b7e..8f77300 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -18,88 +18,106 @@
   .tb-fullscreen-button-style {
     z-index: 1;
   }
+
   section.tb-header-buttons.tb-library-open {
-    pointer-events: none;
     position: absolute;
-    left: 0px;
-    top: 0px;
+    top: 0;
+    left: 0;
     z-index: 1;
+    pointer-events: none;
+
     .md-button.tb-btn-open-library {
-      left: 0px;
-      top: 0px;
-      line-height: 36px;
+      top: 0;
+      left: 0;
       width: 36px;
       height: 36px;
       margin: 4px 0 0 4px;
-      opacity: 0.5;
+      line-height: 36px;
+      opacity: .5;
     }
   }
+
   .tb-rulechain-library {
+    z-index: 1;
     width: 250px;
     min-width: 250px;
-    z-index: 1;
+
     md-toolbar {
-      min-height: 48px;
       height: 48px;
-      .md-toolbar-tools>.md-button:last-child {
-        margin-right: 0px;
+      min-height: 48px;
+
+      .md-toolbar-tools > .md-button:last-child {
+        margin-right: 0;
       }
+
       .md-toolbar-tools {
-        font-size: 14px;
-        padding: 0px 6px;
         height: 48px;
+        padding: 0 6px;
+        font-size: 14px;
+
         .md-button.md-icon-button {
-          margin: 0px;
+          margin: 0;
+
           &.tb-small {
+            width: 32px;
             height: 32px;
             min-height: 32px;
-            line-height: 20px;
             padding: 6px;
-            width: 32px;
+            line-height: 20px;
+
             md-icon {
-              line-height: 20px;
-              font-size: 20px;
-              height: 20px;
               width: 20px;
-              min-height: 20px;
               min-width: 20px;
+              height: 20px;
+              min-height: 20px;
+              font-size: 20px;
+              line-height: 20px;
             }
           }
         }
       }
     }
+
     .tb-rulechain-library-panel-group {
-      overflow-y: auto;
       overflow-x: hidden;
+      overflow-y: auto;
+
       .tb-panel-title {
+        min-width: 150px;
         -webkit-user-select: none;
         -moz-user-select: none;
         -ms-user-select: none;
         user-select: none;
-        min-width: 150px;
       }
+
       .fc-canvas {
         background: #f9f9f9;
       }
+
       md-icon.md-expansion-panel-icon {
-        margin-right: 0px;
+        margin-right: 0;
       }
-      md-expansion-panel-collapsed, .md-expansion-panel-header-container {
+
+      md-expansion-panel-collapsed,
+      .md-expansion-panel-header-container {
+        position: static;
         background: #e6e6e6;
         border-color: #909090;
-        position: static;
       }
+
       md-expansion-panel {
         &.md-open {
           margin-top: 0;
           margin-bottom: 0;
         }
       }
+
       md-expansion-panel-content {
-        padding: 0px;
+        padding: 0;
       }
     }
   }
+
   .tb-rulechain-graph {
     z-index: 0;
     overflow: auto;
@@ -107,29 +125,35 @@
 }
 
 #tb-rule-chain-context-menu {
-  padding-top: 0px;
-  border-radius: 8px;
   max-height: 404px;
+  padding-top: 0;
+  border-radius: 8px;
+
   .tb-context-menu-header {
-    padding: 8px 5px 5px;
-    font-size: 14px;
     display: flex;
     flex-direction: row;
     height: 36px;
     min-height: 36px;
+    padding: 8px 5px 5px;
+    font-size: 14px;
+
     &.tb-rulechain {
       background-color: #aac7e4;
     }
+
     &.tb-link {
       background-color: #aac7e4;
     }
+
     md-icon {
-      padding-left: 2px;
       padding-right: 10px;
+      padding-left: 2px;
     }
+
     .tb-context-menu-title {
       font-weight: 500;
     }
+
     .tb-context-menu-subtitle {
       font-size: 12px;
     }
@@ -139,37 +163,45 @@
 .fc-canvas {
   min-width: 100%;
   min-height: 100%;
-  outline: none;
-  -webkit-touch-callout: none;
   -webkit-user-select: none;
   -khtml-user-select: none;
   -moz-user-select: none;
   -ms-user-select: none;
   user-select: none;
+  outline: none;
+  -webkit-touch-callout: none;
+
   svg {
     display: block;
   }
 }
 
-.tb-rule-node, #tb-rule-chain-context-menu .tb-context-menu-header {
+.tb-rule-node,
+#tb-rule-chain-context-menu .tb-context-menu-header {
   &.tb-filter-type {
     background-color: #f1e861;
   }
+
   &.tb-enrichment-type {
     background-color: #cdf14e;
   }
+
   &.tb-transformation-type {
     background-color: #79cef1;
   }
+
   &.tb-action-type {
     background-color: #f1928f;
   }
+
   &.tb-external-type {
     background-color: #fbc766;
   }
+
   &.tb-rule-chain-type {
     background-color: #d6c4f1;
   }
+
   &.tb-unknown-type {
     background-color: #f16c29;
   }
@@ -180,102 +212,112 @@
   flex-direction: row;
   min-width: 150px;
   max-width: 150px;
+  height: 32px;
   min-height: 32px;
   max-height: 32px;
-  height: 32px;
   padding: 5px 10px;
-  border-radius: 5px;
-  background-color: #F15B26;
-  pointer-events: none;
-  color: #333;
-  border: solid 1px #777;
   font-size: 12px;
   line-height: 16px;
+  color: #333;
+  pointer-events: none;
+  background-color: #f15b26;
+  border: solid 1px #777;
+  border-radius: 5px;
+
   &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) {
     box-shadow: 0 0 10px 6px #51cbee;
+
     .tb-node-title {
+      font-weight: 700;
       text-decoration: underline;
-      font-weight: bold;
     }
   }
+
   &.tb-rule-node-invalid {
     box-shadow: 0 0 10px 6px #ff5c50;
   }
+
   &.tb-input-type {
-    background-color: #a3eaa9;
     user-select: none;
+    background-color: #a3eaa9;
   }
 
   md-icon {
-    font-size: 20px;
     width: 20px;
+    min-width: 20px;
     height: 20px;
     min-height: 20px;
-    min-width: 20px;
     padding-right: 4px;
+    font-size: 20px;
   }
-  .tb-node-type {
 
-  }
   .tb-node-title {
     font-weight: 500;
   }
-  .tb-node-type, .tb-node-title {
+
+  .tb-node-type,
+  .tb-node-title {
     overflow: hidden;
-    white-space: nowrap;
     text-overflow: ellipsis;
+    white-space: nowrap;
   }
 }
 
 .fc-node {
   z-index: 1;
-  outline: none;
   border-radius: 8px;
+  outline: none;
+
   &.fc-dragging {
     z-index: 10;
   }
+
   p {
     padding: 0 15px;
     text-align: center;
   }
+
   .fc-node-overlay {
     position: absolute;
-    pointer-events: none;
-    left: 0;
     top: 0;
     right: 0;
     bottom: 0;
+    left: 0;
+    pointer-events: none;
     background-color: #000;
-    opacity: 0;
     border-radius: 5px;
+    opacity: 0;
   }
+
   &.fc-hover {
     .fc-node-overlay {
-      opacity: 0.25;
+      opacity: .25;
     }
   }
+
   &.fc-selected {
     .fc-node-overlay {
-      opacity: 0.25;
+      opacity: .25;
     }
-  }
-  &.fc-selected {
+
     &:not(.fc-edit) {
-      border: solid 3px red;
       margin: -3px;
+      border: solid 3px #f00;
     }
   }
 }
 
-.fc-leftConnectors, .fc-rightConnectors {
+.fc-leftConnectors,
+.fc-rightConnectors {
   position: absolute;
   top: 0;
-  height: 100%;
+
+  z-index: 0;
 
   display: flex;
   flex-direction: column;
+  height: 100%;
 
-  z-index: 0;
   .fc-magnet {
     align-items: center;
   }
@@ -292,18 +334,18 @@
 .fc-magnet {
   display: flex;
   flex-grow: 1;
-  height: 60px;
   justify-content: center;
+  height: 60px;
 }
 
 .fc-connector {
   width: 14px;
   height: 14px;
-  border: 1px solid #333;
   margin: 10px;
-  border-radius: 5px;
-  background-color: #ccc;
   pointer-events: all;
+  background-color: #ccc;
+  border: 1px solid #333;
+  border-radius: 5px;
 }
 
 .fc-connector.fc-hover {
@@ -312,45 +354,49 @@
 
 .fc-arrow-marker {
   polygon {
-    stroke: gray;
-    fill: gray;
+    fill: #808080;
+    stroke: #808080;
   }
 }
 
 .fc-arrow-marker-selected {
   polygon {
-    stroke: red;
-    fill: red;
+    fill: #f00;
+    stroke: #f00;
   }
 }
 
 .fc-edge {
   outline: none;
-  stroke: gray;
-  stroke-width: 4;
-  fill: transparent;
   transition: stroke-width .2s;
+  fill: transparent;
+  stroke: #808080;
+  stroke-width: 4;
+
   &.fc-selected {
-    stroke: red;
-    stroke-width: 4;
     fill: transparent;
+    stroke: #f00;
+    stroke-width: 4;
   }
+
   &.fc-active {
     animation: dash 3s linear infinite;
     stroke-dasharray: 20;
   }
+
   &.fc-hover {
-    stroke: gray;
-    stroke-width: 6;
     fill: transparent;
+    stroke: #808080;
+    stroke-width: 6;
   }
+
   &.fc-dragging {
     pointer-events: none;
   }
 }
 
 .edge-endpoint {
-  fill: gray;
+  fill: #808080;
 }
 
 .fc-nodedelete {
@@ -364,22 +410,23 @@
 }
 
 .fc-edit {
-  .fc-nodedelete, .fc-nodeedit {
-    outline: none;
-    display: block;
+  .fc-nodedelete,
+  .fc-nodeedit {
     position: absolute;
-    border: solid 2px white;
-    border-radius: 50%;
-    font-weight: 600;
-    line-height: 20px;
+    display: block;
+    width: 22px;
     height: 20px;
     padding-top: 2px;
-    width: 22px;
-    background: #f83e05;
+    font-weight: 600;
+    line-height: 20px;
     color: #fff;
     text-align: center;
     vertical-align: bottom;
     cursor: pointer;
+    background: #f83e05;
+    border: solid 2px #fff;
+    border-radius: 50%;
+    outline: none;
   }
 
   .fc-nodeedit {
@@ -391,7 +438,6 @@
     top: -24px;
     right: -13px;
   }
-
 }
 
 .fc-noselect {
@@ -407,31 +453,37 @@
 .fc-edge-label {
   position: absolute;
   transition: transform .2s;
-//  opacity: 0.8;
+  //  opacity: 0.8;
+
   &.ng-leave {
     transition: 0s none;
   }
+
   &.fc-hover {
     transform: scale(1.25);
   }
+
   &.fc-selected {
     .fc-edge-label-text {
       span {
-        border: solid red;
-        color: #fff;
         font-weight: 600;
-        background-color: red;
+        color: #fff;
+        background-color: #f00;
+        border: solid #f00;
       }
     }
   }
+
   .fc-nodeedit {
     top: -30px;
     right: 14px;
   }
+
   .fc-nodedelete {
     top: -30px;
     right: -13px;
   }
+
   &:focus {
     outline: 0;
   }
@@ -439,27 +491,28 @@
 
 .fc-edge-label-text {
   position: absolute;
-  -webkit-transform: translate(-50%, -50%);
-  transform: translate(-50%, -50%);
-  white-space: nowrap;
-  text-align: center;
   font-size: 14px;
   font-weight: 600;
+  text-align: center;
+  white-space: nowrap;
+  -webkit-transform: translate(-50%, -50%);
+  transform: translate(-50%, -50%);
+
   span {
+    padding: 3px 5px;
+    color: #003a79;
     cursor: default;
+    background-color: #fff;
     border: solid 2px #003a79;
     border-radius: 10px;
-    color: #003a79;
-    background-color: #fff;
-    padding: 3px 5px;
   }
 }
 
 .fc-select-rectangle {
-  border: 2px dashed #5262ff;
   position: absolute;
-  background: rgba(20,125,255,0.1);
   z-index: 2;
+  background: rgba(20, 125, 255, .1);
+  border: 2px dashed #5262ff;
 }
 
 @keyframes dash {
@@ -468,13 +521,15 @@
   }
 }
 
-.tb-rule-node-tooltip, .tb-rule-node-help {
+.tb-rule-node-tooltip,
+.tb-rule-node-help {
   color: #333;
 }
 
 .tb-rule-node-tooltip {
-  font-size: 14px;
   max-width: 300px;
+  font-size: 14px;
+
   &.tb-lib-tooltip {
     width: 300px;
   }
@@ -489,28 +544,33 @@
   color: #ea0d0d;
 }
 
-.tb-rule-node-tooltip, .tb-rule-node-error-tooltip, .tb-rule-node-help {
+.tb-rule-node-tooltip,
+.tb-rule-node-error-tooltip,
+.tb-rule-node-help {
   #tb-node-content {
     .tb-node-title {
       font-weight: 600;
     }
+
     .tb-node-description {
       font-style: italic;
       color: #555;
     }
+
     .tb-node-details {
       padding-top: 10px;
       padding-bottom: 10px;
     }
+
     code {
-      padding: 0px 3px 2px 3px;
+      padding: 0 3px 2px 3px;
       margin: 1px;
-      color: #AD1625;
+      font-size: 12px;
+      color: #ad1625;
       white-space: nowrap;
       background-color: #f7f7f9;
       border: 1px solid #e1e1e8;
       border-radius: 2px;
-      font-size: 12px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss
index 0466673..0bdb3ab 100644
--- a/ui/src/app/rulechain/rulenode.scss
+++ b/ui/src/app/rulechain/rulenode.scss
@@ -16,13 +16,13 @@
 
 .tb-rulenode {
   tb-json-object-edit.tb-rule-node-configuration-json {
-    height: 300px;
     display: block;
+    height: 300px;
   }
 }
 
 .tb-rulenode-directive-error {
-  color: rgb(221,44,0);
   font-size: 13px;
   font-weight: 400;
-}
\ No newline at end of file
+  color: rgb(221, 44, 0);
+}
diff --git a/ui/src/app/rulechain/script/node-script-test.scss b/ui/src/app/rulechain/script/node-script-test.scss
index a75ae59..1dd174d 100644
--- a/ui/src/app/rulechain/script/node-script-test.scss
+++ b/ui/src/app/rulechain/script/node-script-test.scss
@@ -13,24 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../../scss/constants';
+@import "../../../scss/constants";
+
 @import "~compass-sass-mixins/lib/compass";
 
 md-dialog.tb-node-script-test-dialog {
   &.md-dialog-fullscreen {
-    min-height: 100%;
+    width: 100%;
     min-width: 100%;
-    max-height: 100%;
     max-width: 100%;
-    width: 100%;
     height: 100%;
+    min-height: 100%;
+    max-height: 100%;
     border-radius: 0;
   }
 
   .tb-split {
     @include box-sizing(border-box);
-    overflow-y: auto;
     overflow-x: hidden;
+    overflow-y: auto;
   }
 
   .ace_editor {
@@ -38,13 +39,13 @@ md-dialog.tb-node-script-test-dialog {
   }
 
   .tb-content {
-    border: 1px solid #C0C0C0;
     padding-top: 5px;
     padding-left: 5px;
+    border: 1px solid #c0c0c0;
   }
 
   .gutter {
-    background-color: #eeeeee;
+    background-color: #eee;
 
     background-repeat: no-repeat;
     background-position: 50%;
@@ -52,21 +53,23 @@ md-dialog.tb-node-script-test-dialog {
 
   .gutter.gutter-horizontal {
     cursor: col-resize;
-    background-image: url('../../../../node_modules/split.js/grips/vertical.png');
+    background-image: url("../../../../node_modules/split.js/grips/vertical.png");
   }
 
   .gutter.gutter-vertical {
     cursor: row-resize;
-    background-image: url('../../../../node_modules/split.js/grips/horizontal.png');
+    background-image: url("../../../../node_modules/split.js/grips/horizontal.png");
   }
 
-  .tb-split.tb-split-horizontal, .gutter.gutter-horizontal {
-    height: 100%;
+  .tb-split.tb-split-horizontal,
+  .gutter.gutter-horizontal {
     float: left;
+    height: 100%;
   }
 
   .tb-split.tb-split-vertical {
     display: flex;
+
     .tb-split.tb-content {
       height: 100%;
     }
@@ -74,42 +77,44 @@ md-dialog.tb-node-script-test-dialog {
 
   div.tb-editor-area-title-panel {
     position: absolute;
-    font-size: 0.800rem;
-    font-weight: 500;
     top: 13px;
     right: 40px;
     z-index: 5;
+    font-size: .8rem;
+    font-weight: 500;
+
     &.tb-js-function {
       right: 80px;
     }
+
     label {
-      color: #00acc1;
-      background: rgba(220, 220, 220, 0.35);
-      border-radius: 5px;
       padding: 4px;
+      color: #00acc1;
       text-transform: uppercase;
+      background: rgba(220, 220, 220, .35);
+      border-radius: 5px;
     }
+
     .md-button {
-      color: #7B7B7B;
       min-width: 32px;
       min-height: 15px;
-      line-height: 15px;
-      font-size: 0.800rem;
-      margin: 0;
       padding: 4px;
-      background: rgba(220, 220, 220, 0.35);
+      margin: 0;
+      font-size: .8rem;
+      line-height: 15px;
+      color: #7b7b7b;
+      background: rgba(220, 220, 220, .35);
     }
   }
 
   .tb-resize-container {
-    overflow-y: auto;
-    height: 100%;
-    width: 100%;
     position: relative;
+    width: 100%;
+    height: 100%;
+    overflow-y: auto;
 
     .ace_editor {
       height: 100%;
     }
   }
-
 }
diff --git a/ui/src/app/services/toast.scss b/ui/src/app/services/toast.scss
index 428e94e..952a42d 100644
--- a/ui/src/app/services/toast.scss
+++ b/ui/src/app/services/toast.scss
@@ -13,20 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 md-toast.tb-info-toast .md-toast-content {
-  font-size: 18px;
-  padding: 18px;
   height: 100%;
+  padding: 18px;
+  font-size: 18px;
 }
 
 md-toast.tb-success-toast .md-toast-content {
-  font-size: 18px !important;
-  background-color: green;
   height: 100%;
+  font-size: 18px !important;
+  background-color: #008000;
 }
 
 md-toast.tb-error-toast .md-toast-content {
-  font-size: 18px !important;
-  background-color: maroon;
   height: 100%;
+  font-size: 18px !important;
+  background-color: #800000;
 }
diff --git a/ui/src/app/user/user-fieldset.scss b/ui/src/app/user/user-fieldset.scss
index cf21abc..93b8b51 100644
--- a/ui/src/app/user/user-fieldset.scss
+++ b/ui/src/app/user/user-fieldset.scss
@@ -13,18 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@import '../../scss/constants';
+@import "../../scss/constants";
 
 .tb-default-dashboard {
   .tb-default-dashboard-label {
     padding-bottom: 8px;
   }
+
   tb-dashboard-autocomplete {
     @media (min-width: $layout-breakpoint-sm) {
       padding-right: 12px;
     }
+
     @media (max-width: $layout-breakpoint-sm) {
       padding-bottom: 12px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 0696a7b..6ae17e0 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 import './alarms-table-widget.scss';
+import './display-columns-panel.scss';
+import './alarm-status-filter-panel.scss';
 
 /* eslint-disable import/no-unresolved, import/default */
 
 import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
 import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
+import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
@@ -45,7 +49,7 @@ function AlarmsTableWidget() {
 }
 
 /*@ngInject*/
-function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) {
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) {
     var vm = this;
 
     vm.stylesInfo = {};
@@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.selectedAlarms = []
 
     vm.alarmSource = null;
+    vm.alarmSearchStatus = null;
     vm.allAlarms = [];
 
     vm.currentAlarm = null;
@@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.onPaginate = onPaginate;
     vm.onRowClick = onRowClick;
     vm.onActionButtonClick = onActionButtonClick;
+    vm.actionEnabled = actionEnabled;
     vm.isCurrent = isCurrent;
     vm.openAlarmDetails = openAlarmDetails;
     vm.ackAlarms = ackAlarms;
+    vm.ackAlarm = ackAlarm;
     vm.clearAlarms = clearAlarms;
+    vm.clearAlarm = clearAlarm;
 
     vm.cellStyle = cellStyle;
     vm.cellContent = cellContent;
 
+    vm.editAlarmStatusFilter = editAlarmStatusFilter;
+    vm.editColumnsToDisplay = editColumnsToDisplay;
+
     $scope.$watch('vm.ctx', function() {
         if (vm.ctx) {
             vm.settings = vm.ctx.settings;
@@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
 
         vm.ctx.widgetActions = [ vm.searchAction ];
 
-        vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
+        vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
+        vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
+        vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
+
+        if (vm.displayDetails) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.details'),
+                    icon: 'more_horiz',
+                    details: true
+                }
+            );
+        }
+
+        if (vm.allowAcknowledgment) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.acknowledge'),
+                    icon: 'done',
+                    acknowledge: true
+                }
+            );
+        }
+
+        if (vm.allowClear) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.clear'),
+                    icon: 'clear',
+                    clear: true
+                }
+            );
+        }
+
+        vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton'));
 
         if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
             vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
@@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
 
         vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
         vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
-        vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
-        vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
-        vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
         if (!vm.allowAcknowledgment && !vm.allowClear) {
             vm.enableSelection = false;
         }
@@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     }
 
     function onActionButtonClick($event, alarm, actionDescriptor) {
-        if ($event) {
-            $event.stopPropagation();
+        if (actionDescriptor.details) {
+            vm.openAlarmDetails($event, alarm);
+        } else if (actionDescriptor.acknowledge) {
+            vm.ackAlarm($event, alarm);
+        } else if (actionDescriptor.clear) {
+            vm.clearAlarm($event, alarm);
+        } else {
+            if ($event) {
+                $event.stopPropagation();
+            }
+            var entityId;
+            var entityName;
+            if (alarm && alarm.originator) {
+                entityId = alarm.originator;
+                entityName = alarm.originatorName;
+            }
+            vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm});
         }
-        var entityId;
-        var entityName;
-        if (alarm && alarm.originator) {
-            entityId = alarm.originator;
-            entityName = alarm.originatorName;
+    }
+
+    function actionEnabled(alarm, actionDescriptor) {
+        if (actionDescriptor.acknowledge) {
+            return (alarm.status == types.alarmStatus.activeUnack ||
+                    alarm.status == types.alarmStatus.clearedUnack);
+        } else if (actionDescriptor.clear) {
+            return (alarm.status == types.alarmStatus.activeAck ||
+                    alarm.status == types.alarmStatus.activeUnack);
         }
-        vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm });
+        return true;
     }
 
     function isCurrent(alarm) {
@@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function ackAlarm($event, alarm) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('alarm.aknowledge-alarm-title'))
+            .htmlContent($translate.instant('alarm.aknowledge-alarm-text'))
+            .ariaLabel($translate.instant('alarm.acknowledge'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            alarmService.ackAlarm(alarm.id.id).then(function () {
+                vm.selectedAlarms = [];
+                vm.subscription.update();
+            });
+        });
+    }
+
     function clearAlarms($event) {
         if ($event) {
             $event.stopPropagation();
@@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function clearAlarm($event, alarm) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('alarm.clear-alarm-title'))
+            .htmlContent($translate.instant('alarm.clear-alarm-text'))
+            .ariaLabel($translate.instant('alarm.clear'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            alarmService.clearAlarm(alarm.id.id).then(function () {
+                vm.selectedAlarms = [];
+                vm.subscription.update();
+            });
+        });
+    }
 
     function updateAlarms(preserveSelections) {
         if (!preserveSelections) {
@@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function editAlarmStatusFilter($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: AlarmStatusFilterPanelController,
+            controllerAs: 'vm',
+            templateUrl: alarmStatusFilterPanelTemplate,
+            panelClass: 'tb-alarm-status-filter-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'subscription': vm.subscription
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
+    function editColumnsToDisplay($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: DisplayColumnsPanelController,
+            controllerAs: 'vm',
+            templateUrl: displayColumnsPanelTemplate,
+            panelClass: 'tb-display-columns-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'columns': vm.alarmSource.dataKeys
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
     function updateAlarmSource() {
 
         vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle);
@@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
             var dataKey = vm.alarmSource.dataKeys[d];
 
             dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
+            dataKey.display = true;
 
             var keySettings = dataKey.settings;
 
@@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) {  //eslint-disable-line
+
+    var vm = this;
+    vm.columns = columns;
+}
+
+/*@ngInject*/
+function AlarmStatusFilterPanelController(subscription, types) {  //eslint-disable-line
+
+    var vm = this;
+    vm.types = types;
+    vm.subscription = subscription;
+}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss
index 6a71ccd..a031523 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.scss
+++ b/ui/src/app/widget/lib/alarms-table-widget.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-has-timewindow {
   .tb-alarms-table {
     md-toolbar {
       min-height: 60px;
       max-height: 60px;
+
       &.md-table-toolbar {
         .md-toolbar-tools {
           max-height: 60px;
@@ -28,10 +30,10 @@
 }
 
 .tb-alarms-table {
-
   md-toolbar {
     min-height: 39px;
     max-height: 39px;
+
     &.md-table-toolbar {
       .md-toolbar-tools {
         max-height: 39px;
@@ -40,14 +42,45 @@
   }
 
   &.tb-data-table {
-    table.md-table, table.md-table.md-row-select {
+    table.md-table,
+    table.md-table.md-row-select {
+      th.md-column {
+        &.tb-action-cell {
+          .md-button {
+            /* stylelint-disable-next-line selector-max-class */
+            &.md-icon-button {
+              width: 36px;
+              height: 36px;
+              padding: 6px;
+              margin: 0;
+              /* stylelint-disable-next-line selector-max-class */
+              md-icon {
+                width: 24px;
+                height: 24px;
+                font-size: 24px !important;
+                line-height: 24px !important;
+              }
+            }
+          }
+        }
+      }
+
       tbody {
         tr {
           td {
             &.tb-action-cell {
+              width: 36px;
               min-width: 36px;
               max-width: 36px;
-              width: 36px;
+
+              .md-button[disabled] {
+                &.md-icon-button {
+                  /* stylelint-disable-next-line selector-max-class */
+                  md-icon {
+                    color: rgba(0, 0, 0, .38);
+                  }
+                }
+              }
             }
           }
         }
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
index 8480058..39843e1 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -62,33 +62,45 @@
             <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms">
                 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
                 <tr md-row>
-                    <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
-                    <th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th>
-                    <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
+                    <th ng-if="key.display" md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
+                    <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'alarm.alarm-status-filter' | translate}}"
+                                   ng-click="vm.editAlarmStatusFilter($event)">
+                            <md-icon aria-label="{{'alarm.alarm-status-filter' | translate}}"
+                                     class="material-icons">filter_list
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'alarm.alarm-status-filter' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'entity.columns-to-display' | translate}}"
+                                   ng-click="vm.editColumnsToDisplay($event)">
+                            <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+                                     class="material-icons">view_column
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'entity.columns-to-display' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                    </th>
                 </tr>
                 </thead>
                 <tbody md-body>
                 <tr ng-show="vm.alarms.length" md-row md-select="alarm"
                     md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
                     ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
-                    <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
+                    <td ng-if="key.display" md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
                         ng-style="vm.cellStyle(alarm, key)"
                         ng-bind-html="vm.cellContent(alarm, key)">
                     </td>
-                    <td md-cell ng-if="vm.displayDetails" class="tb-action-cell">
-                        <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
-                                   ng-click="vm.openAlarmDetails($event, alarm)" ng-disabled="$root.loading">
-                            <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
-                            <md-tooltip md-direction="top">
-                                {{ 'alarm.details' | translate }}
-                            </md-tooltip>
-                        </md-button>
-                    </td>
-                    <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+                    <td md-cell class="tb-action-cell"
                         ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
                                    maxWidth: vm.actionCellDescriptors.length*36+'px',
                                    width: vm.actionCellDescriptors.length*36+'px'}">
                         <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
+                                   ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)"
                                    aria-label="{{ actionDescriptor.displayName }}"
                                    ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
                             <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.scss b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
new file mode 100644
index 0000000..4bf6eee
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+.tb-alarm-status-filter-panel {
+  min-width: 300px;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  md-content {
+    overflow: hidden;
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
new file mode 100644
index 0000000..45e8414
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
@@ -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.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+    <label class="tb-title" translate>alarm.alarm-status-filter</label>
+    <md-radio-group ng-model="vm.subscription.alarmSearchStatus" class="md-primary">
+        <md-radio-button ng-value="searchStatus"
+                         aria-label="{{ ('alarm.search-status.' + searchStatus) | translate }}"
+                         class="md-primary md-align-top-left md-radio-interactive" ng-repeat="searchStatus in vm.types.alarmSearchStatus">
+            {{ ('alarm.search-status.' + searchStatus) | translate }}
+        </md-radio-button>
+    </md-radio-group>
+</md-content>
diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js
index ee8b0ed..331ce6c 100644
--- a/ui/src/app/widget/lib/CanvasDigitalGauge.js
+++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js
@@ -209,7 +209,7 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
                     this.elementValueClone.renderedValue = this._value;
                 }
                 if (angular.isUndefined(this.elementValueClone.renderedValue)) {
-                    this.elementValueClone.renderedValue = options.minValue;
+                    this.elementValueClone.renderedValue = this.value;
                 }
                 let context = this.contextValueClone;
                 // clear the cache
diff --git a/ui/src/app/widget/lib/display-columns-panel.scss b/ui/src/app/widget/lib/display-columns-panel.scss
new file mode 100644
index 0000000..2e518cb
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+.tb-display-columns-panel {
+  min-width: 300px;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  md-content {
+    overflow: hidden;
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/widget/lib/display-columns-panel.tpl.html b/ui/src/app/widget/lib/display-columns-panel.tpl.html
new file mode 100644
index 0000000..42e2471
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.tpl.html
@@ -0,0 +1,24 @@
+<!--
+
+    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.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+    <label class="tb-title" translate>entity.columns-to-display</label>
+    <md-checkbox aria-label="{{ 'entity.columns-to-display' | translate }}" ng-repeat="column in vm.columns"
+                 ng-model="column.display">{{ column.title }}
+    </md-checkbox>
+</md-content>
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index d0b629d..620f313 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 import './entities-table-widget.scss';
+import './display-columns-panel.scss';
 
 /* eslint-disable import/no-unresolved, import/default */
 
 import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
 //import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
@@ -45,7 +47,7 @@ function EntitiesTableWidget() {
 }
 
 /*@ngInject*/
-function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) {
+function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) {
     var vm = this;
 
     vm.stylesInfo = {};
@@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
     vm.cellStyle = cellStyle;
     vm.cellContent = cellContent;
 
+    vm.editColumnsToDisplay = editColumnsToDisplay;
+
     $scope.$watch('vm.ctx', function() {
         if (vm.ctx && vm.ctx.defaultSubscription) {
             vm.settings = vm.ctx.settings;
@@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
         }
     }
 
+    function editColumnsToDisplay($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: DisplayColumnsPanelController,
+            controllerAs: 'vm',
+            templateUrl: displayColumnsPanelTemplate,
+            panelClass: 'tb-display-columns-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'columns': vm.columns
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
     function updateDatasources() {
 
         vm.stylesInfo = {};
         vm.contentsInfo = {};
         vm.columnWidth = {};
         vm.dataKeys = [];
+        vm.columns = [];
         vm.allEntities = [];
 
         var datasource;
@@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
         vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
 
+        if (vm.displayEntityName) {
+            vm.columns.push(
+                {
+                    name: 'entityName',
+                    label: 'entityName',
+                    title: vm.entityNameColumnTitle,
+                    display: true
+                }
+            );
+            vm.contentsInfo['entityName'] = {
+                useCellContentFunction: false
+            };
+            vm.stylesInfo['entityName'] = {
+                useCellStyleFunction: false
+            };
+            vm.columnWidth['entityName'] = '0px';
+        }
+
+        if (vm.displayEntityType) {
+            vm.columns.push(
+                {
+                    name: 'entityType',
+                    label: 'entityType',
+                    title: $translate.instant('entity.entity-type'),
+                    display: true
+                }
+            );
+            vm.contentsInfo['entityType'] = {
+                useCellContentFunction: false
+            };
+            vm.stylesInfo['entityType'] = {
+                useCellStyleFunction: false
+            };
+            vm.columnWidth['entityType'] = '0px';
+        }
+
         for (var d = 0; d < datasource.dataKeys.length; d++ ) {
             dataKey = angular.copy(datasource.dataKeys[d]);
             if (dataKey.type == types.dataKeyType.function) {
@@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
             var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
             vm.columnWidth[dataKey.label] = columnWidth;
+
+            dataKey.display = true;
+            vm.columns.push(dataKey);
         }
 
         for (var i=0;i<vm.datasources.length;i++) {
@@ -511,4 +579,11 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
     }
 
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) {  //eslint-disable-line
+
+    var vm = this;
+    vm.columns = columns;
+}
diff --git a/ui/src/app/widget/lib/entities-table-widget.scss b/ui/src/app/widget/lib/entities-table-widget.scss
index 62a1f97..d745529 100644
--- a/ui/src/app/widget/lib/entities-table-widget.scss
+++ b/ui/src/app/widget/lib/entities-table-widget.scss
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 .tb-has-timewindow {
   .tb-entities-table {
     md-toolbar {
       min-height: 60px;
       max-height: 60px;
+
       &.md-table-toolbar {
         .md-toolbar-tools {
           max-height: 60px;
@@ -28,10 +30,10 @@
 }
 
 .tb-entities-table {
-
   md-toolbar {
     min-height: 39px;
     max-height: 39px;
+
     &.md-table-toolbar {
       .md-toolbar-tools {
         max-height: 39px;
@@ -40,14 +42,45 @@
   }
 
   &.tb-data-table {
-    table.md-table, table.md-table.md-row-select {
+    table.md-table,
+    table.md-table.md-row-select {
+      th.md-column {
+        &.tb-action-cell {
+          .md-button {
+            /* stylelint-disable-next-line selector-max-class */
+            &.md-icon-button {
+              width: 36px;
+              height: 36px;
+              padding: 6px;
+              margin: 0;
+              /* stylelint-disable-next-line selector-max-class */
+              md-icon {
+                width: 24px;
+                height: 24px;
+                font-size: 24px !important;
+                line-height: 24px !important;
+              }
+            }
+          }
+        }
+      }
+
       tbody {
         tr {
           td {
             &.tb-action-cell {
+              width: 36px;
               min-width: 36px;
               max-width: 36px;
-              width: 36px;
+
+              .md-button[disabled] {
+                &.md-icon-button {
+                  /* stylelint-disable-next-line selector-max-class */
+                  md-icon {
+                    color: rgba(0, 0, 0, .38);
+                  }
+                }
+              }
             }
           }
         }
diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html
index 474d536..66932a0 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html
@@ -41,23 +41,30 @@
             <table md-table>
                 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
                 <tr md-row>
-                    <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
-                    <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
-                    <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
-                    <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
+                    <th ng-if="column.display" md-column md-order-by="{{ column.name }}" ng-repeat="column in vm.columns"><span>{{ column.title }}</span></th>
+                    <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'entity.columns-to-display' | translate}}"
+                                   ng-click="vm.editColumnsToDisplay($event)">
+                            <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+                                     class="material-icons">view_column
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'entity.columns-to-display' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                    </th>
                 </tr>
                 </thead>
                 <tbody md-body>
                 <tr ng-show="vm.entities.length" md-row md-select="entity"
                     md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
                     ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
-                    <td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td>
-                    <td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
-                    <td md-cell flex ng-repeat="key in vm.dataKeys"
-                        ng-style="vm.cellStyle(entity, key)"
-                        ng-bind-html="vm.cellContent(entity, key)">
+                    <td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns"
+                        ng-style="vm.cellStyle(entity, column)"
+                        ng-bind-html="vm.cellContent(entity, column)">
                     </td>
-                    <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+                    <td md-cell class="tb-action-cell"
                         ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
                                    maxWidth: vm.actionCellDescriptors.length*36+'px',
                                    width: vm.actionCellDescriptors.length*36+'px'}">
diff --git a/ui/src/app/widget/lib/extensions-table-widget.scss b/ui/src/app/widget/lib/extensions-table-widget.scss
index f5f228d..42fcf6d 100644
--- a/ui/src/app/widget/lib/extensions-table-widget.scss
+++ b/ui/src/app/widget/lib/extensions-table-widget.scss
@@ -13,16 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-extension-table {
   md-content {
     background-color: #fff;
   }
 }
+
 md-tabs.hide-tabs-menu {
   md-tabs-wrapper {
     display: none;
   }
+
   md-tabs-content-wrapper {
     top: 0 !important;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss
index c41db43..bf2d3e0 100644
--- a/ui/src/app/widget/lib/rpc/knob.scss
+++ b/ui/src/app/widget/lib/rpc/knob.scss
@@ -13,141 +13,156 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$knob-img: url('./svg/knob.svg');
+$knob-img: url("./svg/knob.svg") !default;
 
-$bars-margin-pct: percentage(0.033);
-$background-margin-pct: percentage(0.05);
-$value-container-margin-pct: percentage(0.35);
-$error-height: percentage(0.05);
-$title-height: percentage(0.066);
-$title-container-margin-pct: percentage(0.2);
-$title-container-margin-bottom-pct: percentage(0.05);
-$minmax-height: percentage(0.04);
-$minmax-container-margin-pct: percentage(0.18);
-$minmax-container-margin-bottom-pct: percentage(0.12);
+$bars-margin-pct: percentage(.033) !default;
+$background-margin-pct: percentage(.05) !default;
+$value-container-margin-pct: percentage(.35) !default;
+$error-height: percentage(.05) !default;
+$title-height: percentage(.066) !default;
+$title-container-margin-pct: percentage(.2) !default;
+$title-container-margin-bottom-pct: percentage(.05) !default;
+$minmax-height: percentage(.04) !default;
+$minmax-container-margin-pct: percentage(.18) !default;
+$minmax-container-margin-bottom-pct: percentage(.12) !default;
 
-$background-color: #e6e7e8;
+$background-color: #e6e7e8 !default;
 
 .tb-knob {
-  width:100%;
-  height:100%;
+  width: 100%;
+  height: 100%;
   background: $background-color;
 
   .knob {
     position: relative;
+
     &[draggable] {
       -moz-user-select: none;
       -webkit-user-select: none;
       user-select: none;
     }
+
     #canvasBar {
-      position:absolute;
-      top:0;
-      left:0;
-      bottom: 0;
+      position: absolute;
+      top: 0;
       right: 0;
+      bottom: 0;
+      left: 0;
       z-index: 2;
     }
+
     .canvas-background {
-      position:absolute;
+      position: absolute;
       top: $background-margin-pct;
-      left: $background-margin-pct;
-      bottom: $background-margin-pct;
       right: $background-margin-pct;
-      border-radius: 50%;
+      bottom: $background-margin-pct;
+      left: $background-margin-pct;
+      z-index: 2;
       background: #3f4346;
-      z-index:2;
+      border-radius: 50%;
     }
+
     .value-container {
-      position:absolute;
+      position: absolute;
       top: $value-container-margin-pct;
-      left: $value-container-margin-pct;
-      bottom: $value-container-margin-pct;
       right: $value-container-margin-pct;
-      z-index:4;
+      bottom: $value-container-margin-pct;
+      left: $value-container-margin-pct;
+      z-index: 4;
+
       .knob-value {
-        color: #fff;
         font-weight: 500;
+        color: #fff;
         white-space: nowrap;
       }
     }
+
     .error-container {
-      position:absolute;
+      position: absolute;
       top: 1%;
-      left: 0;
       right: 0;
-      z-index:4;
+      left: 0;
+      z-index: 4;
       height: $error-height;
+
       .knob-error {
         color: #ff3315;
         white-space: nowrap;
       }
     }
+
     .title-container {
-      position:absolute;
-      left: $title-container-margin-pct;
-      bottom: $title-container-margin-bottom-pct;
+      position: absolute;
       right: $title-container-margin-pct;
-      z-index:4;
+      bottom: $title-container-margin-bottom-pct;
+      left: $title-container-margin-pct;
+      z-index: 4;
       height: $title-height;
+
       .knob-title {
-        color: #757575;
         font-weight: 500;
+        color: #757575;
         white-space: nowrap;
       }
     }
+
     .minmax-container {
-      position:absolute;
-      left: $minmax-container-margin-pct;
-      bottom: $minmax-container-margin-bottom-pct;
+      position: absolute;
       right: $minmax-container-margin-pct;
-      z-index:4;
+      bottom: $minmax-container-margin-bottom-pct;
+      left: $minmax-container-margin-pct;
+      z-index: 4;
       height: $minmax-height;
+
       .minmax-label {
-        color: #757575;
         font-weight: 500;
+        color: #757575;
         white-space: nowrap;
       }
     }
+
     .top-pointer-container {
-      position:absolute;
+      position: absolute;
       top: $bars-margin-pct;
-      left: $bars-margin-pct;
-      bottom: $bars-margin-pct;
       right: $bars-margin-pct;
-      z-index:3;
-      cursor:pointer !important;
+      bottom: $bars-margin-pct;
+      left: $bars-margin-pct;
+      z-index: 3;
+      cursor: pointer !important;
+
       .top-pointer {
-        content:'';
-        width:5%;
-        height:5%;
-        background-color:#b5b5b5;
-        position:absolute;
-        top:50%;
-        left:22%;
-        margin-top:-2.5%;
+        position: absolute;
+        top: 50%;
+        left: 22%;
+        width: 5%;
+        height: 5%;
+        margin-top: -2.5%;
+        cursor: pointer !important;
+        content: "";
+        background-color: #b5b5b5;
         border-radius: 50%;
-        cursor:pointer !important;
         box-shadow: 1px 0 2px #040404;
       }
     }
+
     .top{
-      position:absolute;
+      position: absolute;
       top: $bars-margin-pct;
-      left: $bars-margin-pct;
-      bottom: $bars-margin-pct;
       right: $bars-margin-pct;
-      background:$knob-img no-repeat;
+      bottom: $bars-margin-pct;
+      left: $bars-margin-pct;
+      z-index: 2;
+      cursor: pointer !important;
+      background: $knob-img no-repeat;
       background-size: contain;
-      z-index:2;
-      cursor:pointer !important;
     }
+
     #text-measure {
       position: absolute;
-      visibility: hidden;
-      height: auto;
       width: auto;
+      height: auto;
       white-space: nowrap;
+      visibility: hidden;
     }
   }
 }
diff --git a/ui/src/app/widget/lib/rpc/led-indicator.scss b/ui/src/app/widget/lib/rpc/led-indicator.scss
index 9b68c42..b1da5c6 100644
--- a/ui/src/app/widget/lib/rpc/led-indicator.scss
+++ b/ui/src/app/widget/lib/rpc/led-indicator.scss
@@ -15,61 +15,65 @@
  */
 @import "~compass-sass-mixins/lib/compass";
 
-$error-height: 14px;
+$error-height: 14px !default;
 
-$background-color: #e6e7e8;
+$background-color: #e6e7e8 !default;
 
 .tb-led-indicator {
-  width:100%;
-  height:100%;
+  width: 100%;
+  height: 100%;
   background: $background-color;
 
   .title-container {
     .led-title {
-      color: #757575;
       font-weight: 500;
+      color: #757575;
       white-space: nowrap;
     }
   }
 
   .error-container {
-    position:absolute;
+    position: absolute;
     top: 1%;
-    left: 0;
     right: 0;
-    z-index:4;
+    left: 0;
+    z-index: 4;
     height: $error-height;
+
     .led-error {
       color: #ff3315;
       white-space: nowrap;
     }
   }
+
   #text-measure {
     position: absolute;
-    visibility: hidden;
-    height: auto;
     width: auto;
+    height: auto;
     white-space: nowrap;
+    visibility: hidden;
   }
 
   #led-container {
     padding: 10px;
+
     .led {
-      cursor: pointer;
       position: relative;
+      cursor: pointer;
+      background-image: -owg-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
+      background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
+      background-image: -moz-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
+      background-image: -o-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
+      background-image: radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
       border-radius: 50%;
-      background-image: -owg-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25));
-      background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25));
-      background-image: -moz-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25));
-      background-image: -o-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25));
-      background-image: radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25));
-      transition: background-color 0.5s, box-shadow 0.5s;
+      transition: background-color .5s, box-shadow .5s;
+
       &.disabled {
-        background-image: -owg-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
-        background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
-        background-image: -moz-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
-        background-image: -o-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
-        background-image: radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
+        background-image: -owg-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
+        background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
+        background-image: -moz-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
+        background-image: -o-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
+        background-image: radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
       }
     }
   }
diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss
index 9396abc..2e37f63 100644
--- a/ui/src/app/widget/lib/rpc/round-switch.scss
+++ b/ui/src/app/widget/lib/rpc/round-switch.scss
@@ -15,179 +15,201 @@
  */
 @import "~compass-sass-mixins/lib/compass";
 
-$error-height: 14px;
+$error-height: 14px !default;
 
-$background-color: #e6e7e8;
+$background-color: #e6e7e8 !default;
 
 .tb-round-switch {
-  width:100%;
-  height:100%;
+  width: 100%;
+  height: 100%;
   background: $background-color;
 
   .title-container {
     .switch-title {
-      color: #757575;
       font-weight: 500;
+      color: #757575;
       white-space: nowrap;
     }
   }
 
   .error-container {
-    position:absolute;
+    position: absolute;
     top: 1%;
-    left: 0;
     right: 0;
-    z-index:4;
+    left: 0;
+    z-index: 4;
     height: $error-height;
+
     .switch-error {
       color: #ff3315;
       white-space: nowrap;
     }
   }
+
   #text-measure {
     position: absolute;
-    visibility: hidden;
-    height: auto;
     width: auto;
+    height: auto;
     white-space: nowrap;
+    visibility: hidden;
   }
 
   #switch-container {
     padding: 10px;
+
     .switch {
-      cursor: pointer;
       position: relative;
-      background:#ddd;
+      width: 260px;
+      min-width: 260px;
+      height: 260px;
+      min-height: 260px;
+      padding: 25px;
+      font-family: sans-serif;
+      font-size: 48px;
+
+      color: #424242;
+      cursor: pointer;
+      -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd);
+      background: #ddd;
       background: -owg-linear-gradient(270deg, #bbb, #ddd);
       background: -webkit-linear-gradient(270deg, #bbb, #ddd);
       background: -moz-linear-gradient(270deg, #bbb, #ddd);
       background: -o-linear-gradient(270deg, #bbb, #ddd);
-      -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd);
       background: linear-gradient(180deg, #bbb, #ddd);
-      border-radius:130px;
+      border-radius: 130px;
+
       @include box-sizing(border-box);
-      @include box-shadow(
-              0px 0px 0px 8px rgba(0,0,0,.1)
-              ,0px 0px 3px 1px rgba(0,0,0,.1)
-              ,inset 0 8px  3px -8px rgba(255,255,255,.4));
-      height: 260px;
-      min-height: 260px;
-      padding: 25px;
-      width: 260px;
-      min-width: 260px;
 
-      color: #424242;
-      font-family:sans-serif;
-      font-size:48px;
+      @include box-shadow(
+        0 0 0 8px rgba(0,0,0,.1)
+        ,0 0 3px 1px rgba(0,0,0,.1)
+        ,inset 0 8px  3px -8px rgba(255,255,255,.4));
 
       input {
-        display:none
+        display: none;
       }
 
-      .on,.off {
-        position:absolute;
-        text-align:center;
+      .on,
+      .off {
+        position: absolute;
+        width: 100%;
+        text-align: center;
+
         @include text-shadow(1px 1px 4px #4a4a4a);
-        width:100%;
       }
 
       .on {
-        color:#444;
-        top:10px;
-        @include transition(all 0.1s);
-        font-family:sans-serif
+        top: 10px;
+        font-family: sans-serif;
+        color: #444;
+
+        @include transition(all .1s);
       }
 
       .off {
-        bottom:5px;
-        @include transition(all 0.1s);
-        @include transform(scaleY(0.85));
+        bottom: 5px;
+
+        @include transition(all .1s);
+
+        @include transform(scaleY(.85));
       }
 
       .but {
+        position: relative;
+        display: block;
+        width: 200px;
+        height: 178px;
+        font-size: 48px;
         cursor: pointer;
-        background-color:#d8d8d8;
+        background-color: #d8d8d8;
+        border-bottom-width: 0;
         border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px;
-        border-bottom-width:0px;
+
         @include box-shadow(inset 8px 6px 5px -7px #a2a2a2,
-                inset -8px 6px 5px -7px #a2a2a2,
-                inset 0 -3px 2px -2px rgba(200, 200, 200, 0.5),
-                0 3px 3px -2px #ffffff,
-                inset 0 -230px 60px -200px rgba(255, 255, 255, 0.2),
-                inset 0 220px 40px -200px rgba(0, 0, 0, 0.3));
-        display:block;
-        font-size:48px;
-        height:178px;
-        position:relative;
-        @include transition(all 0.2s);
-        width:200px;
+        inset -8px 6px 5px -7px #a2a2a2,
+        inset 0 -3px 2px -2px rgba(200, 200, 200, .5),
+        0 3px 3px -2px #fff,
+        inset 0 -230px 60px -200px rgba(255, 255, 255, .2),
+        inset 0 220px 40px -200px rgba(0, 0, 0, .3));
+
+        @include transition(all .2s);
       }
 
       .back {
+        width: 210px;
+        height: 210px;
+        padding: 4px 4px;
         cursor: pointer;
         background-color: #888787;
-        background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
-        border-radius:105px;
-        @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, 0.3),
-                -30px 30px 30px -20px rgba(58, 58, 58, 0.3),
-                0 30px 30px 0px rgba(16, 16, 16, 0.3),
-                inset 0 -1px 0 0 #484848);
+        background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
+        border-radius: 105px;
+
+        @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, .3),
+        -30px 30px 30px -20px rgba(58, 58, 58, .3),
+        0 30px 30px 0 rgba(16, 16, 16, .3),
+        inset 0 -1px 0 0 #484848);
+
         @include box-sizing(border-box);
-        height:210px;
-        padding:4px 4px;
-        @include transition(all 0.2s);
-        width:210px;
+
+        @include transition(all .2s);
       }
 
 
-      input:checked + .back .on,input:checked + .back .off{
+      input:checked + .back .on,
+      input:checked + .back .off{
         @include text-shadow(1px 1px 4px #4a4a4a);
       }
+
       input:checked + .back .on{
-        color:#4c4c4c;
-        top:10px;
-        @include transform(scaleY(0.85));
+        top: 10px;
+        color: #4c4c4c;
+
+        @include transform(scaleY(.85));
       }
+
       input:checked + .back .off{
-        color:#444;
-        bottom:5px;
+        bottom: 5px;
+        color: #444;
+
         @include transform(scaleY(1));
       }
+
       input:checked + .back .but{
-        background:#dcdcdc;
-        background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
-        background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
-        background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
-        background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
-        background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        margin-top: 20px;
+        background: #dcdcdc;
+        background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
+        background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
+        background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
+        background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
+        background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
         border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px;
+
         @include box-shadow(inset 8px -4px 5px -7px #a9a9a9,
-                inset -8px -4px 5px -7px #808080,
-                0 -3px 8px -4px rgba(50, 50, 50, 0.4),
-                inset 0 3px 4px -2px #9c9c9c,
-                inset 0 280px 40px -200px rgba(0, 0, 0, 0.2),
-                inset 0 -200px 40px -200px rgba(180, 180, 180, 0.2));
-        margin-top:20px;
+        inset -8px -4px 5px -7px #808080,
+        0 -3px 8px -4px rgba(50, 50, 50, .4),
+        inset 0 3px 4px -2px #9c9c9c,
+        inset 0 280px 40px -200px rgba(0, 0, 0, .2),
+        inset 0 -200px 40px -200px rgba(180, 180, 180, .2));
       }
-      input:checked + .back{
 
-        background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
-
-        @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, 0.1),
-                -30px 30px 30px -20px rgba(111, 111, 111, 0.1),
-                0 30px 30px 0px rgba(0, 0, 0, 0.2),
-                inset 0 1px 2px 0 rgba(167, 167, 167, 0.6));
-        padding:2px 4px;
+      input:checked + .back{
+        padding: 2px 4px;
+
+        background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
+
+        @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, .1),
+        -30px 30px 30px -20px rgba(111, 111, 111, .1),
+        0 30px 30px 0 rgba(0, 0, 0, .2),
+        inset 0 1px 2px 0 rgba(167, 167, 167, .6));
       }
-
     }
   }
 }
diff --git a/ui/src/app/widget/lib/rpc/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss
index dc0b8ee..53893a0 100644
--- a/ui/src/app/widget/lib/rpc/switch.scss
+++ b/ui/src/app/widget/lib/rpc/switch.scss
@@ -13,27 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$thumb-img: url('./svg/thumb.svg');
-$thumb-checked-img: url('./svg/thumb-checked.svg');
-$thumb-bar-img: url('./svg/thumb-bar.svg');
-$thumb-bar-checked-img: url('./svg/thumb-bar-checked.svg');
+$thumb-img: url("./svg/thumb.svg") !default;
+$thumb-checked-img: url("./svg/thumb-checked.svg") !default;
+$thumb-bar-img: url("./svg/thumb-bar.svg") !default;
+$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default;
 
-$background-color: #e6e7e8;
+$background-color: #e6e7e8 !default;
 
-$error-height: 14px;
+$error-height: 14px !default;
 
 .tb-switch {
-  width:100%;
-  height:100%;
+  width: 100%;
+  height: 100%;
   background: $background-color;
 
   .error-container {
-    position:absolute;
+    position: absolute;
     top: 1%;
-    left: 0;
     right: 0;
-    z-index:4;
+    left: 0;
+    z-index: 4;
     height: $error-height;
+
     .switch-error {
       color: #ff3315;
       white-space: nowrap;
@@ -42,62 +43,71 @@ $error-height: 14px;
 
   .onoff-container {
     height: 100%;
-    color: #757575;
     font-weight: 500;
+    color: #757575;
     white-space: nowrap;
+
     .off-label {
       color: #b7b5b5;
     }
+
     .on-label {
       color: #ff7e57;
       text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px;
     }
   }
+
   .title-container {
     .switch-title {
-      color: #757575;
       font-weight: 500;
+      color: #757575;
       white-space: nowrap;
     }
   }
 
   #switch-container {
-     padding-left: 10px;
-     padding-right: 10px;
+    padding-right: 10px;
+    padding-left: 10px;
   }
+
   .switch {
     position: relative;
+
     md-switch {
-      margin: 0;
-      position:absolute;
+      position: absolute;
       top: 0;
-      left: 0;
-      bottom: 0;
       right: 0;
+      bottom: 0;
+      left: 0;
+      margin: 0;
+
       .md-container {
         margin: 0;
       }
+
       .md-bar {
+        top: 0;
         left: 0;
         width: 100%;
-        top: 0;
         height: 100%;
-        border-radius: 0;
-        background:$thumb-bar-img no-repeat;
+        background: $thumb-bar-img no-repeat;
         background-size: contain;
+        border-radius: 0;
       }
+
       .md-thumb-container {
-        left: 0.25%;
-        width: 50%;
         top: 5%;
+        left: .25%;
+        width: 50%;
         height: 90%;
       }
+
       .md-thumb {
         top: 0;
         left: 0;
-        height: 100%;
         width: 100%;
-        background:$thumb-img no-repeat;
+        height: 100%;
+        background: $thumb-img no-repeat;
         background-size: contain;
         border-radius: 0;
         box-shadow: none;
@@ -105,22 +115,23 @@ $error-height: 14px;
 
       &.md-checked {
         .md-bar {
-          background:$thumb-bar-checked-img no-repeat;
+          background: $thumb-bar-checked-img no-repeat;
           background-size: contain;
         }
+
         .md-thumb {
-          background:$thumb-checked-img no-repeat;
+          background: $thumb-checked-img no-repeat;
           background-size: contain;
         }
       }
-
     }
   }
+
   #text-measure {
     position: absolute;
-    visibility: hidden;
-    height: auto;
     width: auto;
+    height: auto;
     white-space: nowrap;
+    visibility: hidden;
   }
 }
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.scss b/ui/src/app/widget/lib/timeseries-table-widget.scss
index cd8f8a9..12fbf61 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.scss
+++ b/ui/src/app/widget/lib/timeseries-table-widget.scss
@@ -13,20 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 tb-timeseries-table-widget {
-    table.md-table thead.md-head>tr.md-row {
-        height: 40px;
-    }
+  table.md-table thead.md-head > tr.md-row {
+    height: 40px;
+  }
 
-    table.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {
-        height: 38px;
-    }
+  table.md-table tbody.md-body > tr.md-row,
+  table.md-table tfoot.md-foot > tr.md-row {
+    height: 38px;
+  }
 
-    .md-table-pagination>* {
-        height: 46px;
-    }
+  .md-table-pagination > * {
+    height: 46px;
+  }
 
-    .tb-data-table md-toolbar {
-        z-index: 10;
-    }
+  .tb-data-table md-toolbar {
+    z-index: 10;
+  }
 }
diff --git a/ui/src/app/widget/widget-editor.scss b/ui/src/app/widget/widget-editor.scss
index 077f68c..cd374e3 100644
--- a/ui/src/app/widget/widget-editor.scss
+++ b/ui/src/app/widget/widget-editor.scss
@@ -15,13 +15,13 @@
  */
 @import "~compass-sass-mixins/lib/compass";
 
-$edit-toolbar-height: 40px;
+$edit-toolbar-height: 40px !default;
 
 .tb-editor {
   .tb-split {
     @include box-sizing(border-box);
-    overflow-y: auto;
     overflow-x: hidden;
+    overflow-y: auto;
   }
 
   .ace_editor {
@@ -29,7 +29,7 @@ $edit-toolbar-height: 40px;
   }
 
   .tb-content {
-    border: 1px solid #C0C0C0;
+    border: 1px solid #c0c0c0;
   }
 
   .gutter {
@@ -41,21 +41,23 @@ $edit-toolbar-height: 40px;
 
   .gutter.gutter-horizontal {
     cursor: col-resize;
-    background-image: url('../../../node_modules/split.js/grips/vertical.png');
+    background-image: url("../../../node_modules/split.js/grips/vertical.png");
   }
 
   .gutter.gutter-vertical {
     cursor: row-resize;
-    background-image: url('../../../node_modules/split.js/grips/horizontal.png');
+    background-image: url("../../../node_modules/split.js/grips/horizontal.png");
   }
 
-  .tb-split.tb-split-horizontal, .gutter.gutter-horizontal {
-    height: 100%;
+  .tb-split.tb-split-horizontal,
+  .gutter.gutter-horizontal {
     float: left;
+    height: 100%;
   }
 
   .tb-split.tb-split-vertical {
     display: flex;
+
     .tb-split.tb-content {
       height: 100%;
     }
@@ -64,7 +66,6 @@ $edit-toolbar-height: 40px;
 
 .tb-split-vertical {
   md-tabs {
-
     md-tabs-content-wrapper {
       height: calc(100% - 49px);
 
@@ -74,45 +75,44 @@ $edit-toolbar-height: 40px;
         & > div {
           height: 100%;
         }
-
       }
-
     }
-
   }
 }
 
 div.tb-editor-area-title-panel {
   position: absolute;
-  font-size: 0.800rem;
-  font-weight: 500;
   top: 5px;
   right: 20px;
   z-index: 5;
+  font-size: .8rem;
+  font-weight: 500;
+
   label {
-    color: #00acc1;
-    background: rgba(220, 220, 220, 0.35);
-    border-radius: 5px;
     padding: 4px;
+    color: #00acc1;
     text-transform: uppercase;
+    background: rgba(220, 220, 220, .35);
+    border-radius: 5px;
   }
+
   .md-button {
-    color: #7B7B7B;
     min-width: 32px;
     min-height: 15px;
-    line-height: 15px;
-    font-size: 0.800rem;
-    margin: 0;
     padding: 4px;
-    background: rgba(220, 220, 220, 0.35);
+    margin: 0;
+    font-size: .8rem;
+    line-height: 15px;
+    color: #7b7b7b;
+    background: rgba(220, 220, 220, .35);
   }
 }
 
 .tb-resize-container {
-  overflow-y: auto;
-  height: 100%;
-  width: 100%;
   position: relative;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
 
   .ace_editor {
     height: 100%;
@@ -127,24 +127,28 @@ md-toolbar.tb-edit-toolbar {
   .md-toolbar-tools {
     min-height: $edit-toolbar-height !important;
     max-height: $edit-toolbar-height !important;
+
     .md-button {
       min-width: 65px;
       min-height: 30px;
-      line-height: 30px;
       font-size: 12px;
+      line-height: 30px;
+
       md-icon {
         font-size: 20px;
       }
+
       span {
         padding-right: 6px;
       }
     }
+
     md-input-container {
       input {
-        font-size: 1.200rem;
-        font-weight: 400;
-        letter-spacing: 0.005em;
         height: 28px;
+        font-size: 1.2rem;
+        font-weight: 400;
+        letter-spacing: .005em;
       }
     }
   }
diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss
index 719b6f9..4ecfc34 100644
--- a/ui/src/scss/animations.scss
+++ b/ui/src/scss/animations.scss
@@ -18,6 +18,7 @@
 @include keyframes(tbMoveFromTopFade) {
   from {
     opacity: 0;
+
     @include transform(translate(0, -100%));
   }
 }
@@ -25,6 +26,7 @@
 @include keyframes(tbMoveToTopFade) {
   to {
     opacity: 0;
+
     @include transform(translate(0, -100%));
   }
 }
@@ -32,6 +34,7 @@
 @include keyframes(tbMoveFromBottomFade) {
   from {
     opacity: 0;
+
     @include transform(translate(0, 100%));
   }
 }
@@ -39,6 +42,7 @@
 @include keyframes(tbMoveToBottomFade) {
   to {
     opacity: 0;
+
     @include transform(translate(0, 150%));
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
index 5b6eab4..b462c66 100644
--- a/ui/src/scss/constants.scss
+++ b/ui/src/scss/constants.scss
@@ -17,18 +17,18 @@
 
 // Colors
 
-$gray: #eee;
+$gray: #eee !default;
 
-$primary-palette-color: 'indigo';
-$default: '500';
-$hue-1: '300';
-$hue-2: '800';
-$hue-3: 'a100';
+$primary-palette-color: "indigo" !default;
+$default: "500" !default;
+$hue-1: "300" !default;
+$hue-2: "800" !default;
+$hue-3: "a100" !default;
 
-$primary-default: #305680; //material-color($primary-palette-color, $default);
-$primary-hue-1: material-color($primary-palette-color, $hue-1);
-$primary-hue-2: material-color($primary-palette-color, $hue-2);
-$primary-hue-3: rgb(207, 216, 220);
+$primary-default: #305680 !default; //material-color($primary-palette-color, $default);
+$primary-hue-1: material-color($primary-palette-color, $hue-1) !default;
+$primary-hue-2: material-color($primary-palette-color, $hue-2) !default;
+$primary-hue-3: rgb(207, 216, 220) !default;
 
 // Layout
 
diff --git a/ui/src/scss/fonts.scss b/ui/src/scss/fonts.scss
index 95089eb..6348d35 100644
--- a/ui/src/scss/fonts.scss
+++ b/ui/src/scss/fonts.scss
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 @font-face {
-  font-family: 'Segment7Standard';
-  src: url('data:font/opentype;charset=utf-8;base64,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AAEAAAAIAAAAAAAEAAAAAAAAAAEAAAAAzD2izwAAAADPr89TAAAAAM+4fiECSQAAAkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpwBCAC8ALwAvAFwBpwAvAC8ALwBcAC8AXAIBAC8ALwGWAC8ALwBCAC4ALgBYAC8ALwBcAacALwAvAC8ALwAvAC8ALwAvAC8ALwAvAC8ALwGWAC8ALwAvAC8ALwAvAC8AQgAvAC4ALwAvAC8ALwAvAC8ALwAvAEIALwBCAFwBpwAvAC8ALwAvAC8ALwAvAC8BlgAvAC8ALwAvAC8ALwAvAEIALwAuAC8ALwAvAC8ALwAvAC8AAAGWAAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8ALwAAAAAAAAAAAAAAAAAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') format('opentype');
-  font-weight: normal;
+  font-family: "Segment7Standard";
   font-style: italic;
-}
\ No newline at end of file
+  font-weight: 400;
+  src: url("data:font/opentype;charset=utf-8;base64,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AAEAAAAIAAAAAAAEAAAAAAAAAAEAAAAAzD2izwAAAADPr89TAAAAAM+4fiECSQAAAkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpwBCAC8ALwAvAFwBpwAvAC8ALwBcAC8AXAIBAC8ALwGWAC8ALwBCAC4ALgBYAC8ALwBcAacALwAvAC8ALwAvAC8ALwAvAC8ALwAvAC8ALwGWAC8ALwAvAC8ALwAvAC8AQgAvAC4ALwAvAC8ALwAvAC8ALwAvAEIALwBCAFwBpwAvAC8ALwAvAC8ALwAvAC8BlgAvAC8ALwAvAC8ALwAvAEIALwAuAC8ALwAvAC8ALwAvAC8AAAGWAAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8ALwAAAAAAAAAAAAAAAAAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("opentype");
+}

ui/src/scss/main.scss 309(+181 -128)

diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 2852a7b..27c2d3b 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -14,77 +14,92 @@
  * limitations under the License.
  */
 @import "~compass-sass-mixins/lib/compass";
+
 @import "constants";
+
 @import "animations";
+
 @import "mixins";
+
 @import "fonts";
 
 /***************
  * TYPE DEFAULTS
  ***************/
 
-button, html, input, select, textarea {
-  font-family: Roboto, 'Helvetica Neue', sans-serif;
+button,
+html,
+input,
+select,
+textarea {
+  font-family: Roboto, "Helvetica Neue", sans-serif;
 }
 
 .mdi-set {
   line-height: 1;
-  letter-spacing: normal;
   text-transform: none;
-  white-space: nowrap;
+  letter-spacing: normal;
   word-wrap: normal;
+  white-space: nowrap;
   direction: ltr;
-  -webkit-font-feature-settings: 'liga';
+  -webkit-font-feature-settings: "liga";
 }
 
 a {
-  color: #106CC8;
-  text-decoration: none;
   font-weight: 400;
-  border-bottom: 1px solid rgba(64, 84, 178, 0.25);
-  @include transition(border-bottom 0.35s);
+  color: #106cc8;
+  text-decoration: none;
+  border-bottom: 1px solid rgba(64, 84, 178, .25);
+
+  @include transition(border-bottom .35s);
 }
 
-a:hover, a:focus {
-  border-bottom: 1px solid #4054B2;
+a:hover,
+a:focus {
+  border-bottom: 1px solid #4054b2;
 }
 
-h1, h2, h3, h4, h5, h6 {
-  margin-bottom: 1rem;
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
   margin-top: 1rem;
+  margin-bottom: 1rem;
 }
 
 h1 {
-  font-size: 3.400rem;
+  font-size: 3.4rem;
   font-weight: 400;
   line-height: 4rem;
 }
 
 h2 {
-  font-size: 2.400rem;
+  font-size: 2.4rem;
   font-weight: 400;
   line-height: 3.2rem;
 }
 
 h3 {
-  font-size: 2.000rem;
+  font-size: 2rem;
   font-weight: 500;
-  letter-spacing: 0.005em;
+  letter-spacing: .005em;
 }
 
 h4 {
-  font-size: 1.600rem;
+  font-size: 1.6rem;
   font-weight: 400;
-  letter-spacing: 0.010em;
   line-height: 2.4rem;
+  letter-spacing: .01em;
 }
 
 p {
+  margin: .8em 0 1.6em;
   font-size: 1.6rem;
   font-weight: 400;
-  letter-spacing: 0.010em;
   line-height: 1.6em;
-  margin: 0.8em 0 1.6em;
+  letter-spacing: .01em;
 }
 
 strong {
@@ -92,16 +107,16 @@ strong {
 }
 
 blockquote {
-  border-left: 3px solid rgba(0, 0, 0, 0.12);
-  font-style: italic;
-  margin-left: 0;
   padding-left: 16px;
+  margin-left: 0;
+  font-style: italic;
+  border-left: 3px solid rgba(0, 0, 0, .12);
 }
 
 fieldset {
-  border: none;
   padding: 0;
   margin: 0;
+  border: none;
 }
 
 /*********************************
@@ -119,26 +134,28 @@ form {
 }
 
 md-bottom-sheet .md-subheader {
-  font-family: Roboto, 'Helvetica Neue', sans-serif;
+  font-family: Roboto, "Helvetica Neue", sans-serif;
 }
 
 .md-chips {
-  font-family: Roboto, 'Helvetica Neue', sans-serif;
+  font-family: Roboto, "Helvetica Neue", sans-serif;
 }
 
-md-content.md-default-theme, md-content {
+md-content.md-default-theme,
+md-content {
   background-color: $gray;
 }
 
 md-card {
   background-color: #fff;
+
   h2:first-of-type {
     margin-top: 0;
   }
 }
 
 .md-button:not([disabled]).md-icon-button:hover {
-  background-color: rgba(158, 158, 158, 0.2);
+  background-color: rgba(158, 158, 158, .2);
 }
 
 md-toolbar:not(.md-hue-1),
@@ -147,11 +164,12 @@ md-toolbar:not(.md-hue-1),
 }
 
 md-toolbar md-input-container .md-errors-spacer {
-  min-height: 0px;
+  min-height: 0;
 }
 
 md-toolbar {
-  md-select.md-default-theme:not([disabled]):focus .md-select-value, md-select:not([disabled]):focus .md-select-value {
+  md-select.md-default-theme:not([disabled]):focus .md-select-value,
+  md-select:not([disabled]):focus .md-select-value {
     color: #fff;
   }
 }
@@ -159,11 +177,10 @@ md-toolbar {
 md-menu-item {
   overflow: hidden;
   fill: #737373;
-}
 
-md-menu-item {
   .md-button {
     display: block;
+
     .tb-alt-text {
       float: right;
     }
@@ -190,13 +207,14 @@ md-sidenav {
   overflow-y: auto;
 }
 
-.md-radio-interactive input, button {
+.md-radio-interactive input,
+button {
   pointer-events: all;
 }
 
 .md-color-picker-input-container {
   md-input-container {
-    margin-bottom: 0px;
+    margin-bottom: 0;
   }
 }
 
@@ -204,32 +222,36 @@ md-sidenav {
  * THINGSBOARD SPECIFIC
  ***********************/
 
-$swift-ease-out-duration: 0.4s !default;
-$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
+$swift-ease-out-duration: .4s !default;
+$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
 
 $input-label-float-offset: 6px !default;
-$input-label-float-scale: 0.75 !default;
+$input-label-float-scale: .75 !default;
 
 label {
   &.tb-title {
-    pointer-events: none;
-    color: #666;
+    padding-bottom: 15px;
     font-size: 13px;
     font-weight: 400;
-    padding-bottom: 15px;
+    color: #666;
+    pointer-events: none;
+
     &.no-padding {
-      padding-bottom: 0px;
+      padding-bottom: 0;
     }
-    &.tb-required:after {
-      content: ' *';
+
+    &.tb-required::after {
       font-size: 13px;
+      color: rgba(0, 0, 0, .54);
       vertical-align: top;
-      color: rgba(0,0,0,0.54);
+      content: " *";
     }
+
     &.tb-error {
-      color: rgb(221,44,0);
-      &.tb-required:after {
-        color: rgb(221,44,0);
+      color: rgb(221, 44, 0);
+
+      &.tb-required::after {
+        color: rgb(221, 44, 0);
       }
     }
   }
@@ -246,96 +268,107 @@ label {
 }
 
 .tb-readonly-label {
-  color: rgba(0,0,0,0.54);
+  color: rgba(0, 0, 0, .54);
 }
 
 .tb-disabled-label {
-  color: rgba(0,0,0,0.44);
+  color: rgba(0, 0, 0, .44);
 }
 
+/* stylelint-disable-next-line no-duplicate-selectors */
 label {
   &.tb-small {
-    pointer-events: none;
-    color: rgba(0,0,0,0.54);
     font-size: 12px;
+    color: rgba(0, 0, 0, .54);
+    pointer-events: none;
   }
 }
 
 div {
   &.tb-small {
-    color: rgba(0,0,0,0.54);
     font-size: 14px;
+    color: rgba(0, 0, 0, .54);
   }
 }
 
 .tb-hint {
+  padding-bottom: 15px;
   font-size: 12px;
   line-height: 14px;
-  transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2);
-  color: grey;
-  padding-bottom: 15px;
-  &.ng-hide, &.ng-enter, &.ng-leave.ng-leave-active {
+  color: #808080;
+  transition: all .3s cubic-bezier(.55, 0, .55, .2);
+
+  &.ng-hide,
+  &.ng-enter,
+  &.ng-leave.ng-leave-active {
     bottom: 26px;
     opacity: 0;
   }
-  &.ng-leave, &.ng-enter.ng-enter-active {
+
+  &.ng-leave,
+  &.ng-enter.ng-enter-active {
     bottom: 7px;
     opacity: 1;
   }
 }
 
 .md-caption {
-  &.tb-required:after {
-    content: ' *';
+  &.tb-required::after {
     font-size: 10px;
+    color: rgba(0, 0, 0, .54);
     vertical-align: top;
-    color: rgba(0,0,0,0.54);
+    content: " *";
   }
 }
 
 pre.tb-highlight {
-  background-color: #f7f7f7;
   display: block;
-  margin: 20px 0;
   padding: 15px;
+  margin: 20px 0;
   overflow-x: auto;
+  background-color: #f7f7f7;
+
   code {
+    box-sizing: border-box;
+    display: inline-block;
     padding: 0;
-    color: #303030;
     font-family: monospace;
-    display: inline-block;
-    box-sizing: border-box;
-    vertical-align: bottom;
     font-size: 16px;
-    font-weight: bold;
+    font-weight: 700;
+    color: #303030;
+    vertical-align: bottom;
   }
 }
 
 .tb-notice {
-  background-color: #f7f7f7;
   padding: 15px;
-  border: 1px solid #ccc;
   font-size: 16px;
+  background-color: #f7f7f7;
+  border: 1px solid #ccc;
 }
 
 .tb-data-table {
   md-toolbar {
     z-index: 0;
   }
+
   md-toolbar.md-table-toolbar.md-default-theme:not(.md-menu-toolbar).md-default .md-button[disabled],
   md-toolbar.md-table-toolbar:not(.md-menu-toolbar).md-default .md-button[disabled] {
-    color: rgba(0,0,0,0.38);
+    color: rgba(0, 0, 0, .38);
   }
+
   md-toolbar.md-default-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon,
   md-toolbar:not(.md-menu-toolbar) .md-button[disabled] md-icon {
-    color: rgba(0,0,0,.28);
+    color: rgba(0, 0, 0, .28);
   }
+
   span.no-data-found {
     position: relative;
+    display: flex;
     height: calc(100% - 57px);
     text-transform: uppercase;
-    display: flex;
   }
+
   table.md-table {
     &.md-row-select td.md-cell,
     &.md-row-select th.md-column {
@@ -366,49 +399,55 @@ pre.tb-highlight {
 
     td.md-cell,
     th.md-column {
-
       &:last-child {
         padding: 0 12px 0 0;
       }
-
     }
   }
 
-  table.md-table, table.md-table.md-row-select {
+  table.md-table,
+  table.md-table.md-row-select {
     tbody {
       &.md-body {
         tr {
           &.md-row:not([disabled]) {
             outline: none;
+
             &:hover {
-              background-color: rgba(221, 221, 221, 0.3) !important;
+              background-color: rgba(221, 221, 221, .3) !important;
             }
+
             &.md-selected {
-              background-color: rgba(221, 221, 221, 0.5) !important;
+              background-color: rgba(221, 221, 221, .5) !important;
             }
-            &.tb-current, &.tb-current:hover{
-              background-color: rgba(221, 221, 221, 0.65) !important;
+
+            &.tb-current,
+            &.tb-current:hover{
+              background-color: rgba(221, 221, 221, .65) !important;
             }
           }
         }
       }
+
       tr {
         td {
           &.tb-action-cell {
+            width: 72px;
+            min-width: 72px;
+            max-width: 72px;
             overflow: hidden;
             text-overflow: ellipsis;
             white-space: nowrap;
-            min-width: 72px;
-            max-width: 72px;
-            width: 72px;
+
             .md-button {
               &.md-icon-button {
-                margin: 0;
-                padding: 6px;
                 width: 36px;
                 height: 36px;
+                padding: 6px;
+                margin: 0;
               }
             }
+
             .tb-spacer {
               padding-left: 38px;
             }
@@ -420,34 +459,40 @@ pre.tb-highlight {
 }
 
 .tb-severity {
-  font-weight: bold;
+  font-weight: 700;
+
   &.tb-critical {
-    color: red !important;
+    color: #f00 !important;
   }
+
   &.tb-major {
-    color: orange !important;
+    color: #ffa500 !important;
   }
+
   &.tb-minor {
     color: #ffca3d !important;
   }
+
   &.tb-warning {
     color: #abab00 !important;
   }
+
   &.tb-indeterminate {
-    color: green !important;
+    color: #008000 !important;
   }
 }
 
 .tb-card-description {
-  color: rgba(0,0,0,0.54);
   font-size: 13px;
+  color: rgba(0, 0, 0, .54);
+
   @include line-clamp(2, 1.1);
 }
 
 /***********************
  * Flow
  ***********************/
-$previewSize: 100px;
+$previewSize: 100px !default;
 
 .file-input {
   display: none;
@@ -455,17 +500,19 @@ $previewSize: 100px;
 
 .tb-flow-drop {
   position: relative;
-  border: dashed 2px;
   height: $previewSize;
   overflow: hidden;
+  border: dashed 2px;
+
   label {
-    width: 100%;
-    height: 100%;
     display: flex;
     flex-direction: column;
     justify-content: center;
+    width: 100%;
+    height: 100%;
     font-size: 16px;
     text-align: center;
+
     @media (min-width: $layout-breakpoint-sm) {
       font-size: 24px;
     }
@@ -474,8 +521,8 @@ $previewSize: 100px;
 
 .tb-container {
   position: relative;
-  margin-top: 32px;
   padding: 10px 0;
+  margin-top: 32px;
 }
 
 /***********************
@@ -483,12 +530,12 @@ $previewSize: 100px;
  ***********************/
 
 .tb-prompt {
-  color: rgba(0,0,0,0.38);
-  text-transform: uppercase;
   display: flex;
   font-size: 18px;
   font-weight: 400;
   line-height: 18px;
+  color: rgba(0, 0, 0, .38);
+  text-transform: uppercase;
 }
 
 /***********************
@@ -500,35 +547,37 @@ $previewSize: 100px;
 }
 
 .tb-error-message {
+  padding: 10px 0 0 10px;
+  margin-top: -6px;
+  overflow: hidden;
   font-size: 12px;
   line-height: 14px;
-  overflow: hidden;
-  padding: 10px 0px 0px 10px;
-  color: rgb(221,44,0);
-  margin-top: -6px;
+  color: rgb(221, 44, 0);
 }
 
 .tb-error-message.ng-animate {
-  @include transition(all .3s cubic-bezier(.55,0,.55,.2));
+  @include transition(all .3s cubic-bezier(.55, 0, .55, .2));
 }
 
-.tb-error-message.ng-enter-prepare, .tb-error-message.ng-enter {
-  opacity:0;
+.tb-error-message.ng-enter-prepare,
+.tb-error-message.ng-enter {
   margin-top: -24px;
+  opacity: 0;
 }
 
 .tb-error-message.ng-enter.ng-enter-active {
-  opacity:1;
   margin-top: -6px;
+  opacity: 1;
 }
 
 .tb-error-message.ng-leave {
-  opacity:1;
   margin-top: -6px;
+  opacity: 1;
 }
+
 .tb-error-message.ng-leave.ng-leave-active {
-  opacity:0;
   margin-top: -24px;
+  opacity: 0;
 }
 
 /***********************
@@ -545,39 +594,41 @@ md-tabs.tb-headless {
 
 .md-button.tb-card-button {
   width: 100%;
-  height: 100%;
   max-width: 240px;
+  height: 100%;
+
   span {
+    height: 18px;
+    min-height: 18px;
+    max-height: 18px;
     padding: 0 0 20px 0;
+    margin: auto;
     font-size: 18px;
     font-weight: 400;
-    white-space: normal;
     line-height: 18px;
-    max-height: 18px;
-    min-height: 18px;
-    height: 18px;
-    margin: auto;
+    white-space: normal;
   }
 }
 
 .md-button.tb-layout-button {
   width: 100%;
-  height: 100%;
   max-width: 240px;
+  height: 100%;
+
   span {
     padding: 40px;
     font-size: 18px;
     font-weight: 400;
-    white-space: normal;
     line-height: 18px;
+    white-space: normal;
   }
 }
 
 .md-button.tb-add-new-widget {
+  padding-right: 12px;
+  font-size: 24px;
   border-style: dashed;
   border-width: 2px;
-  font-size: 24px;
-  padding-right: 12px;
 }
 
 /***********************
@@ -585,11 +636,12 @@ md-tabs.tb-headless {
  ***********************/
 
 section.tb-header-buttons {
-  pointer-events: none;
   position: absolute;
-  right: 0px;
   top: 86px;
+  right: 0;
   z-index: 3;
+  pointer-events: none;
+
   @media (min-width: $layout-breakpoint-sm) {
     top: 86px;
   }
@@ -633,6 +685,7 @@ section.tb-footer-buttons {
 
 ._md-toast-open-bottom .tb-footer-buttons {
   @include transition(all .4s cubic-bezier(.25, .8, .25, 1));
+
   @include transform(translate3d(0, -42px, 0));
 }
 
@@ -641,27 +694,27 @@ section.tb-footer-buttons {
  ***********************/
 
 .md-icon-button.tb-md-32 {
-  vertical-align: middle;
   width: 32px;
-  height: 32px;
   min-width: 32px;
+  height: 32px;
   min-height: 32px;
-  margin: 0px !important;
-  padding: 0px !important;
+  padding: 0 !important;
+  margin: 0 !important;
+  vertical-align: middle;
 }
 
 .material-icons.tb-md-20 {
-  font-size: 20px;
   width: 20px;
-  height: 20px;
   min-width: 20px;
+  height: 20px;
   min-height: 20px;
+  font-size: 20px;
 }
 
 .material-icons.tb-md-96 {
-  font-size: 96px;
   width: 96px;
   height: 96px;
+  font-size: 96px;
 }
 
 /***********************
@@ -671,17 +724,17 @@ section.tb-footer-buttons {
 .tb-absolute-fill {
   position: absolute;
   top: 0;
-  left: 0;
   right: 0;
   bottom: 0;
+  left: 0;
 }
 
 .tb-progress-cover {
   position: absolute;
   top: 0;
-  left: 0;
   right: 0;
   bottom: 0;
+  left: 0;
   z-index: 6;
   opacity: 1;
 }
diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss
index cb66171..a754538 100644
--- a/ui/src/scss/mixins.scss
+++ b/ui/src/scss/mixins.scss
@@ -17,43 +17,49 @@
 
 @mixin input-placeholder {
   // replaces compass/css/user-interface/input-placeholder()
+
   &::-webkit-input-placeholder  {
     @content;
   }
+
   &:-moz-placeholder {
     @content;
     opacity: 1;
   }
+
   &::-moz-placeholder {
     @content;
     opacity: 1;
   }
+
   &:-ms-input-placeholder {
     @content;
   }
 }
 
 @mixin line-clamp($numLines: 1, $lineHeight: 1.412) {
-  overflow: hidden;
   position: relative;
+  max-height: ($numLines * $lineHeight * 1em);
+  padding-right: 2em;
+  margin-right: -1em;
+  overflow: hidden;
   line-height: $lineHeight;
   text-align: justify;
-  margin-right: -1em;
-  padding-right: 2em;
-  max-height: ($numLines*$lineHeight)+em;
-  &:before {
-    content: '...';
+
+  &::before {
     position: absolute;
     right: 1em;
     bottom: 0;
+    content: "...";
   }
-  &:after {
-    content: '';
+
+  &::after {
     position: absolute;
     right: 1em;
     width: 1em;
     height: 1em;
-    margin-top: 0.2em;
-    background: white;
+    margin-top: .2em;
+    content: "";
+    background: #fff;
   }
 }
diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js
index 242ad9d..cfff811 100644
--- a/ui/webpack.config.dev.js
+++ b/ui/webpack.config.dev.js
@@ -18,6 +18,8 @@
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 const ExtractTextPlugin = require('extract-text-webpack-plugin');
 const CopyWebpackPlugin = require('copy-webpack-plugin');
+const StyleLintPlugin = require('stylelint-webpack-plugin')
+
 const webpack = require('webpack');
 const path = require('path');
 const dirTree = require('directory-tree');
@@ -76,6 +78,7 @@ module.exports = {
             title: 'ThingsBoard',
             inject: 'body',
         }),
+        new StyleLintPlugin(),
         new webpack.optimize.OccurrenceOrderPlugin(),
         new webpack.NoErrorsPlugin(),
         new ExtractTextPlugin('style.[contentHash].css', {