diff --git a/application/src/main/data/upgrade/2.4.x/schema_update.sql b/application/src/main/data/upgrade/2.4.x/schema_update.sql index 7cc234e47b..15d2994360 100644 --- a/application/src/main/data/upgrade/2.4.x/schema_update.sql +++ b/application/src/main/data/upgrade/2.4.x/schema_update.sql @@ -18,8 +18,13 @@ CREATE TABLE IF NOT EXISTS edge ( id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY, additional_info varchar, customer_id varchar(31), + root_rule_chain_id varchar(31), configuration varchar(10000000), + type varchar(255), name varchar(255), + label varchar(255), + routing_key varchar(255), + secret varchar(255), search_text varchar(255), tenant_id varchar(31) ); 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 d8468f6765..9a4de3bd55 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 @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; @@ -97,17 +98,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(tenantId, entityId); - log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); - // Creating and starting the actors; - for (RuleNode ruleNode : ruleNodeList) { - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) { + ruleChainName = ruleChain.getName(); + List ruleNodeList = service.getRuleChainNodes(tenantId, entityId); + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); + // Creating and starting the actors; + for (RuleNode ruleNode : ruleNodeList) { + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); + } + initRoutes(ruleChain, ruleNodeList); + started = true; } - initRoutes(ruleChain, ruleNodeList); - started = true; } } else { onUpdate(context); @@ -118,31 +121,35 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(tenantId, entityId); - log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); - for (RuleNode ruleNode : ruleNodeList) { - RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); - if (existing == null) { - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); - } else { - log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); - existing.setSelf(ruleNode); - existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) { + ruleChainName = ruleChain.getName(); + List ruleNodeList = service.getRuleChainNodes(tenantId, entityId); + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); + for (RuleNode ruleNode : ruleNodeList) { + RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); + if (existing == null) { + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); + } else { + log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); + existing.setSelf(ruleNode); + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); + } } - } - Set existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); - List removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); - removedRules.forEach(ruleNodeId -> { - log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); - RuleNodeCtx removed = nodeActors.remove(ruleNodeId); - removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); - }); + Set existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); + List removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); + removedRules.forEach(ruleNodeId -> { + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); + RuleNodeCtx removed = nodeActors.remove(ruleNodeId); + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); + }); - initRoutes(ruleChain, ruleNodeList); + initRoutes(ruleChain, ruleNodeList); + } else if (ruleChain.getType().equals(RuleChainType.EDGE)){ + stop(context); + } } } 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 010fd58243..50ce018166 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 @@ -139,7 +139,7 @@ public class TenantActor extends RuleChainManagerActor { RuleChain ruleChain = null; if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { ruleChain = systemContext.getRuleChainService().findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId())); - if (RuleChainType.SYSTEM.equals(ruleChain.getType())) { + if (ruleChain !=null && !RuleChainType.SYSTEM.equals(ruleChain.getType())) { log.debug("[{}] Non SYSTEM rule chains are ignored and not started. Current rule chain type [{}]", tenantId, ruleChain.getType()); return; } diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 32018f63d1..7bc78750dd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.edge.Edge; @@ -37,9 +38,12 @@ import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.model.SecurityUser; @@ -74,7 +78,8 @@ public class EdgeController extends BaseController { @ResponseBody public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException { try { - edge.setTenantId(getCurrentUser().getTenantId()); + TenantId tenantId = getCurrentUser().getTenantId(); + edge.setTenantId(tenantId); boolean created = edge.getId() == null; Operation operation = created ? Operation.CREATE : Operation.WRITE; @@ -84,6 +89,12 @@ public class EdgeController extends BaseController { Edge result = checkNotNull(edgeService.saveEdge(edge)); + if (created) { + RuleChain rootTenantRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); + ruleChainService.assignRuleChainToEdge(tenantId, rootTenantRuleChain.getId(), result.getId()); + edgeService.setRootRuleChain(tenantId, result, rootTenantRuleChain.getId()); + } + logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null); return result; } catch (Exception e) { @@ -250,6 +261,36 @@ public class EdgeController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST) + @ResponseBody + public Edge setRootRuleChain(@PathVariable(EDGE_ID) String strEdgeId, + @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException { + checkParameter(EDGE_ID, strEdgeId); + checkParameter("ruleChainId", strRuleChainId); + try { + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.WRITE); + + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.WRITE); + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE, + edge.getId(), edge); + + Edge updatedEdge = edgeService.setRootRuleChain(getTenantId(), edge, ruleChainId); + + logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null); + + return updatedEdge; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.EDGE), + null, + null, + ActionType.UPDATED, e, strEdgeId); + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/edges", params = {"limit"}, method = RequestMethod.GET) @ResponseBody 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 57ab0ae642..10df2fc09c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.security.permission.Operation; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index 46c9f0ddd6..a649425566 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -18,10 +18,12 @@ package org.thingsboard.server.dao.edge; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; @@ -73,6 +75,8 @@ public interface EdgeService { void pushEventToEdge(TenantId tenantId, TbMsg tbMsg); TimePageData findQueueEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink); + + Edge setRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index c3447e8361..4b4efad2f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -124,7 +124,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa } public boolean isAssignedToEdge(EdgeId edgeId) { - return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null)); + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null)); } public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortEdgeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortEdgeInfo.java index 5a28264d64..44c3ad941c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ShortEdgeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ShortEdgeInfo.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; @AllArgsConstructor public class ShortEdgeInfo { @@ -29,6 +30,9 @@ public class ShortEdgeInfo { @Getter @Setter private String title; + @Getter @Setter + private RuleChainId rootRuleChainId; + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 93198dcb50..d30be5a269 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; @EqualsAndHashCode(callSuper = true) @@ -41,6 +42,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo implements H private TenantId tenantId; private CustomerId customerId; + private RuleChainId rootRuleChainId; private String name; private String type; private String label; @@ -60,6 +62,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo implements H super(edge); this.tenantId = edge.getTenantId(); this.customerId = edge.getCustomerId(); + this.rootRuleChainId = edge.getRootRuleChainId(); this.type = edge.getType(); this.name = edge.getName(); this.routingKey = edge.getRoutingKey(); @@ -69,7 +72,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo implements H @JsonIgnore public ShortEdgeInfo toShortEdgeInfo() { - return new ShortEdgeInfo(id, name); + return new ShortEdgeInfo(id, name, rootRuleChainId); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index 92295a6363..d1c6295c81 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -94,7 +94,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im } public boolean isAssignedToEdge(EdgeId edgeId) { - return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null)); + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null)); } public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java index c9de9fab67..885f44221d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; @@ -472,6 +473,14 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic return eventService.findEvents(tenantId, edgeId, DataConstants.EDGE_QUEUE_EVENT_TYPE, pageLink); } + @Override + public Edge setRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) { + edge.setRootRuleChainId(ruleChainId); + Edge saveEdge = saveEdge(edge); + ruleChainService.updateEdgeRuleChains(tenantId, saveEdge.getId()); + return saveEdge; + } + private DataValidator edgeValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 089ddaa0c7..d64d620b7c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -359,6 +359,7 @@ public class ModelConstants { public static final String EDGE_COLUMN_FAMILY_NAME = "edge"; public static final String EDGE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String EDGE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; + public static final String EDGE_ROOT_RULE_CHAIN_ID_PROPERTY = "root_rule_chain_id"; public static final String EDGE_NAME_PROPERTY = "name"; public static final String EDGE_LABEL_PROPERTY = "label"; public static final String EDGE_TYPE_PROPERTY = "type"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java index 7d1deff8b6..c810e30ecd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java @@ -25,6 +25,7 @@ import lombok.Data; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.model.type.JsonCodec; @@ -37,6 +38,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY; @@ -60,6 +62,9 @@ public class EdgeEntity implements SearchTextEntity { @Column(name = EDGE_CUSTOMER_ID_PROPERTY) private UUID customerId; + @Column(name = EDGE_ROOT_RULE_CHAIN_ID_PROPERTY) + private UUID rootRuleChainId; + @Column(name = EDGE_TYPE_PROPERTY) private String type; @@ -95,6 +100,12 @@ public class EdgeEntity implements SearchTextEntity { if (edge.getTenantId() != null) { this.tenantId = edge.getTenantId().getId(); } + if (edge.getCustomerId() != null) { + this.customerId = edge.getCustomerId().getId(); + } + if (edge.getRootRuleChainId() != null) { + this.rootRuleChainId = edge.getRootRuleChainId().getId(); + } this.type = edge.getType(); this.name = edge.getName(); this.label = edge.getLabel(); @@ -119,6 +130,9 @@ public class EdgeEntity implements SearchTextEntity { if (customerId != null) { edge.setCustomerId(new CustomerId(customerId)); } + if (rootRuleChainId != null) { + edge.setRootRuleChainId(new RuleChainId(rootRuleChainId)); + } edge.setType(type); edge.setName(name); edge.setLabel(label); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java index 8f1d37d1c3..a9ab96ef71 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -39,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY; @@ -58,6 +60,9 @@ public class EdgeEntity extends BaseSqlEntity implements SearchTextEntity< @Column(name = EDGE_CUSTOMER_ID_PROPERTY) private String customerId; + @Column(name = EDGE_ROOT_RULE_CHAIN_ID_PROPERTY) + private String rootRuleChainId; + @Column(name = EDGE_TYPE_PROPERTY) private String type; @@ -98,6 +103,9 @@ public class EdgeEntity extends BaseSqlEntity implements SearchTextEntity< if (edge.getCustomerId() != null) { this.customerId = UUIDConverter.fromTimeUUID(edge.getCustomerId().getId()); } + if (edge.getRootRuleChainId() != null) { + this.rootRuleChainId = UUIDConverter.fromTimeUUID(edge.getRootRuleChainId().getId()); + } this.type = edge.getType(); this.name = edge.getName(); this.label = edge.getLabel(); @@ -131,6 +139,9 @@ public class EdgeEntity extends BaseSqlEntity implements SearchTextEntity< if (customerId != null) { edge.setCustomerId(new CustomerId(UUIDConverter.fromString(customerId))); } + if (rootRuleChainId != null) { + edge.setRootRuleChainId(new RuleChainId(UUIDConverter.fromString(rootRuleChainId))); + } edge.setType(type); edge.setName(name); edge.setLabel(label); 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 2da4203339..b42445fae3 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 @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EdgeId; @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.edge.EdgeService; @@ -116,6 +118,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); ruleChain.setRoot(true); + ruleChain.setType(RuleChainType.SYSTEM); ruleChainDao.save(tenantId, ruleChain); return true; } catch (ExecutionException | InterruptedException e) { @@ -359,8 +362,17 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); - if (ruleChain != null && ruleChain.isRoot()) { - throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!"); + if (ruleChain != null) { + if (ruleChain.isRoot()) { + throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!"); + } + if (ruleChain.getAssignedEdges() != null && !ruleChain.getAssignedEdges().isEmpty()) { + for (ShortEdgeInfo assignedEdge : ruleChain.getAssignedEdges()) { + if (assignedEdge.getRootRuleChainId() != null && assignedEdge.getRootRuleChainId().equals(ruleChainId)) { + throw new DataValidationException("Can't delete rule chain that is root for edge [" + assignedEdge.getTitle() + "]. Please assign another root rule chain first to the edge!"); + } + } + } } checkRuleNodesAndDelete(tenantId, ruleChainId); } @@ -398,13 +410,16 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC RuleChain ruleChain = findRuleChainById(tenantId, ruleChainId); Edge edge = edgeDao.findById(tenantId, edgeId.getId()); if (edge == null) { - throw new DataValidationException("Can't unassign ruleChain from non-existent edge!"); + throw new DataValidationException("Can't unassign rule chain from non-existent edge!"); + } + if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) { + throw new DataValidationException("Can't unassign root rule chain from edge [" + edge.getName() + "]. Please assign another root rule chain first!"); } if (ruleChain.removeAssignedEdge(edge)) { try { deleteRelation(tenantId, new EntityRelation(edgeId, ruleChainId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE)); } catch (ExecutionException | InterruptedException e) { - log.warn("[{}] Failed to delete ruleChain relation. Edge Id: [{}]", ruleChainId, edgeId); + log.warn("[{}] Failed to delete rule chain relation. Edge Id: [{}]", ruleChainId, edgeId); throw new RuntimeException(e); } return saveRuleChain(ruleChain); @@ -442,13 +457,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); Validator.validateId(edgeId, "Incorrect customerId " + edgeId); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - ListenableFuture> dashboards = ruleChainDao.findRuleChainsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); + ListenableFuture> ruleChains = ruleChainDao.findRuleChainsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); - return Futures.transform(dashboards, new Function, TimePageData>() { + return Futures.transform(ruleChains, new Function, TimePageData>() { @Nullable @Override - public TimePageData apply(@Nullable List RuleChain) { - return new TimePageData<>(RuleChain, pageLink); + public TimePageData apply(@Nullable List ruleChain) { + return new TimePageData<>(ruleChain, pageLink); } }); } 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 fd319c480e..7c0747284a 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 @@ -47,5 +47,4 @@ public interface RuleChainDao extends Dao { * @return the list of rule chain objects */ ListenableFuture> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink); - } 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 f30063943c..3cf7bdb860 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 @@ -88,5 +88,4 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao vm.ruleChains.data.length) { + vm.theRuleChains.fetchMoreItems_(index); + return null; + } + var item = vm.ruleChains.data[index]; + if (item) { + item.indexNumber = index + 1; + } + return item; + }, + + getLength: function () { + if (vm.ruleChains.hasNext) { + return vm.ruleChains.data.length + vm.ruleChains.nextPageLink.limit; + } else { + return vm.ruleChains.data.length; + } + }, + + fetchMoreItems_: function () { + if (vm.ruleChains.hasNext && !vm.ruleChains.pending) { + vm.ruleChains.pending = true; + ruleChainService.getRuleChains(vm.ruleChains.nextPageLink).then( + function success(ruleChains) { + vm.ruleChains.data = vm.ruleChains.data.concat(ruleChains.data); + vm.ruleChains.nextPageLink = ruleChains.nextPageLink; + vm.ruleChains.hasNext = ruleChains.hasNext; + if (vm.ruleChains.hasNext) { + vm.ruleChains.nextPageLink.limit = vm.ruleChains.pageSize; + } + vm.ruleChains.pending = false; + }, + function fail() { + vm.ruleChains.hasNext = false; + vm.ruleChains.pending = false; + }); + } + } + }; + + function cancel() { + $mdDialog.cancel(); + } + + function assign() { + var assignTasks = []; + for (var i=0;i 0; + } + + function toggleRuleChainSelection($event, ruleChain) { + $event.stopPropagation(); + if (vm.isRuleChainSelected(ruleChain)) { + vm.ruleChains.selection = null; + } else { + vm.ruleChains.selection = ruleChain; + } + } + + function isRuleChainSelected(ruleChain) { + return vm.ruleChains.selection != null && ruleChain && + ruleChain.id.id === vm.ruleChains.selection.id.id; + } + + function searchRuleChainTextUpdated() { + vm.ruleChains = { + pageSize: vm.ruleChains.pageSize, + data: [], + nextPageLink: { + limit: vm.ruleChains.pageSize, + textSearch: vm.searchText + }, + selection: null, + hasNext: true, + pending: false + }; + } +} diff --git a/ui/src/app/edge/set-root-rule-chain-to-edges.tpl.html b/ui/src/app/edge/set-root-rule-chain-to-edges.tpl.html new file mode 100644 index 0000000000..7dda9dd267 --- /dev/null +++ b/ui/src/app/edge/set-root-rule-chain-to-edges.tpl.html @@ -0,0 +1,76 @@ + + +
+ +
+

edge.set-root-rule-chain-to-edges

+ + + + +
+
+ + + +
+
+ edge.set-root-rule-chain-text + + + + search + + + +
+ rulechain.no-rulechains-text + + + + + {{ ruleChain.name }} + + + +
+
+
+
+ + + + {{ 'action.assign' | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 629a151e03..79d53a3af1 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -792,6 +792,7 @@ "dashboards": "Edge Dashboards", "manage-edge-rulechains": "Manage edge rule chains", "rulechains": "Edge Rule Chains", + "rulechain": "Edge Rule Chain", "edge-key": "Edge key", "copy-edge-key": "Copy edge key", "edge-key-copied-message": "Edge key has been copied to clipboard", @@ -803,7 +804,10 @@ "manage-edge-entity-views": "Manage edge entity views", "assets": "Edge assets", "devices": "Edge devices", - "entity-views": "Edge entity views" + "entity-views": "Edge entity views", + "set-root-rule-chain-text": "Please select root rule chain for edge(s)", + "set-root-rule-chain-to-edges": "Set root rule chain for Edge(s)", + "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }" }, "error": { "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", diff --git a/ui/src/app/rulechain/add-rulechain.tpl.html b/ui/src/app/rulechain/add-rulechain.tpl.html index afa765fc43..f27b8a8c9c 100644 --- a/ui/src/app/rulechain/add-rulechain.tpl.html +++ b/ui/src/app/rulechain/add-rulechain.tpl.html @@ -31,7 +31,7 @@
- +
diff --git a/ui/src/app/rulechain/add-rulechains-to-edge.controller.js b/ui/src/app/rulechain/add-rulechains-to-edge.controller.js index 5bc6e400ea..f05e62cd0a 100644 --- a/ui/src/app/rulechain/add-rulechains-to-edge.controller.js +++ b/ui/src/app/rulechain/add-rulechains-to-edge.controller.js @@ -54,7 +54,7 @@ export default function AddRuleChainsToEdgeController(ruleChainService, $mdDialo vm.ruleChains.pending = true; ruleChainService.getRuleChains(vm.ruleChains.nextPageLink).then( function success(ruleChains) { - vm.ruleChains.data = vm.ruleChains.data.concat(filterNonRootRuleChains(ruleChains.data)); + vm.ruleChains.data = ruleChains.data; vm.ruleChains.nextPageLink = ruleChains.nextPageLink; vm.ruleChains.hasNext = ruleChains.hasNext; if (vm.ruleChains.hasNext) { @@ -119,12 +119,4 @@ export default function AddRuleChainsToEdgeController(ruleChainService, $mdDialo pending: false }; } - - function filterNonRootRuleChains(ruleChains) { - return $filter('filter')(ruleChains, isNonRootRuleChain); - } - - function isNonRootRuleChain(ruleChain) { - return ruleChain && !ruleChain.root; - } } \ No newline at end of file diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html index 8233f21912..966594af31 100644 --- a/ui/src/app/rulechain/rulechain-card.tpl.html +++ b/ui/src/app/rulechain/rulechain-card.tpl.html @@ -16,4 +16,4 @@ -->
{{'rulechain.assigned-to-edges' | translate}}: '{{vm.item.assignedEdgesText}}'
-
rulechain.root
+
rulechain.root
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index 941f850d11..69a8089fd1 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -51,7 +51,7 @@ - + {{ruleChainType}} diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js index ccfe9bf6a9..31c2817d99 100644 --- a/ui/src/app/rulechain/rulechain.directive.js +++ b/ui/src/app/rulechain/rulechain.directive.js @@ -49,7 +49,7 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, theForm: '=', onSetRootRuleChain: '&', onExportRuleChain: '&', - onDeleteRuleChain: '&', + onDeleteRuleChain: '&' } }; } diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 8a11c3dde1..2236be2796 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -145,5 +145,43 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}' } + }) + .state('home.edges.ruleChains.ruleChain', { + url: '/:ruleChainId', + reloadOnSearch: false, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainTemplate, + controller: 'RuleChainController', + controllerAs: 'vm' + } + }, + resolve: { + ruleChain: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChain($stateParams.ruleChainId); + }, + ruleChainMetaData: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); + }, + ruleNodeComponents: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleNodeComponents(); + } + }, + data: { + import: false, + searchEnabled: false, + pageTitle: 'edge.rulechain' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "edge.rulechain"}' + } }); } \ No newline at end of file diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index c19f62f6c0..3662b08db5 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -25,7 +25,7 @@ import addRuleChainsToEdgeTemplate from "./add-rulechains-to-edge.tpl.html"; import './rulechain-card.scss'; /*@ngInject*/ -export default function RuleChainsController(ruleChainService, userService, importExport, $state, +export default function RuleChainsController(ruleChainService, userService, edgeService, importExport, $state, $stateParams, $filter, $translate, $mdDialog, $document, $q, types) { var vm = this; @@ -107,6 +107,11 @@ export default function RuleChainsController(ruleChainService, userService, impo if (edgeId) { vm.edgeRuleChainsTitle = $translate.instant('edge.rulechains'); + edgeService.getEdge(edgeId).then( + function success(edge) { + vm.edge = edge; + } + ); } if (vm.ruleChainsScope === 'tenant') { @@ -133,8 +138,7 @@ export default function RuleChainsController(ruleChainService, userService, impo }, name: function() { return $translate.instant('action.assign') }, details: function() { return $translate.instant('rulechain.manage-assigned-edges') }, - icon: "wifi_tethering", - isEnabled: isNonRootRuleChain + icon: "wifi_tethering" }); ruleChainActionsList.push({ @@ -214,6 +218,16 @@ export default function RuleChainsController(ruleChainService, userService, impo return ruleChainService.unassignRuleChainFromEdge(edgeId, ruleChainId); }; + ruleChainActionsList.push({ + 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 + }); + ruleChainActionsList.push( { onAction: function ($event, item) { @@ -221,7 +235,8 @@ export default function RuleChainsController(ruleChainService, userService, impo }, name: function() { return $translate.instant('action.unassign') }, details: function() { return $translate.instant('rulechain.unassign-from-edge') }, - icon: "assignment_return" + icon: "assignment_return", + isEnabled: isNonRootRuleChain } ); @@ -288,7 +303,13 @@ export default function RuleChainsController(ruleChainService, userService, impo if ($event) { $event.stopPropagation(); } - $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id}); + if (vm.ruleChainsScope === 'edge') { + $state.go('home.edges.ruleChains.ruleChain', { + ruleChainId: ruleChain.id.id + }); + } else { + $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id}); + } } function deleteRuleChain(ruleChainId) { @@ -300,11 +321,19 @@ export default function RuleChainsController(ruleChainService, userService, impo } function isRootRuleChain(ruleChain) { - return ruleChain && ruleChain.root; + if (angular.isDefined(vm.edge) && vm.edge != null) { + return angular.isDefined(vm.edge.rootRuleChainId) && vm.edge.rootRuleChainId != null && vm.edge.rootRuleChainId.id === ruleChain.id.id; + } else { + return ruleChain && ruleChain.root; + } } function isNonRootRuleChain(ruleChain) { - return ruleChain && !ruleChain.root; + if (angular.isDefined(vm.edge) && vm.edge != null) { + return angular.isDefined(vm.edge.rootRuleChainId) && vm.edge.rootRuleChainId != null && vm.edge.rootRuleChainId.id !== ruleChain.id.id; + } else { + return ruleChain && !ruleChain.root; + } } function exportRuleChain($event, ruleChain) { @@ -322,11 +351,20 @@ export default function RuleChainsController(ruleChainService, userService, impo .cancel($translate.instant('action.no')) .ok($translate.instant('action.yes')); $mdDialog.show(confirm).then(function () { - ruleChainService.setRootRuleChain(ruleChain.id.id).then( - () => { - vm.grid.refreshList(); - } - ); + if (angular.isDefined(vm.edge) && vm.edge != null) { + edgeService.setRootRuleChain(vm.edge.id.id, ruleChain.id.id).then( + (edge) => { + vm.edge = edge; + vm.grid.refreshList(); + } + ); + } else { + ruleChainService.setRootRuleChain(ruleChain.id.id).then( + () => { + vm.grid.refreshList(); + } + ); + } }); } @@ -396,7 +434,7 @@ export default function RuleChainsController(ruleChainService, userService, impo function success(_ruleChains) { var ruleChains = { pageSize: pageSize, - data: filterNonRootRuleChains(_ruleChains.data), + data: _ruleChains.data, nextPageLink: _ruleChains.nextPageLink, selections: {}, selectedCount: 0, @@ -423,10 +461,6 @@ export default function RuleChainsController(ruleChainService, userService, impo }); } - function filterNonRootRuleChains(ruleChains) { - return $filter('filter')(ruleChains, isNonRootRuleChain); - } - function unassignFromEdge($event, ruleChain, edgeId) { if ($event) { $event.stopPropagation();