thingsboard-aplcache

Merged with develop

5/17/2018 1:48:19 AM

Changes

docker/k8s/tb.yaml 12(+12 -0)

ui/src/app/api/plugin.service.js 218(+0 -218)

ui/src/app/api/rule.service.js 186(+0 -186)

ui/src/app/component/component.directive.js 75(+0 -75)

ui/src/app/component/component.tpl.html 58(+0 -58)

ui/src/app/component/component-dialog.controller.js 105(+0 -105)

ui/src/app/component/component-dialog.service.js 60(+0 -60)

ui/src/app/component/component-dialog.tpl.html 79(+0 -79)

ui/src/app/component/index.js 28(+0 -28)

ui/src/app/components/plugin-select.directive.js 115(+0 -115)

ui/src/app/components/plugin-select.scss 37(+0 -37)

ui/src/app/components/plugin-select.tpl.html 44(+0 -44)

ui/src/app/plugin/add-plugin.tpl.html 48(+0 -48)

ui/src/app/plugin/index.js 36(+0 -36)

ui/src/app/plugin/plugin.controller.js 218(+0 -218)

ui/src/app/plugin/plugin.directive.js 94(+0 -94)

ui/src/app/plugin/plugin.routes.js 46(+0 -46)

ui/src/app/plugin/plugin-card.tpl.html 19(+0 -19)

ui/src/app/plugin/plugin-fieldset.tpl.html 90(+0 -90)

ui/src/app/plugin/plugins.tpl.html 77(+0 -77)

ui/src/app/rule/add-rule.tpl.html 48(+0 -48)

ui/src/app/rule/index.js 40(+0 -40)

ui/src/app/rule/rule.controller.js 210(+0 -210)

ui/src/app/rule/rule.directive.js 191(+0 -191)

ui/src/app/rule/rule.routes.js 46(+0 -46)

ui/src/app/rule/rule.scss 55(+0 -55)

ui/src/app/rule/rule-card.tpl.html 19(+0 -19)

ui/src/app/rule/rule-fieldset.tpl.html 219(+0 -219)

ui/src/app/rule/rules.tpl.html 77(+0 -77)

Details

diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index a17e29b..b4f9102 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -64,6 +64,7 @@ import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
 import org.thingsboard.server.service.executors.ExternalCallExecutorService;
 import org.thingsboard.server.service.mail.MailExecutorService;
+import org.thingsboard.server.service.queue.MsgQueueService;
 import org.thingsboard.server.service.rpc.DeviceRpcService;
 import org.thingsboard.server.service.script.JsExecutorService;
 import org.thingsboard.server.service.state.DeviceStateService;
@@ -201,7 +202,7 @@ public class ActorSystemContext {
 
     @Autowired
     @Getter
-    private MsgQueue msgQueue;
+    private MsgQueueService msgQueueService;
 
     @Autowired
     @Getter
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 f16bdc9..f8bc94f 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
@@ -85,21 +85,6 @@ class DefaultTbContext implements TbContext {
     }
 
     @Override
-    public void tellOthers(TbMsg msg) {
-        throw new RuntimeException("Not Implemented!");
-    }
-
-    @Override
-    public void tellSibling(TbMsg msg, ServerAddress address) {
-        throw new RuntimeException("Not Implemented!");
-    }
-
-    @Override
-    public void ack(TbMsg msg) {
-
-    }
-
-    @Override
     public void tellError(TbMsg msg, Throwable th) {
         if (nodeCtx.getSelf().isDebugMode()) {
             mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, "", th);
@@ -119,7 +104,7 @@ class DefaultTbContext implements TbContext {
 
     @Override
     public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
-        return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), mainCtx.getQueuePartitionId());
+        return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), mainCtx.getQueuePartitionId());
     }
 
     @Override
@@ -134,6 +119,7 @@ class DefaultTbContext implements TbContext {
 
     @Override
     public void tellNext(TbMsg msg, Set<String> relationTypes) {
+        //TODO: fix this to send set of relations instead of loop.
         relationTypes.forEach(type -> tellNext(msg, type));
     }
 
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index 7bf3ff7..38a2ab7 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -95,12 +95,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
 
     private void reprocess(List<RuleNode> ruleNodeList) {
         for (RuleNode ruleNode : ruleNodeList) {
-            for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), systemContext.getQueuePartitionId())) {
+            for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), systemContext.getQueuePartitionId())) {
                 pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, "");
             }
         }
         if (firstNode != null) {
-            for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), systemContext.getQueuePartitionId())) {
+            for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), systemContext.getQueuePartitionId())) {
                 pushMsgToNode(firstNode, tbMsg, "");
             }
         }
@@ -215,7 +215,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         int relationsCount = relations.size();
         EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
         if (relationsCount == 0) {
-            queue.ack(msg, ackId.getId(), msg.getClusterPartition());
+            queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
         } else if (relationsCount == 1) {
             for (RuleNodeRelation relation : relations) {
                 pushToTarget(msg, relation.getOut(), relation.getType());
@@ -233,7 +233,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
                 }
             }
             //TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
-            queue.ack(msg, ackId.getId(), msg.getClusterPartition());
+            queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
         }
     }
 
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
index 136d4f6..771b85b 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.dao.queue.MsgQueue;
+import org.thingsboard.server.service.queue.MsgQueueService;
 
 import javax.annotation.Nullable;
 import java.util.function.Consumer;
@@ -35,14 +36,14 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
 
     protected final TenantId tenantId;
     protected final T entityId;
-    protected final MsgQueue queue;
+    protected final MsgQueueService queue;
     protected ComponentLifecycleState state;
 
     protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
         super(systemContext, logger);
         this.tenantId = tenantId;
         this.entityId = id;
-        this.queue = systemContext.getMsgQueue();
+        this.queue = systemContext.getMsgQueueService();
     }
 
     public abstract void start(ActorContext context) throws Exception;
@@ -88,7 +89,7 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
 
     protected void putToQueue(final TbMsg tbMsg, final Consumer<TbMsg> onSuccess) {
         EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId();
-        Futures.addCallback(queue.put(tbMsg, entityId.getId(), tbMsg.getClusterPartition()), new FutureCallback<Void>() {
+        Futures.addCallback(queue.put(this.tenantId, tbMsg, entityId.getId(), tbMsg.getClusterPartition()), new FutureCallback<Void>() {
             @Override
             public void onSuccess(@Nullable Void result) {
                 onSuccess.accept(tbMsg);
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
index d4a1f34..295bfb9 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
@@ -67,7 +67,7 @@ public abstract class EntityActorsManager<T extends EntityId, A extends UntypedA
         }
     }
 
-    protected void visit(M entity, ActorRef actorRef) {}
+    public void visit(M entity, ActorRef actorRef) {}
 
     public ActorRef getOrCreateActor(ActorContext context, T entityId) {
         return actors.computeIfAbsent(entityId, eId ->
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
index ff0c52e..11ed5a3 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
@@ -49,7 +49,7 @@ public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, 
     }
 
     @Override
-    protected void visit(RuleChain entity, ActorRef actorRef) {
+    public void visit(RuleChain entity, ActorRef actorRef) {
         if (entity.isRoot()) {
             rootChain = entity;
             rootChainActor = actorRef;
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
index a8bb069..87f1b16 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
@@ -20,10 +20,14 @@ import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.plugin.PluginManager;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.common.data.plugin.PluginMetaData;
 import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.dao.plugin.BasePluginService;
 
+import java.util.Collections;
+
 public class SystemRuleChainManager extends RuleChainManager {
 
     public SystemRuleChainManager(ActorSystemContext systemContext) {
@@ -32,7 +36,7 @@ public class SystemRuleChainManager extends RuleChainManager {
 
     @Override
     protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
-        return service::findSystemRuleChains;
+        return link -> new TextPageData<>(Collections.emptyList(), link);
     }
 
     @Override
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 10e247b..a3094de 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -30,8 +30,11 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
 import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
 import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
+import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
 import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
@@ -130,6 +133,11 @@ public class TenantActor extends RuleChainManagerActor {
     private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
         ActorRef target = getEntityActorRef(msg.getEntityId());
         if (target != null) {
+            if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
+                RuleChain ruleChain = systemContext.getRuleChainService().
+                        findRuleChainById(new RuleChainId(msg.getEntityId().getId()));
+                ruleChainManager.visit(ruleChain, target);
+            }
             target.tell(msg, ActorRef.noSender());
         } else {
             logger.debug("Invalid component lifecycle msg: {}", msg);
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 4441fb8..3fd1698 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -558,15 +558,10 @@ public abstract class BaseController {
         SecurityUser authUser = getCurrentUser();
         TenantId tenantId = ruleChain.getTenantId();
         validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
-        if (authUser.getAuthority() != Authority.SYS_ADMIN) {
-            if (authUser.getTenantId() == null ||
-                    !tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
-                throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
-                        ThingsboardErrorCode.PERMISSION_DENIED);
-
-            } else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
-                ruleChain.setConfiguration(null);
-            }
+        if (authUser.getAuthority() != Authority.TENANT_ADMIN ||
+                !authUser.getTenantId().equals(tenantId)) {
+            throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
+                    ThingsboardErrorCode.PERMISSION_DENIED);
         }
         return ruleChain;
     }
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 81e5179..8619f8c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -64,7 +64,7 @@ public class RuleChainController extends BaseController {
     @Autowired
     private EventService eventService;
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
     @ResponseBody
     public RuleChain getRuleChainById(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -77,7 +77,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET)
     @ResponseBody
     public RuleChainMetaData getRuleChainMetaData(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -92,7 +92,7 @@ public class RuleChainController extends BaseController {
     }
 
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
     @ResponseBody
     public RuleChain saveRuleChain(@RequestBody RuleChain ruleChain) throws ThingsboardException {
@@ -118,7 +118,46 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
+    @ResponseBody
+    public RuleChain setRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
+        checkParameter(RULE_CHAIN_ID, strRuleChainId);
+        try {
+            RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
+            RuleChain ruleChain = checkRuleChain(ruleChainId);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
+            if (ruleChainService.setRootRuleChain(ruleChainId)) {
+
+                previousRootRuleChain = ruleChainService.findRuleChainById(previousRootRuleChain.getId());
+
+                actorService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
+                        ComponentLifecycleEvent.UPDATED);
+
+                logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
+                        null, ActionType.UPDATED, null);
+
+                ruleChain = ruleChainService.findRuleChainById(ruleChainId);
+
+                actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(),
+                        ComponentLifecycleEvent.UPDATED);
+
+                logEntityAction(ruleChain.getId(), ruleChain,
+                        null, ActionType.UPDATED, null);
+
+            }
+            return ruleChain;
+        } catch (Exception e) {
+            logEntityAction(emptyId(EntityType.RULE_CHAIN),
+                    null,
+                    null,
+                    ActionType.UPDATED, e, strRuleChainId);
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST)
     @ResponseBody
     public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException {
@@ -142,7 +181,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET)
     @ResponseBody
     public TextPageData<RuleChain> getRuleChains(
@@ -151,48 +190,6 @@ public class RuleChainController extends BaseController {
             @RequestParam(required = false) String idOffset,
             @RequestParam(required = false) String textOffset) throws ThingsboardException {
         try {
-            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
-                return checkNotNull(ruleChainService.findSystemRuleChains(pageLink));
-            } else {
-                TenantId tenantId = getCurrentUser().getTenantId();
-                TextPageData<RuleChain> ruleChainsData = checkNotNull(ruleChainService.findAllTenantRuleChainsByTenantIdAndPageLink(tenantId, pageLink));
-                List<RuleChain> ruleChains = ruleChainsData.getData();
-                ruleChains.stream()
-                        .filter(ruleChain -> ruleChain.getTenantId().getId().equals(ModelConstants.NULL_UUID))
-                        .forEach(ruleChain -> ruleChain.setConfiguration(null));
-                return ruleChainsData;
-            }
-        } catch (Exception e) {
-            throw handleException(e);
-        }
-    }
-
-    @PreAuthorize("hasAuthority('SYS_ADMIN')")
-    @RequestMapping(value = "/system/ruleChains", params = {"limit"}, method = RequestMethod.GET)
-    @ResponseBody
-    public TextPageData<RuleChain> getSystemRuleChains(
-            @RequestParam int limit,
-            @RequestParam(required = false) String textSearch,
-            @RequestParam(required = false) String idOffset,
-            @RequestParam(required = false) String textOffset) throws ThingsboardException {
-        try {
-            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            return checkNotNull(ruleChainService.findSystemRuleChains(pageLink));
-        } catch (Exception e) {
-            throw handleException(e);
-        }
-    }
-
-    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
-    @RequestMapping(value = "/tenant/ruleChains", params = {"limit"}, method = RequestMethod.GET)
-    @ResponseBody
-    public TextPageData<RuleChain> getTenantRuleChains(
-            @RequestParam int limit,
-            @RequestParam(required = false) String textSearch,
-            @RequestParam(required = false) String idOffset,
-            @RequestParam(required = false) String textOffset) throws ThingsboardException {
-        try {
             TenantId tenantId = getCurrentUser().getTenantId();
             TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
             return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
@@ -201,7 +198,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
     @ResponseStatus(value = HttpStatus.OK)
     public void deleteRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -209,6 +206,7 @@ public class RuleChainController extends BaseController {
         try {
             RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
             RuleChain ruleChain = checkRuleChain(ruleChainId);
+
             ruleChainService.deleteRuleChainById(ruleChainId);
 
             actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java
new file mode 100644
index 0000000..a67278c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java
@@ -0,0 +1,102 @@
+/**
+ * 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.service.queue;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.dao.queue.MsgQueue;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+@Service
+@Slf4j
+public class DefaultMsgQueueService implements MsgQueueService {
+
+    @Value("${rule.queue.max_size}")
+    private long queueMaxSize;
+
+    @Value("${rule.queue.cleanup_period}")
+    private long queueCleanUpPeriod;
+
+    @Autowired
+    private MsgQueue msgQueue;
+
+    private ScheduledExecutorService cleanupExecutor;
+
+    private Map<TenantId, AtomicLong> pendingCountPerTenant = new ConcurrentHashMap<>();
+
+    @PostConstruct
+    public void init() {
+        if (queueCleanUpPeriod > 0) {
+            cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
+            cleanupExecutor.scheduleAtFixedRate(() -> cleanup(),
+                    queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS);
+        }
+    }
+
+    @PreDestroy
+    public void stop() {
+        if (cleanupExecutor != null) {
+            cleanupExecutor.shutdownNow();
+        }
+    }
+
+    @Override
+    public ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) {
+        AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong());
+        if (pendingMsgCount.incrementAndGet() < queueMaxSize) {
+            return msgQueue.put(tenantId, msg, nodeId, clusterPartition);
+        } else {
+            pendingMsgCount.decrementAndGet();
+            return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!"));
+        }
+    }
+
+    @Override
+    public ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) {
+        ListenableFuture<Void> result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition);
+        AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong());
+        pendingMsgCount.decrementAndGet();
+        return result;
+    }
+
+    @Override
+    public Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) {
+        return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition);
+    }
+
+    private void cleanup() {
+        pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> {
+            pendingMsgCount.set(0);
+            msgQueue.cleanUp(tenantId);
+        });
+    }
+
+}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 45d4b47..2be73d4 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -89,6 +89,7 @@ mqtt:
     leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
     boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
     worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
+    max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
   # MQTT SSL configuration
   ssl:
     # Enable/disable SSL support
@@ -306,18 +307,21 @@ spring:
 
 rule:
   queue:
-    type: "memory"
-    max_size: 10000
-
+    #Message queue type (memory or db)
+    type: "${RULE_QUEUE_TYPE:memory}"
+    #Message queue maximum size (per tenant)
+    max_size: "${RULE_QUEUE_MAX_SIZE:100}"
+    #Message queue cleanup period in seconds
+    cleanup_period: "${RULE_QUEUE_CLEANUP_PERIOD:3600}"
 
 
 # PostgreSQL DAO Configuration
 #spring:
 #  data:
-#    jpa:
+#    sql:
 #      repositories:
 #        enabled: "true"
-#  jpa:
+#  sql:
 #    hibernate:
 #      ddl-auto: "validate"
 #    database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
index 5b895b1..6c2d3db 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
@@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 import org.thingsboard.server.dao.queue.MsgQueue;
 import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.service.queue.MsgQueueService;
 
 import java.io.IOException;
 
@@ -41,7 +42,7 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
     protected RuleChainService ruleChainService;
 
     @Autowired
-    protected MsgQueue msgQueue;
+    protected MsgQueueService msgQueueService;
 
     protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception {
         return doPost("/api/ruleChain", ruleChain, RuleChain.class);
diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
index a294816..356dfee 100644
--- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
@@ -189,7 +189,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
         Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
         Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
 
-        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(ruleChain.getId().getId(), 0L));
+        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), ruleChain.getId().getId(), 0L));
         Assert.assertEquals(0, unAckMsgList.size());
     }
 
@@ -306,10 +306,10 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
         Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
         Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
 
-        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(rootRuleChain.getId().getId(), 0L));
+        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), rootRuleChain.getId().getId(), 0L));
         Assert.assertEquals(0, unAckMsgList.size());
 
-        unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(secondaryRuleChain.getId().getId(), 0L));
+        unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), secondaryRuleChain.getId().getId(), 0L));
         Assert.assertEquals(0, unAckMsgList.size());
     }
 
diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
index 0ea6ff4..2f25b97 100644
--- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
@@ -186,7 +186,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
                 new TbMsgMetaData(),
                 "{}",
                 ruleChain.getId(), null, 0L);
-        msgQueue.put(tbMsg, ruleChain.getId().getId(), 0L);
+        msgQueueService.put(device.getTenantId(), tbMsg, ruleChain.getId().getId(), 0L);
 
         Thread.sleep(1000);
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
index 2e10036..fd61976 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
@@ -67,6 +67,10 @@ public abstract class AbstractCassandraCluster {
     private long initTimeout;
     @Value("${cassandra.init_retry_interval_ms}")
     private long initRetryInterval;
+    @Value("${cassandra.max_requests_per_connection_local:128}")
+    private int max_requests_local;
+    @Value("${cassandra.max_requests_per_connection_remote:128}")
+    private int max_requests_remote;
 
     @Autowired
     private CassandraSocketOptions socketOpts;
@@ -97,8 +101,8 @@ public abstract class AbstractCassandraCluster {
                 .withClusterName(clusterName)
                 .withSocketOptions(socketOpts.getOpts())
                 .withPoolingOptions(new PoolingOptions()
-                        .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768)
-                        .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768));
+                        .setMaxRequestsPerConnection(HostDistance.LOCAL, max_requests_local)
+                        .setMaxRequestsPerConnection(HostDistance.REMOTE, max_requests_remote));
         this.clusterBuilder.withQueryOptions(queryOpts.getOpts());
         this.clusterBuilder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
         if (ssl) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
index ba186cc..3b98356 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
@@ -88,15 +88,27 @@ public abstract class CassandraAbstractDao {
     }
 
     private ResultSet execute(Statement statement, ConsistencyLevel level) {
-        log.debug("Execute cassandra statement {}", statement);
+        if (log.isDebugEnabled()) {
+            log.debug("Execute cassandra statement {}", statementToString(statement));
+        }
         return executeAsync(statement, level).getUninterruptibly();
     }
 
     private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) {
-        log.debug("Execute cassandra async statement {}", statement);
+        if (log.isDebugEnabled()) {
+            log.debug("Execute cassandra async statement {}", statementToString(statement));
+        }
         if (statement.getConsistencyLevel() == null) {
             statement.setConsistencyLevel(level);
         }
         return new RateLimitedResultSetFuture(getSession(), rateLimiter, statement);
     }
+
+    private static String statementToString(Statement statement) {
+        if (statement instanceof BoundStatement) {
+            return ((BoundStatement)statement).preparedStatement().getQueryString();
+        } else {
+            return statement.toString();
+        }
+    }
 }
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java
index e49b89e..19021eb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java
@@ -16,15 +16,19 @@
 package org.thingsboard.server.dao.queue;
 
 import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbMsg;
 
 import java.util.UUID;
 
 public interface MsgQueue {
 
-    ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition);
+    ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition);
 
-    ListenableFuture<Void> ack(TbMsg msg, UUID nodeId, long clusterPartition);
+    ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition);
+
+    Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition);
+
+    ListenableFuture<Void> cleanUp(TenantId tenantId);
 
-    Iterable<TbMsg> findUnprocessed(UUID nodeId, long clusterPartition);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
index ef55bcb..ca61a63 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
@@ -28,8 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.context.annotation.Bean;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
+import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -80,7 +82,7 @@ public class QueueBenchmark implements CommandLineRunner {
                     try {
                         TbMsg msg = randomMsg();
                         UUID nodeId = UUIDs.timeBased();
-                        ListenableFuture<Void> put = msgQueue.put(msg, nodeId, 100L);
+                        ListenableFuture<Void> put = msgQueue.put(new TenantId(EntityId.NULL_UUID), msg, nodeId, 100L);
 //                    ListenableFuture<Void> put = msgQueue.ack(msg, nodeId, 100L);
                         Futures.addCallback(put, new FutureCallback<Void>() {
                             @Override
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 9fc797c..7b2b391 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
@@ -16,7 +16,6 @@
 package org.thingsboard.server.dao.relation;
 
 import com.google.common.base.Function;
-import com.google.common.util.concurrent.AsyncFunction;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
@@ -29,12 +28,23 @@ import org.springframework.cache.annotation.Caching;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.relation.*;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.EntityRelationInfo;
+import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
+import org.thingsboard.server.common.data.relation.EntitySearchDirection;
+import org.thingsboard.server.common.data.relation.EntityTypeFilter;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
 import org.thingsboard.server.dao.entity.EntityService;
 import org.thingsboard.server.dao.exception.DataValidationException;
 
 import javax.annotation.Nullable;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
@@ -170,12 +180,12 @@ public class BaseRelationService implements RelationService {
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         log.trace("Executing deleteEntityRelations [{}]", entity);
         validate(entity);
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
+            inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
-        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo, (List<List<EntityRelation>> relations) ->
+        ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
+        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, relations ->
                 getBooleans(relations, cache, true));
 
         ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
@@ -186,13 +196,12 @@ public class BaseRelationService implements RelationService {
             log.error("Error deleting entity inbound relations", e);
         }
 
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup));
+            outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
-        Futures.transform(inboundRelationsFrom, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
-                getBooleans(relations, cache, false));
+        ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
+        Futures.transform(outboundRelations, relations -> getBooleans(relations, cache, false));
 
         boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity);
         return inboundDeleteResult && outboundDeleteResult;
@@ -201,9 +210,7 @@ public class BaseRelationService implements RelationService {
     private List<Boolean> getBooleans(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
         List<Boolean> results = new ArrayList<>();
         for (List<EntityRelation> relationList : relations) {
-            relationList.stream().forEach(relation -> {
-                checkFromDeleteSync(cache, results, relation, isRemove);
-            });
+            relationList.forEach(relation -> checkFromDeleteSync(cache, results, relation, isRemove));
         }
         return results;
     }
@@ -211,10 +218,8 @@ public class BaseRelationService implements RelationService {
     private void checkFromDeleteSync(Cache cache, List<Boolean> results, EntityRelation relation, boolean isRemove) {
         if (isRemove) {
             results.add(relationDao.deleteRelation(relation));
-            cacheEviction(relation, false, cache);
-        } else {
-            cacheEviction(relation, true, cache);
         }
+        cacheEviction(relation, cache);
     }
 
     @Override
@@ -222,12 +227,12 @@ public class BaseRelationService implements RelationService {
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         log.trace("Executing deleteEntityRelationsAsync [{}]", entity);
         validate(entity);
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
+            inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
-        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transformAsync(inboundRelationsTo,
+        ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
+        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transformAsync(inboundRelations,
                 relations -> {
                     List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);
                     return Futures.allAsList(results);
@@ -235,12 +240,12 @@ public class BaseRelationService implements RelationService {
 
         ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
 
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListFrom.add(relationDao.findAllByTo(entity, typeGroup));
+            outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
-        Futures.transformAsync(inboundRelationsFrom, relations -> {
+        ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
+        Futures.transformAsync(outboundRelations, relations -> {
             List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, false);
             return Futures.allAsList(results);
         });
@@ -252,9 +257,7 @@ public class BaseRelationService implements RelationService {
     private List<ListenableFuture<Boolean>> getListenableFutures(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
         List<ListenableFuture<Boolean>> results = new ArrayList<>();
         for (List<EntityRelation> relationList : relations) {
-            relationList.forEach(relation -> {
-                checkFromDeleteAsync(cache, results, relation, isRemove);
-            });
+            relationList.forEach(relation -> checkFromDeleteAsync(cache, results, relation, isRemove));
         }
         return results;
     }
@@ -262,13 +265,11 @@ public class BaseRelationService implements RelationService {
     private void checkFromDeleteAsync(Cache cache, List<ListenableFuture<Boolean>> results, EntityRelation relation, boolean isRemove) {
         if (isRemove) {
             results.add(relationDao.deleteRelationAsync(relation));
-            cacheEviction(relation, false, cache);
-        } else {
-            cacheEviction(relation, true, cache);
         }
+        cacheEviction(relation, cache);
     }
 
-    private void cacheEviction(EntityRelation relation, boolean outboundOnly, Cache cache) {
+    private void cacheEviction(EntityRelation relation, Cache cache) {
         List<Object> fromToTypeAndTypeGroup = new ArrayList<>();
         fromToTypeAndTypeGroup.add(relation.getFrom());
         fromToTypeAndTypeGroup.add(relation.getTo());
@@ -287,18 +288,16 @@ public class BaseRelationService implements RelationService {
         fromAndTypeGroup.add(relation.getTypeGroup());
         cache.evict(fromAndTypeGroup);
 
-        if (!outboundOnly) {
-            List<Object> toAndTypeGroup = new ArrayList<>();
-            toAndTypeGroup.add(relation.getTo());
-            toAndTypeGroup.add(relation.getTypeGroup());
-            cache.evict(toAndTypeGroup);
-
-            List<Object> toTypeAndTypeGroup = new ArrayList<>();
-            fromTypeAndTypeGroup.add(relation.getTo());
-            fromTypeAndTypeGroup.add(relation.getType());
-            fromTypeAndTypeGroup.add(relation.getTypeGroup());
-            cache.evict(toTypeAndTypeGroup);
-        }
+        List<Object> toAndTypeGroup = new ArrayList<>();
+        toAndTypeGroup.add(relation.getTo());
+        toAndTypeGroup.add(relation.getTypeGroup());
+        cache.evict(toAndTypeGroup);
+
+        List<Object> toTypeAndTypeGroup = new ArrayList<>();
+        fromTypeAndTypeGroup.add(relation.getTo());
+        fromTypeAndTypeGroup.add(relation.getType());
+        fromTypeAndTypeGroup.add(relation.getTypeGroup());
+        cache.evict(toTypeAndTypeGroup);
     }
 
     @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
@@ -383,10 +382,10 @@ public class BaseRelationService implements RelationService {
         return Futures.transformAsync(relations,
                 relations1 -> {
                     List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
-                    relations1.stream().forEach(relation ->
+                    relations1.forEach(relation ->
                             futures.add(fetchRelationInfoAsync(relation,
-                                    relation2 -> relation2.getFrom(),
-                                    (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName)))
+                                    EntityRelation::getFrom,
+                                    EntityRelationInfo::setFromName))
                     );
                     return Futures.successfulAsList(futures);
                 });
@@ -396,13 +395,11 @@ public class BaseRelationService implements RelationService {
                                                                         Function<EntityRelation, EntityId> entityIdGetter,
                                                                         BiConsumer<EntityRelationInfo, String> entityNameSetter) {
         ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(entityIdGetter.apply(relation));
-        ListenableFuture<EntityRelationInfo> entityRelationInfo =
-                Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
-                    EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
-                    entityNameSetter.accept(entityRelationInfo1, entityName1);
-                    return entityRelationInfo1;
-                });
-        return entityRelationInfo;
+        return Futures.transform(entityName, entityName1 -> {
+            EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
+            entityNameSetter.accept(entityRelationInfo1, entityName1);
+            return entityRelationInfo1;
+        });
     }
 
     @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
@@ -437,7 +434,7 @@ public class BaseRelationService implements RelationService {
 
         try {
             ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), maxLvl, new ConcurrentHashMap<>());
-            return Futures.transform(relationSet, (Function<Set<EntityRelation>, List<EntityRelation>>) input -> {
+            return Futures.transform(relationSet, input -> {
                 List<EntityRelation> relations = new ArrayList<>();
                 if (filters == null || filters.isEmpty()) {
                     relations.addAll(input);
@@ -464,7 +461,7 @@ public class BaseRelationService implements RelationService {
         return Futures.transformAsync(relations,
                 relations1 -> {
                     List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
-                    relations1.stream().forEach(relation ->
+                    relations1.forEach(relation ->
                             futures.add(fetchRelationInfoAsync(relation,
                                     relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
                                     (EntityRelationInfo relationInfo, String entityName) -> {
@@ -582,7 +579,7 @@ public class BaseRelationService implements RelationService {
         }
         //TODO: try to remove this blocking operation
         List<Set<EntityRelation>> relations = Futures.successfulAsList(futures).get();
-        relations.forEach(r -> r.forEach(d -> children.add(d)));
+        relations.forEach(r -> r.forEach(children::add));
         return Futures.immediateFuture(children);
     }
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index 9ce1fbe..4a64890 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
@@ -57,8 +57,6 @@ import java.util.stream.Collectors;
 @Slf4j
 public class BaseRuleChainService extends AbstractEntityService implements RuleChainService {
 
-    public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
-
     @Autowired
     private RuleChainDao ruleChainDao;
 
@@ -71,12 +69,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     @Override
     public RuleChain saveRuleChain(RuleChain ruleChain) {
         ruleChainValidator.validate(ruleChain);
-        if (ruleChain.getTenantId() == null) {
-            log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT);
-            ruleChain.setTenantId(SYSTEM_TENANT);
-        }
         RuleChain savedRuleChain = ruleChainDao.save(ruleChain);
-        if (ruleChain.isRoot() && ruleChain.getTenantId() != null && ruleChain.getId() == null) {
+        if (ruleChain.isRoot() && ruleChain.getId() == null) {
             try {
                 createRelation(new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(),
                         EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
@@ -90,6 +84,31 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
+    public boolean setRootRuleChain(RuleChainId ruleChainId) {
+        RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId());
+        if (!ruleChain.isRoot()) {
+            RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
+            if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
+                try {
+                    deleteRelation(new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
+                            EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
+                    previousRootRuleChain.setRoot(false);
+                    ruleChainDao.save(previousRootRuleChain);
+                    createRelation(new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
+                            EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
+                    ruleChain.setRoot(true);
+                    ruleChainDao.save(ruleChain);
+                    return true;
+                } catch (ExecutionException | InterruptedException e) {
+                    log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
     public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) {
         Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
         RuleChain ruleChain = findRuleChainById(ruleChainMetaData.getRuleChainId());
@@ -267,13 +286,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
-    public TextPageData<RuleChain> findSystemRuleChains(TextPageLink pageLink) {
-        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request.");
-        List<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink);
-        return new TextPageData<>(ruleChains, pageLink);
-    }
-
-    @Override
     public TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) {
         Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request.");
         Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request.");
@@ -282,17 +294,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
-    public TextPageData<RuleChain> findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
-        log.trace("Executing findAllTenantRuleChainsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
-        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
-        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
-        List<RuleChain> ruleChains = ruleChainDao.findAllRuleChainsByTenantId(tenantId.getId(), pageLink);
-        return new TextPageData<>(ruleChains, pageLink);
-    }
-
-    @Override
     public void deleteRuleChainById(RuleChainId ruleChainId) {
         Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
+        RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId());
+        if (ruleChain != null && ruleChain.isRoot()) {
+            throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
+        }
         checkRuleNodesAndDelete(ruleChainId);
     }
 
@@ -325,6 +332,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
         relationService.saveRelation(relation);
     }
 
+    private void deleteRelation(EntityRelation relation) throws ExecutionException, InterruptedException {
+        log.debug("Deleting relation: {}", relation);
+        relationService.deleteRelation(relation);
+    }
+
     private DataValidator<RuleChain> ruleChainValidator =
             new DataValidator<RuleChain>() {
                 @Override
@@ -332,16 +344,17 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
                     if (StringUtils.isEmpty(ruleChain.getName())) {
                         throw new DataValidationException("Rule chain name should be specified!.");
                     }
-                    if (ruleChain.getTenantId() != null && !ruleChain.getTenantId().isNullUid()) {
-                        Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId());
-                        if (tenant == null) {
-                            throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
-                        }
-                        if (ruleChain.isRoot()) {
-                            RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
-                            if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
-                                throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
-                            }
+                    if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
+                        throw new DataValidationException("Rule chain should be assigned to tenant!");
+                    }
+                    Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId());
+                    if (tenant == null) {
+                        throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
+                    }
+                    if (ruleChain.isRoot()) {
+                        RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
+                        if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
+                            throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
                         }
                     }
                 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
index 47c8e86..56a20db 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
@@ -60,15 +60,4 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao<RuleCh
         return DaoUtil.convertDataList(ruleChainEntities);
     }
 
-    @Override
-    public List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
-        log.debug("Try to find all rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
-        List<RuleChainEntity> ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
-                Arrays.asList(in(ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))),
-                pageLink);
-
-        log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink);
-        return DaoUtil.convertDataList(ruleChainEntities);
-    }
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
index b9a9932..4a7cfae 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
@@ -37,13 +37,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
      */
     List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
 
-    /**
-     * Find all rule chains by tenantId and page link.
-     *
-     * @param tenantId the tenantId
-     * @param pageLink the page link
-     * @return the list of rule chain objects
-     */
-    List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
index d516e54..4dbe6c9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
@@ -36,6 +36,8 @@ public interface RuleChainService {
 
     RuleChain saveRuleChain(RuleChain ruleChain);
 
+    boolean setRootRuleChain(RuleChainId ruleChainId);
+
     RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData);
 
     RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId);
@@ -54,12 +56,8 @@ public interface RuleChainService {
 
     List<EntityRelation> getRuleNodeRelations(RuleNodeId ruleNodeId);
 
-    TextPageData<RuleChain> findSystemRuleChains(TextPageLink pageLink);
-
     TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink);
 
-    TextPageData<RuleChain> findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
-
     void deleteRuleChainById(RuleChainId ruleChainId);
 
     void deleteRuleChainsByTenantId(TenantId tenantId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
index 425369f..fb5d20a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
@@ -63,15 +63,4 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
                         new PageRequest(0, pageLink.getLimit())));
     }
 
-    @Override
-    public List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
-        return DaoUtil.convertDataList(ruleChainRepository
-                .findAllTenantRuleChainsByTenantId(
-                        UUIDConverter.fromTimeUUID(tenantId),
-                        NULL_UUID_STR,
-                        Objects.toString(pageLink.getTextSearch(), ""),
-                        pageLink.getIdOffset() == null ? NULL_UUID_STR :  UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
-                        new PageRequest(0, pageLink.getLimit())));
-    }
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
index 3b5e4db..58d4aac 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
@@ -35,12 +35,4 @@ public interface RuleChainRepository extends CrudRepository<RuleChainEntity, Str
                                          @Param("idOffset") String idOffset,
                                          Pageable pageable);
 
-    @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId IN (:tenantId, :nullTenantId) " +
-            "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
-            "AND rc.id > :idOffset ORDER BY rc.id")
-    List<RuleChainEntity> findAllTenantRuleChainsByTenantId(@Param("tenantId") String tenantId,
-                                                            @Param("nullTenantId") String nullTenantId,
-                                                            @Param("searchText") String searchText,
-                                                            @Param("idOffset") String idOffset,
-                                                            Pageable pageable);
 }
diff --git a/docker/k8s/redis.yaml b/docker/k8s/redis.yaml
new file mode 100644
index 0000000..3af810e
--- /dev/null
+++ b/docker/k8s/redis.yaml
@@ -0,0 +1,93 @@
+#
+# 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.
+#
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    name: redis-service
+  name: redis-service
+spec:
+  ports:
+    - name: redis-service
+      protocol: TCP
+      port: 6379
+      targetPort: 6379
+  selector:
+    app: redis
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: redis-conf
+data:
+  redis.conf: |
+    appendonly yes
+    protected-mode no
+    bind 0.0.0.0
+    port 6379
+    dir /var/lib/redis
+---
+apiVersion: apps/v1beta1
+kind: StatefulSet
+metadata:
+  name: redis
+spec:
+  serviceName: redis-service
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: redis
+    spec:
+      terminationGracePeriodSeconds: 10
+      containers:
+        - name: redis
+          image: redis:4.0.9
+          command:
+            - redis-server
+          args:
+            - /etc/redis/redis.conf
+          resources:
+            requests:
+              cpu: 100m
+              memory: 100Mi
+          ports:
+            - containerPort: 6379
+              name: redis
+          volumeMounts:
+            - name: redis-data
+              mountPath: /var/lib/redis
+            - name: redis-conf
+              mountPath: /etc/redis
+      volumes:
+        - name: redis-conf
+          configMap:
+            name: redis-conf
+            items:
+              - key: redis.conf
+                path: redis.conf
+  volumeClaimTemplates:
+    - metadata:
+        name: redis-data
+        annotations:
+          volume.beta.kubernetes.io/storage-class: fast
+      spec:
+        accessModes: [ "ReadWriteOnce" ]
+        resources:
+          requests:
+            storage: 1Gi
\ No newline at end of file

docker/k8s/tb.yaml 12(+12 -0)

diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml
index 74b6702..1d94cb1 100644
--- a/docker/k8s/tb.yaml
+++ b/docker/k8s/tb.yaml
@@ -54,6 +54,8 @@ data:
   cassandra.host: "cassandra-headless"
   cassandra.port: "9042"
   database.type: "cassandra"
+  cache.type: "redis"
+  redis.host: "redis-service"
 ---
 apiVersion: apps/v1beta1
 kind: StatefulSet
@@ -127,6 +129,16 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: status.podIP
+        - name: CACHE_TYPE
+          valueFrom:
+            configMapKeyRef:
+              name: tb-config
+              key: cache.type
+        - name: REDIS_HOST
+          valueFrom:
+            configMapKeyRef:
+              name: tb-config
+              key: redis.host
         command:
         - sh
         - -c
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
index fcb8912..01386de 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
@@ -48,12 +48,6 @@ public interface TbContext {
 
     void tellSelf(TbMsg msg, long delayMs);
 
-    void tellOthers(TbMsg msg);
-
-    void tellSibling(TbMsg msg, ServerAddress address);
-
-    void ack(TbMsg msg);
-
     void tellError(TbMsg msg, Throwable th);
 
     void updateSelf(RuleNode self);
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
index b5a5982..adb861b 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
@@ -69,13 +69,13 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
 
     protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) {
         return ctx.getJsExecutor().executeAsync(() -> {
-            TbMsg theMsg = msg;
+            TbMsg dummyMsg = msg;
             if (previousDetails != null) {
                 TbMsgMetaData metaData = msg.getMetaData().copy();
                 metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails));
-                theMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), metaData, msg.getData());
+                dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData());
             }
-            return buildDetailsJsEngine.executeJson(theMsg);
+            return buildDetailsJsEngine.executeJson(dummyMsg);
         });
     }
 
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
index 976d8ba..432966e 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
@@ -33,8 +33,6 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
  */
 public class MqttTransportServerInitializer extends ChannelInitializer<SocketChannel> {
 
-    private static final int MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
-
     private final SessionMsgProcessor processor;
     private final DeviceService deviceService;
     private final DeviceAuthService authService;
@@ -42,10 +40,11 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
     private final MqttTransportAdaptor adaptor;
     private final MqttSslHandlerProvider sslHandlerProvider;
     private final QuotaService quotaService;
+    private final int maxPayloadSize;
 
     public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
                                           MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
-                                          QuotaService quotaService) {
+                                          QuotaService quotaService, int maxPayloadSize) {
         this.processor = processor;
         this.deviceService = deviceService;
         this.authService = authService;
@@ -53,6 +52,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
         this.adaptor = adaptor;
         this.sslHandlerProvider = sslHandlerProvider;
         this.quotaService = quotaService;
+        this.maxPayloadSize = maxPayloadSize;
     }
 
     @Override
@@ -63,7 +63,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
             sslHandler = sslHandlerProvider.getSslHandler();
             pipeline.addLast(sslHandler);
         }
-        pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
+        pipeline.addLast("decoder", new MqttDecoder(maxPayloadSize));
         pipeline.addLast("encoder", MqttEncoder.INSTANCE);
 
         MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
index 1ae7d38..f0129e1 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
@@ -82,7 +82,8 @@ public class MqttTransportService {
     private Integer bossGroupThreadCount;
     @Value("${mqtt.netty.worker_group_thread_count}")
     private Integer workerGroupThreadCount;
-
+    @Value("${mqtt.netty.max_payload_size}")
+    private Integer maxPayloadSize;
 
     private MqttTransportAdaptor adaptor;
 
@@ -106,7 +107,7 @@ public class MqttTransportService {
         b.group(bossGroup, workerGroup)
                 .channel(NioServerSocketChannel.class)
                 .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
-                        adaptor, sslHandlerProvider, quotaService));
+                        adaptor, sslHandlerProvider, quotaService, maxPayloadSize));
 
         serverChannel = b.bind(host, port).sync().channel();
         log.info("Mqtt transport started!");
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index ba1265f..762bf1a 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -21,8 +21,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
 
 /*@ngInject*/
 function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
-                       assetService, tenantService, customerService,
-                       ruleService, pluginService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
+                       assetService, tenantService, customerService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
     var service = {
         getEntity: getEntity,
         getEntities: getEntities,
@@ -61,12 +60,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.entityType.customer:
                 promise = customerService.getCustomer(entityId, config);
                 break;
-            case types.entityType.rule:
-                promise = ruleService.getRule(entityId, config);
-                break;
-            case types.entityType.plugin:
-                promise = pluginService.getPlugin(entityId, config);
-                break;
             case types.entityType.dashboard:
                 promise = dashboardService.getDashboardInfo(entityId, config);
                 break;
@@ -146,14 +139,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 promise = getEntitiesByIdsPromise(
                     (id) => customerService.getCustomer(id, config), entityIds);
                 break;
-            case types.entityType.rule:
-                promise = getEntitiesByIdsPromise(
-                    (id) => ruleService.getRule(id, config), entityIds);
-                break;
-            case types.entityType.plugin:
-                promise = getEntitiesByIdsPromise(
-                    (id) => pluginService.getPlugin(id, config), entityIds);
-                break;
             case types.entityType.dashboard:
                 promise = getEntitiesByIdsPromise(
                     (id) => dashboardService.getDashboardInfo(id, config), entityIds);
@@ -268,12 +253,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     promise = customerService.getCustomers(pageLink, config);
                 }
                 break;
-            case types.entityType.rule:
-                promise = ruleService.getAllRules(pageLink, config);
-                break;
-            case types.entityType.plugin:
-                promise = pluginService.getAllPlugins(pageLink, config);
-                break;
             case types.entityType.rulechain:
                 promise = ruleChainService.getRuleChains(pageLink, config);
                 break;
@@ -742,16 +721,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         switch(authority) {
             case 'SYS_ADMIN':
                 entityTypes.tenant = types.entityType.tenant;
-                entityTypes.rule = types.entityType.rule;
-                entityTypes.plugin = types.entityType.plugin;
                 break;
             case 'TENANT_ADMIN':
                 entityTypes.device = types.entityType.device;
                 entityTypes.asset = types.entityType.asset;
                 entityTypes.tenant = types.entityType.tenant;
                 entityTypes.customer = types.entityType.customer;
-                entityTypes.rule = types.entityType.rule;
-                entityTypes.plugin = types.entityType.plugin;
                 entityTypes.dashboard = types.entityType.dashboard;
                 if (useAliasEntityTypes) {
                     entityTypes.current_customer = types.aliasEntityType.current_customer;
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index 821d82d..e7436de 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -22,11 +22,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
     var ruleNodeComponents = null;
 
     var service = {
-        getSystemRuleChains: getSystemRuleChains,
-        getTenantRuleChains: getTenantRuleChains,
         getRuleChains: getRuleChains,
         getRuleChain: getRuleChain,
         saveRuleChain: saveRuleChain,
+        setRootRuleChain: setRootRuleChain,
         deleteRuleChain: deleteRuleChain,
         getRuleChainMetaData: getRuleChainMetaData,
         saveRuleChainMetaData: saveRuleChainMetaData,
@@ -40,29 +39,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
 
     return service;
 
-    function getSystemRuleChains (pageLink, config) {
-        var deferred = $q.defer();
-        var url = '/api/system/ruleChains?limit=' + pageLink.limit;
-        if (angular.isDefined(pageLink.textSearch)) {
-            url += '&textSearch=' + pageLink.textSearch;
-        }
-        if (angular.isDefined(pageLink.idOffset)) {
-            url += '&idOffset=' + pageLink.idOffset;
-        }
-        if (angular.isDefined(pageLink.textOffset)) {
-            url += '&textOffset=' + pageLink.textOffset;
-        }
-        $http.get(url, config).then(function success(response) {
-            deferred.resolve(response.data);
-        }, function fail() {
-            deferred.reject();
-        });
-        return deferred.promise;
-    }
-
-    function getTenantRuleChains (pageLink, config) {
+    function getRuleChains (pageLink, config) {
         var deferred = $q.defer();
-        var url = '/api/tenant/ruleChains?limit=' + pageLink.limit;
+        var url = '/api/ruleChains?limit=' + pageLink.limit;
         if (angular.isDefined(pageLink.textSearch)) {
             url += '&textSearch=' + pageLink.textSearch;
         }
@@ -80,18 +59,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function getRuleChains (pageLink, config) {
+    function getRuleChain(ruleChainId, config) {
         var deferred = $q.defer();
-        var url = '/api/ruleChains?limit=' + pageLink.limit;
-        if (angular.isDefined(pageLink.textSearch)) {
-            url += '&textSearch=' + pageLink.textSearch;
-        }
-        if (angular.isDefined(pageLink.idOffset)) {
-            url += '&idOffset=' + pageLink.idOffset;
-        }
-        if (angular.isDefined(pageLink.textOffset)) {
-            url += '&textOffset=' + pageLink.textOffset;
-        }
+        var url = '/api/ruleChain/' + ruleChainId;
         $http.get(url, config).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
@@ -100,10 +70,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function getRuleChain(ruleChainId, config) {
+    function saveRuleChain(ruleChain) {
         var deferred = $q.defer();
-        var url = '/api/ruleChain/' + ruleChainId;
-        $http.get(url, config).then(function success(response) {
+        var url = '/api/ruleChain';
+        $http.post(url, ruleChain).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
             deferred.reject();
@@ -111,10 +81,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function saveRuleChain(ruleChain) {
+    function setRootRuleChain(ruleChainId) {
         var deferred = $q.defer();
-        var url = '/api/ruleChain';
-        $http.post(url, ruleChain).then(function success(response) {
+        var url = '/api/ruleChain/' + ruleChainId + '/root';
+        $http.post(url).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
             deferred.reject();
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index 3131013..f021efb 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -74,6 +74,7 @@ import thingsboardApiAttribute from './api/attribute.service';
 import thingsboardApiEntity from './api/entity.service';
 import thingsboardApiAlarm from './api/alarm.service';
 import thingsboardApiAuditLog from './api/audit-log.service';
+import thingsboardApiComponentDescriptor from './api/component-descriptor.service';
 import thingsboardApiRuleChain from './api/rule-chain.service';
 
 import 'typeface-roboto';
@@ -81,7 +82,7 @@ import 'font-awesome/css/font-awesome.min.css';
 import 'angular-material/angular-material.min.css';
 import 'angular-material-icons/angular-material-icons.css';
 import 'angular-gridster/dist/angular-gridster.min.css';
-import 'v-accordion/dist/v-accordion.min.css'
+import 'v-accordion/dist/v-accordion.min.css';
 import 'md-color-picker/dist/mdColorPicker.min.css';
 import 'mdPickers/dist/mdPickers.min.css';
 import 'angular-hotkeys/build/hotkeys.min.css';
@@ -139,6 +140,7 @@ angular.module('thingsboard', [
     thingsboardApiEntity,
     thingsboardApiAlarm,
     thingsboardApiAuditLog,
+    thingsboardApiComponentDescriptor,
     thingsboardApiRuleChain,
     uiRouter])
     .config(AppConfig)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 5c30563..7f85f6a 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -297,16 +297,15 @@ export default angular.module('thingsboard.types', [])
                 }
             },
             componentType: {
+                enrichment: "ENRICHMENT",
                 filter: "FILTER",
-                processor: "PROCESSOR",
+                transformation: "TRANSFORMATION",
                 action: "ACTION",
-                plugin: "PLUGIN"
+                external: "EXTERNAL"
             },
             entityType: {
                 device: "DEVICE",
                 asset: "ASSET",
-                rule: "RULE",
-                plugin: "PLUGIN",
                 tenant: "TENANT",
                 customer: "CUSTOMER",
                 user: "USER",
@@ -331,18 +330,6 @@ export default angular.module('thingsboard.types', [])
                     list: 'entity.list-of-assets',
                     nameStartsWith: 'entity.asset-name-starts-with'
                 },
-                "RULE": {
-                    type: 'entity.type-rule',
-                    typePlural: 'entity.type-rules',
-                    list: 'entity.list-of-rules',
-                    nameStartsWith: 'entity.rule-name-starts-with'
-                },
-                "PLUGIN": {
-                    type: 'entity.type-plugin',
-                    typePlural: 'entity.type-plugins',
-                    list: 'entity.list-of-plugins',
-                    nameStartsWith: 'entity.plugin-name-starts-with'
-                },
                 "TENANT": {
                     type: 'entity.type-tenant',
                     typePlural: 'entity.type-tenants',
@@ -419,7 +406,8 @@ export default angular.module('thingsboard.types', [])
             extensionType: {
                 http: "HTTP",
                 mqtt: "MQTT",
-                opc: "OPC UA"
+                opc: "OPC UA",
+                modbus: "MODBUS"
             },
             extensionValueType: {
                 string: 'value.string',
@@ -463,6 +451,26 @@ export default angular.module('thingsboard.types', [])
                 PKCS12: "PKCS12",
                 JKS: "JKS"
             },
+            extensionModbusFunctionCodes: {
+                1: "Read Coils (1)",
+                2: "Read Discrete Inputs (2)",
+                3: "Read Multiple Holding Registers (3)",
+                4: "Read Input Registers (4)"
+            },
+            extensionModbusTransports: {
+                tcp: "TCP",
+                udp: "UDP",
+                rtu: "RTU"
+            },
+            extensionModbusRtuParities: {
+                none: "none",
+                even: "even",
+                odd: "odd"
+            },
+            extensionModbusRtuEncodings: {
+                ascii: "ascii",
+                rtu: "rtu"
+            },
             latestTelemetry: {
                 value: "LATEST_TELEMETRY",
                 name: "attribute.scope-latest-telemetry",
diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js
index 2dfc3be..e46c614 100644
--- a/ui/src/app/entity/entity-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-autocomplete.directive.js
@@ -131,18 +131,6 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
                     scope.noEntitiesMatchingText = 'device.no-devices-matching';
                     scope.entityRequiredText = 'device.device-required';
                     break;
-                case types.entityType.rule:
-                    scope.selectEntityText = 'rule.select-rule';
-                    scope.entityText = 'rule.rule';
-                    scope.noEntitiesMatchingText = 'rule.no-rules-matching';
-                    scope.entityRequiredText = 'rule.rule-required';
-                    break;
-                case types.entityType.plugin:
-                    scope.selectEntityText = 'plugin.select-plugin';
-                    scope.entityText = 'plugin.plugin';
-                    scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
-                    scope.entityRequiredText = 'plugin.plugin-required';
-                    break;
                 case types.entityType.rulechain:
                     scope.selectEntityText = 'rulechain.select-rulechain';
                     scope.entityText = 'rulechain.rulechain';
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
index 2b0eded..4b8221a 100644
--- a/ui/src/app/extension/extension-dialog.controller.js
+++ b/ui/src/app/extension/extension-dialog.controller.js
@@ -49,7 +49,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
                 "brokers": []
             };
         }
-        if (vm.extension.type === "OPC UA") {
+        if (vm.extension.type === "OPC UA" || vm.extension.type === "MODBUS") {
             vm.extension.configuration = {
                 "servers": []
             };
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
index 4e200d5..fcf7840 100644
--- a/ui/src/app/extension/extension-dialog.tpl.html
+++ b/ui/src/app/extension/extension-dialog.tpl.html
@@ -62,6 +62,7 @@
                         <div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
                         <div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div>
                         <div tb-extension-form-opc  configuration="vm.extension.configuration"  ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
+                        <div tb-extension-form-modbus  configuration="vm.extension.configuration"  ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.modbus"></div>
                     </fieldset>
                 </md-content>
             </div>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
new file mode 100644
index 0000000..2dfc961
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+import 'brace/ext/language_tools';
+import 'brace/mode/json';
+import 'brace/theme/github';
+
+import './extension-form.scss';
+
+/* eslint-disable angular/log */
+
+import extensionFormModbusTemplate from './extension-form-modbus.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ExtensionFormModbusDirective($compile, $templateCache, $translate, types) {
+
+
+    var linker = function(scope, element) {
+
+
+        function Server() {
+            this.transport = {
+                "type": "tcp",
+                "host": "localhost",
+                "port": 502,
+                "timeout": 3000
+            };
+            this.devices = []
+        }
+
+        function Device() {
+            this.unitId = 1;
+            this.deviceName = "";
+            this.attributesPollPeriod = 1000;
+            this.timeseriesPollPeriod = 1000;
+            this.attributes = [];
+            this.timeseries = [];
+        }
+
+        function Tag(globalPollPeriod) {
+            this.tag = "";
+            this.type = "long";
+            this.pollPeriod = globalPollPeriod;
+            this.functionCode = 3;
+            this.address = 0;
+            this.registerCount = 1;
+            this.bit = 0;
+            this.byteOrder = "BIG";
+        }
+
+
+        var template = $templateCache.get(extensionFormModbusTemplate);
+        element.html(template);
+
+        scope.types = types;
+        scope.theForm = scope.$parent.theForm;
+
+
+        if (!scope.configuration.servers.length) {
+            scope.configuration.servers.push(new Server());
+        }
+
+        scope.addServer = function(serversList) {
+            serversList.push(new Server());
+            scope.theForm.$setDirty();
+        };
+
+        scope.addDevice = function(deviceList) {
+            deviceList.push(new Device());
+            scope.theForm.$setDirty();
+        };
+
+        scope.addNewAttribute = function(device) {
+            device.attributes.push(new Tag(device.attributesPollPeriod));
+            scope.theForm.$setDirty();
+        };
+
+        scope.addNewTimeseries = function(device) {
+            device.timeseries.push(new Tag(device.timeseriesPollPeriod));
+            scope.theForm.$setDirty();
+        };
+
+        scope.removeItem = (item, itemList) => {
+            var index = itemList.indexOf(item);
+            if (index > -1) {
+                itemList.splice(index, 1);
+            }
+            scope.theForm.$setDirty();
+        };
+
+        scope.onTransportChanged = function(server) {
+            var type = server.transport.type;
+
+            server.transport = {};
+            server.transport.type = type;
+            server.transport.timeout = 3000;
+
+            scope.theForm.$setDirty();
+        };
+        
+        $compile(element.contents())(scope);
+
+
+        scope.collapseValidation = function(index, id) {
+            var invalidState = angular.element('#'+id+':has(.ng-invalid)');
+            if(invalidState.length) {
+                invalidState.addClass('inner-invalid');
+            }
+        };
+
+        scope.expandValidation = function (index, id) {
+            var invalidState = angular.element('#'+id);
+            invalidState.removeClass('inner-invalid');
+        };
+
+    };
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            configuration: "=",
+            isAdd: "="
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
new file mode 100644
index 0000000..989d3f0
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
@@ -0,0 +1,774 @@
+<!--
+
+    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-card class="extension-form extension-modbus">
+    <md-card-title>
+        <md-card-title-text>
+            <span translate class="md-headline">extension.configuration</span>
+        </md-card-title-text>
+    </md-card-title>
+
+    <md-card-content>
+        <v-accordion id="modbus-server-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+            <v-pane id="modbus-servers-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'extension.modbus-server' | translate }}
+                </v-pane-header>
+
+                <v-pane-content>
+                    <div ng-if="configuration.servers.length === 0">
+                        <span translate layout-align="center center" class="tb-prompt">extension.modbus-add-server-prompt</span>
+                    </div>
+
+                    <div ng-if="configuration.servers.length > 0">
+                        <ol class="list-group">
+                            <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
+                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                           class="md-icon-button"
+                                           ng-click="removeItem(server, configuration.servers)"
+                                           ng-hide="configuration.servers.length < 2"
+                                >
+                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                    <md-tooltip md-direction="top">
+                                        {{ 'action.remove' | translate }}
+                                    </md-tooltip>
+                                </md-button>
+
+                                <md-card>
+                                    <md-card-content>
+                                        
+                                        <div layout="row">
+    
+                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                <label translate>extension.modbus-transport</label>
+                                                <md-select required
+                                                           name="transportType_{{serverIndex}}"
+                                                           ng-model="server.transport.type"
+                                                           ng-change="onTransportChanged(server)"
+                                                >
+                                                    <md-option ng-value="transportType"
+                                                               ng-repeat="(transportType, transportValue) in types.extensionModbusTransports"
+                                                    ><span ng-bind="transportValue"></span></md-option>
+                                                </md-select>
+                                                <div ng-messages="theForm['transportType_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                            
+                                        </div>
+                                        
+                                        <div layout="row" ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.host</label>
+                                                <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
+                                                <div ng-messages="theForm['transportHost_' + serverIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.port</label>
+                                                <input type="number"
+                                                       required
+                                                       name="transportPort_{{serverIndex}}"
+                                                       ng-model="server.transport.port"
+                                                       min="1"
+                                                       max="65535"
+                                                >
+                                                <div ng-messages="theForm['transportPort_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                    <div translate
+                                                         ng-message="min"
+                                                    >extension.port-range</div>
+                                                    <div translate
+                                                         ng-message="max"
+                                                    >extension.port-range</div>
+                                                </div>
+                                            </md-input-container>
+                                            
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.timeout</label>
+                                                <input type="number"
+                                                       required name="transportTimeout_{{serverIndex}}"
+                                                       ng-model="server.transport.timeout"
+                                                >
+                                                <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </div>
+                                        
+                                        <div ng-if="server.transport.type == 'rtu'">
+                                        
+                                            <div layout="row">
+                                                <md-input-container flex="70" class="md-block">
+                                                    <label translate>extension.modbus-port-name</label>
+                                                    <input required name="transportPortName_{{serverIndex}}" ng-model="server.transport.portName">
+                                                    <div ng-messages="theForm['transportPortName_' + serverIndex].$error">
+                                                        <div translate ng-message="required">extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="30" class="md-block">
+                                                    <label translate>extension.timeout</label>
+                                                    <input type="number"
+                                                           required name="transportTimeout_{{serverIndex}}"
+                                                           ng-model="server.transport.timeout"
+                                                    >
+                                                    <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                            </div>
+                                            
+                                            <div layout="row">
+                                                <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                    <label translate>extension.modbus-encoding</label>
+                                                    <md-select required
+                                                               name="transportEncoding_{{serverIndex}}"
+                                                               ng-model="server.transport.encoding"
+                                                    >
+                                                        <md-option ng-value="encodingType"
+                                                                   ng-repeat="(encodingType, encodingValue) in types.extensionModbusRtuEncodings"
+                                                        ><span ng-bind="encodingValue"></span></md-option>
+                                                    </md-select>
+                                                    <div ng-messages="theForm['transportEncoding_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                    <label translate>extension.modbus-parity</label>
+                                                    <md-select name="transportParity_{{serverIndex}}" ng-model="server.transport.parity">
+                                                        <md-option ng-repeat="(parityKey, parityValue) in types.extensionModbusRtuParities"
+                                                                   ng-value="parityValue"
+                                                        >
+                                                            {{parityValue}}
+                                                        </md-option>
+                                                    </md-select>
+                                                    <div ng-messages="theForm['transportParity_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                            
+                                            </div>
+                                            
+                                            <div layout="row">
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-baudrate</label>
+                                                    <input type="number"
+                                                           required name="transportBaudRate_{{serverIndex}}"
+                                                           ng-model="server.transport.baudRate"
+                                                    >
+                                                    <div ng-messages="theForm['transportBaudRate_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-databits</label>
+                                                    <input type="number"
+                                                           required
+                                                           name="transportDataBits_{{serverIndex}}"
+                                                           ng-model="server.transport.dataBits"
+                                                           min="7"
+                                                           max="8"
+                                                    >
+                                                    <div ng-messages="theForm['transportDataBits_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                        <div translate
+                                                             ng-message="min"
+                                                        >extension.modbus-databits-range</div>
+                                                        <div translate
+                                                             ng-message="max"
+                                                        >extension.modbus-databits-range</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-stopbits</label>
+                                                    <input type="number"
+                                                           required
+                                                           name="transportStopBits_{{serverIndex}}"
+                                                           ng-model="server.transport.stopBits"
+                                                           min="1"
+                                                           max="2"
+                                                    >
+                                                    <div ng-messages="theForm['transportStopBits_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                        <div translate
+                                                             ng-message="min"
+                                                        >extension.modbus-stopbits-range</div>
+                                                        <div translate
+                                                             ng-message="max"
+                                                        >extension.modbus-stopbits-range</div>
+                                                    </div>
+                                                </md-input-container>
+                                            </div>                                          
+                                        </div>
+
+                                        <v-accordion id="modbus-mapping-accordion"
+                                                     class="vAccordion--default"
+                                                     onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                            <v-pane id="modbus-mapping-pane_{{serverIndex}}">
+                                                <v-pane-header>
+                                                    {{ 'extension.mapping' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="server.devices.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item"
+                                                                ng-repeat="(deviceIndex, device) in server.devices"
+                                                            >
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                           class="md-icon-button"
+                                                                           ng-click="removeItem(device, server.devices)"
+                                                                >
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <div flex layout="row">
+                                                                            <md-input-container flex="80" class="md-block">
+                                                                                <label translate>extension.modbus-device-name</label>
+                                                                                <input required
+                                                                                       name="deviceName_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.deviceName"
+                                                                                >
+                                                                                <div ng-messages="theForm['deviceName_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        
+                                                                            <md-input-container flex="20" class="md-block">
+                                                                                <label translate>extension.modbus-unit-id</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="unitId_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.unitId"
+                                                                                       min="1"
+                                                                                       max="247"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['unitId_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-unit-id-range</div>
+                                                                                    <div translate
+                                                                                         ng-message="max"
+                                                                                    >extension.modbus-unit-id-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </div>
+                                                                          
+                                                                        <div flex layout="row">  
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.modbus-attributes-poll-period</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="attributesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.attributesPollPeriod"
+                                                                                       min="1"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['attributesPollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.modbus-timeseries-poll-period</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="timeseriesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.timeseriesPollPeriod"
+                                                                                       min="1"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['timeseriesPollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            
+                                                                        </div>
+
+
+                                                                        <v-accordion id="modbus-attributes-accordion"
+                                                                                     class="vAccordion--default"
+                                                                                     onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                                                            <v-pane id="modbus-attributes-pane_{{serverIndex}}{{deviceIndex}}">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.attributes' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-show="device.attributes.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item"
+                                                                                                ng-repeat="(attributeIndex, attribute) in device.attributes"
+                                                                                            >
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                                                           class="md-icon-button"
+                                                                                                           ng-click="removeItem(attribute, device.attributes)">
+                                                                                                    <ng-md-icon icon="close"
+                                                                                                                aria-label="{{ 'action.remove' | translate }}"
+                                                                                                    ></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-tag</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusAttributeTag_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                       ng-model="attribute.tag"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusAttributeTag_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="30" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusAttributeType_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                           ng-model="attribute.type"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+                                                                                                                               ng-value="attrType"
+                                                                                                                    >
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusAttributeType_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                            <md-input-container flex="20" class="md-block">
+                                                                                                                <label translate>extension.modbus-poll-period</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.pollPeriod"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusAttributeFunctionCode_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                           ng-model="attribute.functionCode"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+                                                                                                                               ng-value="functionCode"
+                                                                                                                    >
+                                                                                                                        {{functionName}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusAttributeFunctionCode_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-register-address</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="address_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.address"
+                                                                                                                       min="0"
+                                                                                                                       max="65535"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="attribute.type != 'boolean'">
+                                                                                                                <label translate>extension.modbus-register-count</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="registerCount_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.registerCount"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-count-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="attribute.type == 'boolean' && attribute.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-register-bit-index</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="bit_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.bit"
+                                                                                                                       min="0"
+                                                                                                                       max="15"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">    
+                                                                                                            <md-input-container flex="100" class="md-block" ng-if="attribute.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-byte-order</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                       ng-model="attribute.byteOrder"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container> 
+                                                                                                            
+                                                                                                        </section>
+
+
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addNewAttribute(device)"
+                                                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                                                        >
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-attribute</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+                                                                        <v-accordion id="modbus-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                                                            <v-pane id="modbus-timeseries-pane_{{serverIndex}}{{deviceIndex}}">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.timeseries' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-show="device.timeseries.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item"
+                                                                                                ng-repeat="(timeseriesIndex, timeserie) in device.timeseries"
+                                                                                            >
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                                                           class="md-icon-button"
+                                                                                                           ng-click="removeItem(timeserie, device.timeseries)">
+                                                                                                    <ng-md-icon icon="close"
+                                                                                                                aria-label="{{ 'action.remove' | translate }}"
+                                                                                                    ></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-tag</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusTimeserieTag_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                       ng-model="timeserie.tag"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusTimeserieTag_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="30" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusTimeserieType_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                           ng-model="timeserie.type"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+                                                                                                                               ng-value="attrType"
+                                                                                                                    >
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusTimeserieType_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                            <md-input-container flex="20" class="md-block">
+                                                                                                                <label translate>extension.modbus-poll-period</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.pollPeriod"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">  
+                                                                                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusTimeserieFunctionCode_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                           ng-model="timeserie.functionCode"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+                                                                                                                               ng-value="functionCode"
+                                                                                                                    >
+                                                                                                                        {{functionName}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusTimeserieFunctionCode_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-register-address</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="address_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.address"
+                                                                                                                       min="0"
+                                                                                                                       max="65535"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="timeserie.type != 'boolean'">
+                                                                                                                <label translate>extension.modbus-register-count</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="registerCount_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.registerCount"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-count-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="timeserie.type == 'boolean' && timeserie.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-register-bit-index</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="bit_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.bit"
+                                                                                                                       min="0"
+                                                                                                                       max="15"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                           
+                                                                                                            <md-input-container flex="100" class="md-block" ng-if="timeserie.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-byte-order</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                       ng-model="timeserie.byteOrder"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container> 
+                                                                                                            
+                                                                                                        </section>
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addNewTimeseries(device)"
+                                                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                                                        >
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-timeseries</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex
+                                                         layout="row"
+                                                         layout-align="start center"
+                                                    >
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click="addDevice(server.devices)"
+                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                        >
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-device</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                    </md-card-content>
+                                </md-card>
+                            </li>
+                        </ol>
+
+                        <div flex
+                             layout="row"
+                             layout-align="start center"
+                        >
+                            <md-button class="md-primary md-raised"
+                                       ng-click="addServer(configuration.servers)"
+                                       aria-label="{{ 'action.add' | translate }}"
+                            >
+                                <md-icon class="material-icons">add</md-icon>
+                                <span translate>extension.modbus-add-server</span>
+                            </md-button>
+                        </div>
+
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+        <!--{{config}}-->
+    </md-card-content>
+</md-card>
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
index 5a7c00b..59d8a7e 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
@@ -194,7 +194,7 @@
                                             </md-input-container>
                                         </div>
 
-                                        <v-accordion id="opc-keystore-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                        <v-accordion id="opc-keystore-accordion" class="vAccordion--default" ng-if="server.security != 'None'" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
                                             <v-pane id="opc-keystore-pane__{{serverIndex}}" expanded="true">
                                                 <v-pane-header>
                                                     {{ 'extension.opc-keystore' | translate }}
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
index f04880c..8e29348 100644
--- a/ui/src/app/extension/index.js
+++ b/ui/src/app/extension/index.js
@@ -17,6 +17,8 @@ import ExtensionTableDirective from './extension-table.directive';
 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
+import ExtensionFormModbusDirective from './extensions-forms/extension-form-modbus.directive';
+
 import {ParseToNull} from './extension-dialog.controller';
 
 export default angular.module('thingsboard.extension', [])
@@ -24,5 +26,6 @@ export default angular.module('thingsboard.extension', [])
     .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
     .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
     .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
+    .directive('tbExtensionFormModbus', ExtensionFormModbusDirective)
     .directive('parseToNull', ParseToNull)
     .name;
\ No newline at end of file
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index f04b036..d64441f 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -25,8 +25,7 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
 
 /*@ngInject*/
 export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types,
-                                     dashboardUtils, entityService, dashboardService, pluginService, ruleService,
-                                     ruleChainService, widgetService, toast, attributeService) {
+                                     dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
 
 
     var service = {
@@ -34,10 +33,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         importDashboard: importDashboard,
         exportWidget: exportWidget,
         importWidget: importWidget,
-        exportPlugin: exportPlugin,
-        importPlugin: importPlugin,
-        exportRule: exportRule,
-        importRule: importRule,
         exportRuleChain: exportRuleChain,
         importRuleChain: importRuleChain,
         exportWidgetType: exportWidgetType,
@@ -221,62 +216,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         return true;
     }
 
-    // Rule functions
-
-    function exportRule(ruleId) {
-        ruleService.getRule(ruleId).then(
-            function success(rule) {
-                var name = rule.name;
-                name = name.toLowerCase().replace(/\W/g,"_");
-                exportToPc(prepareExport(rule), name + '.json');
-            },
-            function fail(rejection) {
-                var message = rejection;
-                if (!message) {
-                    message = $translate.instant('error.unknown-error');
-                }
-                toast.showError($translate.instant('rule.export-failed-error', {error: message}));
-            }
-        );
-    }
-
-    function importRule($event) {
-        var deferred = $q.defer();
-        openImportDialog($event, 'rule.import', 'rule.rule-file').then(
-            function success(rule) {
-                if (!validateImportedRule(rule)) {
-                    toast.showError($translate.instant('rule.invalid-rule-file-error'));
-                    deferred.reject();
-                } else {
-                    rule.state = 'SUSPENDED';
-                    ruleService.saveRule(rule).then(
-                        function success() {
-                            deferred.resolve();
-                        },
-                        function fail() {
-                            deferred.reject();
-                        }
-                    );
-                }
-            },
-            function fail() {
-                deferred.reject();
-            }
-        );
-        return deferred.promise;
-    }
-
-    function validateImportedRule(rule) {
-        if (angular.isUndefined(rule.name)
-            || angular.isUndefined(rule.pluginToken)
-            || angular.isUndefined(rule.filters)
-            || angular.isUndefined(rule.action))
-        {
-            return false;
-        }
-        return true;
-    }
-
     // Rule chain functions
 
     function exportRuleChain(ruleChainId) {
@@ -361,65 +300,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         return true;
     }
 
-    // Plugin functions
-
-    function exportPlugin(pluginId) {
-        pluginService.getPlugin(pluginId).then(
-            function success(plugin) {
-                if (!plugin.configuration || plugin.configuration === null) {
-                    plugin.configuration = {};
-                }
-                var name = plugin.name;
-                name = name.toLowerCase().replace(/\W/g,"_");
-                exportToPc(prepareExport(plugin), name + '.json');
-            },
-            function fail(rejection) {
-                var message = rejection;
-                if (!message) {
-                    message = $translate.instant('error.unknown-error');
-                }
-                toast.showError($translate.instant('plugin.export-failed-error', {error: message}));
-            }
-        );
-    }
-
-    function importPlugin($event) {
-        var deferred = $q.defer();
-        openImportDialog($event, 'plugin.import', 'plugin.plugin-file').then(
-            function success(plugin) {
-                if (!validateImportedPlugin(plugin)) {
-                    toast.showError($translate.instant('plugin.invalid-plugin-file-error'));
-                    deferred.reject();
-                } else {
-                    plugin.state = 'SUSPENDED';
-                    pluginService.savePlugin(plugin).then(
-                        function success() {
-                            deferred.resolve();
-                        },
-                        function fail() {
-                            deferred.reject();
-                        }
-                    );
-                }
-            },
-            function fail() {
-                deferred.reject();
-            }
-        );
-        return deferred.promise;
-    }
-
-    function validateImportedPlugin(plugin) {
-        if (angular.isUndefined(plugin.name)
-            || angular.isUndefined(plugin.clazz)
-            || angular.isUndefined(plugin.apiToken)
-            || angular.isUndefined(plugin.configuration))
-        {
-            return false;
-        }
-        return true;
-    }
-
     // Widget functions
 
     function exportWidget(dashboard, sourceState, sourceLayout, widget) {
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 6ec7ef7..8f2958d 100644
--- a/ui/src/app/layout/index.js
+++ b/ui/src/app/layout/index.js
@@ -50,8 +50,6 @@ import thingsboardAsset from '../asset';
 import thingsboardDevice from '../device';
 import thingsboardWidgetLibrary from '../widget';
 import thingsboardDashboard from '../dashboard';
-import thingsboardPlugin from '../plugin';
-import thingsboardRule from '../rule';
 import thingsboardRuleChain from '../rulechain';
 
 import thingsboardJsonForm from '../jsonform';
@@ -83,8 +81,6 @@ export default angular.module('thingsboard.home', [
     thingsboardDevice,
     thingsboardWidgetLibrary,
     thingsboardDashboard,
-    thingsboardPlugin,
-    thingsboardRule,
     thingsboardRuleChain,
     thingsboardJsonForm,
     thingsboardApiDevice,
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index fc161e7..2a5eab4 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -886,8 +886,10 @@ export default angular.module('thingsboard.locale', [])
                     "response-timeout": "Response timeout in milliseconds",
                     "topic-expression": "Topic expression",
                     "client-scope": "Client scope",
+                    "add-device": "Add device",
                     "opc-server": "Servers",
                     "opc-add-server": "Add server",
+                    "opc-add-server-prompt": "Please add server",
                     "opc-application-name": "Application name",
                     "opc-application-uri": "Application uri",
                     "opc-scan-period-in-seconds": "Scan period in seconds",
@@ -902,6 +904,34 @@ export default angular.module('thingsboard.locale', [])
                     "opc-keystore-key-password":"Key password",
                     "opc-device-node-pattern":"Device node pattern",
                     "opc-device-name-pattern":"Device name pattern",
+                    "modbus-server": "Servers/slaves",
+                    "modbus-add-server": "Add server/slave",
+                    "modbus-add-server-prompt": "Please add server/slave",
+                    "modbus-transport": "Transport",
+                    "modbus-port-name": "Serial port name",
+                    "modbus-encoding": "Encoding",
+                    "modbus-parity": "Parity",
+                    "modbus-baudrate": "Baud rate",
+                    "modbus-databits": "Data bits",
+                    "modbus-stopbits": "Stop bits",
+                    "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
+                    "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
+                    "modbus-unit-id": "Unit ID",
+                    "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
+                    "modbus-device-name":"Device name",
+                    "modbus-poll-period": "Poll period (ms)",
+                    "modbus-attributes-poll-period": "Attributes poll period (ms)",
+                    "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
+                    "modbus-poll-period-range": "Poll period should be positive value.",
+                    "modbus-tag": "Tag",
+                    "modbus-function": "Function",
+                    "modbus-register-address": "Register address",
+                    "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
+                    "modbus-register-bit-index": "Bit index",
+                    "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
+                    "modbus-register-count": "Register count",
+                    "modbus-register-count-range": "Register count should be a positive value.",
+                    "modbus-byte-order": "Byte order",
 
                     "sync": {
                         "status": "Status",
@@ -1163,11 +1193,15 @@ export default angular.module('thingsboard.locale', [])
                 "rulechain": {
                     "rulechain": "Rule chain",
                     "rulechains": "Rule chains",
+                    "root": "Root",
                     "delete": "Delete rule chain",
                     "name": "Name",
                     "name-required": "Name is required.",
                     "description": "Description",
                     "add": "Add Rule Chain",
+                    "set-root": "Make rule chain root",
+                    "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
+                    "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
                     "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
                     "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
                     "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index 130d5ae..875bc5d 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -598,9 +598,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         edgeDoubleClick: function (event, edge) {
             openLinkDetails(edge);
         },
+        edgeEdit: function(event, edge) {
+            openLinkDetails(edge);
+        },
         nodeCallbacks: {
             'doubleClick': function (event, node) {
                 openNodeDetails(node);
+            },
+            'nodeEdit': function (event, node) {
+                openNodeDetails(node);
             }
         },
         isValidEdge: function (source, destination) {
diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js
index b23cd98..8d19229 100644
--- a/ui/src/app/rulechain/rulechain.directive.js
+++ b/ui/src/app/rulechain/rulechain.directive.js
@@ -40,6 +40,7 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, 
             isEdit: '=',
             isReadOnly: '=',
             theForm: '=',
+            onSetRootRuleChain: '&',
             onExportRuleChain: '&',
             onDeleteRuleChain: '&'
         }
diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js
index 2aefd82..f649f53 100644
--- a/ui/src/app/rulechain/rulechain.routes.js
+++ b/ui/src/app/rulechain/rulechain.routes.js
@@ -81,7 +81,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
                 pageTitle: 'rulechain.rulechain'
             },
             ncyBreadcrumb: {
-                label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
+                label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name + (vm.ruleChain.root ? (\' (\' + (\'rulechain.root\' | translate) + \')\') : \'\') }}", "translate": "false"}'
             }
     }).state('home.ruleChains.importRuleChain', {
         url: '/ruleChain/import',
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index a4a708c..252ab3c 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -223,6 +223,7 @@
 .fc-node {
   z-index: 1;
   outline: none;
+  border-radius: 8px;
   &.fc-dragging {
     z-index: 10;
   }
@@ -239,6 +240,7 @@
     bottom: 0;
     background-color: #000;
     opacity: 0;
+    border-radius: 5px;
   }
   &.fc-hover {
     .fc-node-overlay {
@@ -250,6 +252,12 @@
       opacity: 0.25;
     }
   }
+  &.fc-selected {
+    &:not(.fc-edit) {
+      border: solid 3px red;
+      margin: -3px;
+    }
+  }
 }
 
 .fc-leftConnectors, .fc-rightConnectors {
@@ -340,27 +348,43 @@
 
 .fc-nodedelete {
   display: none;
+  font-size: 18px;
 }
 
-.fc-selected .fc-nodedelete {
-  outline: none;
-  display: block;
-  position: absolute;
-  right: -13px;
-  top: -16px;
-  border: solid 2px white;
-  border-radius: 50%;
-  font-weight: 600;
-  font-size: 18px;
-  line-height: 18px;
-  height: 20px;
-  padding-top: 2px;
-  width: 22px;
-  background: #494949;
-  color: #fff;
-  text-align: center;
-  vertical-align: bottom;
-  cursor: pointer;
+.fc-nodeedit {
+  display: none;
+  font-size: 15px;
+}
+
+.fc-edit {
+  .fc-nodedelete, .fc-nodeedit {
+    outline: none;
+    display: block;
+    position: absolute;
+    border: solid 2px white;
+    border-radius: 50%;
+    font-weight: 600;
+    line-height: 20px;
+    height: 20px;
+    padding-top: 2px;
+    width: 22px;
+    background: #f83e05;
+    color: #fff;
+    text-align: center;
+    vertical-align: bottom;
+    cursor: pointer;
+  }
+
+  .fc-nodeedit {
+    top: -24px;
+    right: 16px;
+  }
+
+  .fc-nodedelete {
+    top: -24px;
+    right: -13px;
+  }
+
 }
 
 .fc-noselect {
@@ -376,7 +400,7 @@
 .fc-edge-label {
   position: absolute;
   transition: transform .2s;
-  opacity: 0.8;
+//  opacity: 0.8;
   &.ng-leave {
     transition: 0s none;
   }
@@ -387,13 +411,19 @@
     .fc-edge-label-text {
       span {
         border: solid red;
-        color: red;
+        color: #fff;
+        font-weight: 600;
+        background-color: red;
       }
     }
   }
+  .fc-nodeedit {
+    top: -30px;
+    right: 14px;
+  }
   .fc-nodedelete {
-    right: -13px;
     top: -30px;
+    right: -13px;
   }
   &:focus {
     outline: 0;
diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html
index 48a572c..a3ebfff 100644
--- a/ui/src/app/rulechain/rulechain-card.tpl.html
+++ b/ui/src/app/rulechain/rulechain-card.tpl.html
@@ -15,4 +15,4 @@
     limitations under the License.
 
 -->
-<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>rulechain.system</div>
+<div ng-if="item && item.root" translate>rulechain.root</div>
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
index 2189daa..9f786ab 100644
--- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
@@ -18,8 +18,11 @@
 <md-button ng-click="onExportRuleChain({event: $event})"
            ng-show="!isEdit"
            class="md-raised md-primary">{{ 'rulechain.export' | translate }}</md-button>
+<md-button ng-click="onSetRootRuleChain({event: $event})"
+           ng-show="!isEdit && !ruleChain.root"
+           class="md-raised md-primary">{{ 'rulechain.set-root' | translate }}</md-button>
 <md-button ng-click="onDeleteRuleChain({event: $event})"
-           ng-show="!isEdit && !isReadOnly"
+           ng-show="!isEdit && !ruleChain.root"
            class="md-raised md-primary">{{ 'rulechain.delete' | translate }}</md-button>
 
 <div layout="row">
diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js
index 7c857f2..3da5a51 100644
--- a/ui/src/app/rulechain/rulechains.controller.js
+++ b/ui/src/app/rulechain/rulechains.controller.js
@@ -21,7 +21,8 @@ import ruleChainCard from './rulechain-card.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function RuleChainsController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
+export default function RuleChainsController(ruleChainService, userService, importExport, $state,
+                                             $stateParams, $filter, $translate, $mdDialog, types) {
 
     var ruleChainActionsList = [
         {
@@ -42,12 +43,21 @@ export default function RuleChainsController(ruleChainService, userService, impo
         },
         {
             onAction: function ($event, item) {
+                setRootRuleChain($event, item);
+            },
+            name: function() { return $translate.instant('rulechain.set-root') },
+            details: function() { return $translate.instant('rulechain.set-root') },
+            icon: "flag",
+            isEnabled: isNonRootRuleChain
+        },
+        {
+            onAction: function ($event, item) {
                 vm.grid.deleteItem($event, item);
             },
             name: function() { return $translate.instant('action.delete') },
             details: function() { return $translate.instant('rulechain.delete') },
             icon: "delete",
-            isEnabled: isRuleChainEditable
+            isEnabled: isNonRootRuleChain
         }
     ];
 
@@ -107,10 +117,7 @@ export default function RuleChainsController(ruleChainService, userService, impo
         addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') },
         noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') },
         itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') },
-        isSelectionEnabled: isRuleChainEditable,
-        isDetailsReadOnly: function(ruleChain) {
-            return !isRuleChainEditable(ruleChain);
-        }
+        isSelectionEnabled: isNonRootRuleChain
     };
 
     if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
@@ -121,9 +128,11 @@ export default function RuleChainsController(ruleChainService, userService, impo
         vm.ruleChainGridConfig.topIndex = $stateParams.topIndex;
     }
 
-    vm.isRuleChainEditable = isRuleChainEditable;
+    vm.isRootRuleChain = isRootRuleChain;
+    vm.isNonRootRuleChain = isNonRootRuleChain;
 
     vm.exportRuleChain = exportRuleChain;
+    vm.setRootRuleChain = setRootRuleChain;
 
     function deleteRuleChainTitle(ruleChain) {
         return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name});
@@ -172,12 +181,12 @@ export default function RuleChainsController(ruleChainService, userService, impo
         return ruleChain ? ruleChain.name : '';
     }
 
-    function isRuleChainEditable(ruleChain) {
-        if (userService.getAuthority() === 'TENANT_ADMIN') {
-            return ruleChain && ruleChain.tenantId.id != types.id.nullUid;
-        } else {
-            return userService.getAuthority() === 'SYS_ADMIN';
-        }
+    function isRootRuleChain(ruleChain) {
+        return ruleChain && ruleChain.root;
+    }
+
+    function isNonRootRuleChain(ruleChain) {
+        return ruleChain && !ruleChain.root;
     }
 
     function exportRuleChain($event, ruleChain) {
@@ -185,4 +194,22 @@ export default function RuleChainsController(ruleChainService, userService, impo
         importExport.exportRuleChain(ruleChain.id.id);
     }
 
+    function setRootRuleChain($event, ruleChain) {
+        $event.stopPropagation();
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('rulechain.set-root-rulechain-title', {ruleChainName: ruleChain.name}))
+            .htmlContent($translate.instant('rulechain.set-root-rulechain-text'))
+            .ariaLabel($translate.instant('rulechain.set-root'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            ruleChainService.setRootRuleChain(ruleChain.id.id).then(
+                () => {
+                    vm.grid.refreshList();
+                }
+            );
+        });
+
+    }
 }
diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html
index cf9d256..c780b46 100644
--- a/ui/src/app/rulechain/rulechains.tpl.html
+++ b/ui/src/app/rulechain/rulechains.tpl.html
@@ -26,8 +26,10 @@
                      is-edit="vm.grid.detailsConfig.isDetailsEditMode"
                      is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
                      the-form="vm.grid.detailsForm"
+                     on-set-root-rule-chain="vm.setRootRuleChain(event, vm.grid.detailsConfig.currentItem)"
                      on-export-rule-chain="vm.exportRuleChain(event, vm.grid.detailsConfig.currentItem)"
-                     on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule-chain>
+                     on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)">
+            </tb-rule-chain>
         </md-tab>
         <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex
diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html
index d52d8e1..6a82a7f 100644
--- a/ui/src/app/rulechain/rulenode.tpl.html
+++ b/ui/src/app/rulechain/rulenode.tpl.html
@@ -45,6 +45,9 @@
             </div>
         </div>
     </div>
+    <div ng-if="modelservice.isEditable() && !node.readonly" class="fc-nodeedit" ng-click="callbacks.nodeEdit($event, node)">
+        <i class="fa fa-pencil" aria-hidden="true"></i>
+    </div>
     <div ng-if="modelservice.isEditable() && !node.readonly" class="fc-nodedelete" ng-click="modelservice.nodes.delete(node)">
         &times;
     </div>
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js
index 5d97ea6..1c33d1f 100644
--- a/ui/src/app/services/menu.service.js
+++ b/ui/src/app/services/menu.service.js
@@ -67,24 +67,6 @@ function Menu(userService, $state, $rootScope) {
                             icon: 'home'
                         },
                         {
-                            name: 'plugin.plugins',
-                            type: 'link',
-                            state: 'home.plugins',
-                            icon: 'extension'
-                        },
-                        {
-                            name: 'rule.rules',
-                            type: 'link',
-                            state: 'home.rules',
-                            icon: 'settings_ethernet'
-                        },
-                        {
-                            name: 'rulechain.rulechains',
-                            type: 'link',
-                            state: 'home.ruleChains',
-                            icon: 'settings_ethernet'
-                        },
-                        {
                             name: 'tenant.tenants',
                             type: 'link',
                             state: 'home.tenants',
@@ -119,31 +101,6 @@ function Menu(userService, $state, $rootScope) {
                         }];
                     homeSections =
                         [{
-                            name: 'rule-plugin.management',
-                            places: [
-                                {
-                                    name: 'plugin.plugins',
-                                    icon: 'extension',
-                                    state: 'home.plugins'
-                                },
-                                {
-                                    name: 'rule.rules',
-                                    icon: 'settings_ethernet',
-                                    state: 'home.rules'
-                                }
-                            ]
-                        },
-                        {
-                            name: 'rulechain.management',
-                            places: [
-                                {
-                                    name: 'rulechain.rulechains',
-                                    icon: 'settings_ethernet',
-                                    state: 'home.ruleChains'
-                                }
-                            ]
-                        },
-                        {
                             name: 'tenant.management',
                             places: [
                                 {
@@ -187,18 +144,6 @@ function Menu(userService, $state, $rootScope) {
                             icon: 'home'
                         },
                         {
-                            name: 'plugin.plugins',
-                            type: 'link',
-                            state: 'home.plugins',
-                            icon: 'extension'
-                        },
-                        {
-                            name: 'rule.rules',
-                            type: 'link',
-                            state: 'home.rules',
-                            icon: 'settings_ethernet'
-                        },
-                        {
                             name: 'rulechain.rulechains',
                             type: 'link',
                             state: 'home.ruleChains',
@@ -243,21 +188,6 @@ function Menu(userService, $state, $rootScope) {
 
                     homeSections =
                         [{
-                            name: 'rule-plugin.management',
-                            places: [
-                                {
-                                    name: 'plugin.plugins',
-                                    icon: 'extension',
-                                    state: 'home.plugins'
-                                },
-                                {
-                                    name: 'rule.rules',
-                                    icon: 'settings_ethernet',
-                                    state: 'home.rules'
-                                }
-                            ]
-                        },
-                        {
                             name: 'rulechain.management',
                             places: [
                                 {
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index 7ce5987..1f363d6 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -251,7 +251,7 @@ export default class TbFlot {
                 if (this.tickUnits) {
                      formatted += ' ' + this.tickUnits;
                 }
-             
+
                 return formatted;
             };
 
@@ -1097,7 +1097,7 @@ export default class TbFlot {
                     "axisTickDecimals": {
                         "title": "Axis tick number of digits after floating point",
                         "type": "number",
-                        "default": 0
+                        "default": null
                     },
                     "axisTickSize": {
                         "title": "Axis step size between ticks",