Commit efd565d8b607ae384ef925a634d0002d4d79faec

Authored by Volodymyr Babak
1 parent 66af90bf

Added root rule chain for the edge. Part 2

Showing 31 changed files with 542 additions and 87 deletions
... ... @@ -18,8 +18,13 @@ CREATE TABLE IF NOT EXISTS edge (
18 18 id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
19 19 additional_info varchar,
20 20 customer_id varchar(31),
  21 + root_rule_chain_id varchar(31),
21 22 configuration varchar(10000000),
  23 + type varchar(255),
22 24 name varchar(255),
  25 + label varchar(255),
  26 + routing_key varchar(255),
  27 + secret varchar(255),
23 28 search_text varchar(255),
24 29 tenant_id varchar(31)
25 30 );
... ...
... ... @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
39 39 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
40 40 import org.thingsboard.server.common.data.relation.EntityRelation;
41 41 import org.thingsboard.server.common.data.rule.RuleChain;
  42 +import org.thingsboard.server.common.data.rule.RuleChainType;
42 43 import org.thingsboard.server.common.data.rule.RuleNode;
43 44 import org.thingsboard.server.common.msg.TbMsg;
44 45 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
... ... @@ -97,17 +98,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
97 98 if (!started) {
98 99 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
99 100 if (ruleChain != null) {
100   - ruleChainName = ruleChain.getName();
101   - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
102   - log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
103   - // Creating and starting the actors;
104   - for (RuleNode ruleNode : ruleNodeList) {
105   - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
106   - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
107   - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  101 + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
  102 + ruleChainName = ruleChain.getName();
  103 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
  104 + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
  105 + // Creating and starting the actors;
  106 + for (RuleNode ruleNode : ruleNodeList) {
  107 + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  108 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  109 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  110 + }
  111 + initRoutes(ruleChain, ruleNodeList);
  112 + started = true;
108 113 }
109   - initRoutes(ruleChain, ruleNodeList);
110   - started = true;
111 114 }
112 115 } else {
113 116 onUpdate(context);
... ... @@ -118,31 +121,35 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
118 121 public void onUpdate(ActorContext context) {
119 122 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
120 123 if (ruleChain != null) {
121   - ruleChainName = ruleChain.getName();
122   - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
123   - log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
124   - for (RuleNode ruleNode : ruleNodeList) {
125   - RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
126   - if (existing == null) {
127   - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
128   - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
129   - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
130   - } else {
131   - log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
132   - existing.setSelf(ruleNode);
133   - existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
  124 + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
  125 + ruleChainName = ruleChain.getName();
  126 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
  127 + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
  128 + for (RuleNode ruleNode : ruleNodeList) {
  129 + RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
  130 + if (existing == null) {
  131 + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  132 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  133 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  134 + } else {
  135 + log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  136 + existing.setSelf(ruleNode);
  137 + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
  138 + }
134 139 }
135   - }
136 140
137   - Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
138   - List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
139   - removedRules.forEach(ruleNodeId -> {
140   - log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
141   - RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
142   - removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
143   - });
  141 + Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
  142 + List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
  143 + removedRules.forEach(ruleNodeId -> {
  144 + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
  145 + RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
  146 + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
  147 + });
144 148
145   - initRoutes(ruleChain, ruleNodeList);
  149 + initRoutes(ruleChain, ruleNodeList);
  150 + } else if (ruleChain.getType().equals(RuleChainType.EDGE)){
  151 + stop(context);
  152 + }
146 153 }
147 154 }
148 155
... ...
... ... @@ -139,7 +139,7 @@ public class TenantActor extends RuleChainManagerActor {
139 139 RuleChain ruleChain = null;
140 140 if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
141 141 ruleChain = systemContext.getRuleChainService().findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
142   - if (RuleChainType.SYSTEM.equals(ruleChain.getType())) {
  142 + if (ruleChain !=null && !RuleChainType.SYSTEM.equals(ruleChain.getType())) {
143 143 log.debug("[{}] Non SYSTEM rule chains are ignored and not started. Current rule chain type [{}]", tenantId, ruleChain.getType());
144 144 return;
145 145 }
... ...
... ... @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer;
30 30 import org.thingsboard.server.common.data.Device;
31 31 import org.thingsboard.server.common.data.EntitySubtype;
32 32 import org.thingsboard.server.common.data.EntityType;
  33 +import org.thingsboard.server.common.data.Tenant;
33 34 import org.thingsboard.server.common.data.audit.ActionType;
34 35 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
35 36 import org.thingsboard.server.common.data.edge.Edge;
... ... @@ -37,9 +38,12 @@ import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
37 38 import org.thingsboard.server.common.data.exception.ThingsboardException;
38 39 import org.thingsboard.server.common.data.id.CustomerId;
39 40 import org.thingsboard.server.common.data.id.EdgeId;
  41 +import org.thingsboard.server.common.data.id.RuleChainId;
40 42 import org.thingsboard.server.common.data.id.TenantId;
41 43 import org.thingsboard.server.common.data.page.TextPageData;
42 44 import org.thingsboard.server.common.data.page.TextPageLink;
  45 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  46 +import org.thingsboard.server.common.data.rule.RuleChain;
43 47 import org.thingsboard.server.dao.exception.IncorrectParameterException;
44 48 import org.thingsboard.server.dao.model.ModelConstants;
45 49 import org.thingsboard.server.service.security.model.SecurityUser;
... ... @@ -74,7 +78,8 @@ public class EdgeController extends BaseController {
74 78 @ResponseBody
75 79 public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException {
76 80 try {
77   - edge.setTenantId(getCurrentUser().getTenantId());
  81 + TenantId tenantId = getCurrentUser().getTenantId();
  82 + edge.setTenantId(tenantId);
78 83 boolean created = edge.getId() == null;
79 84
80 85 Operation operation = created ? Operation.CREATE : Operation.WRITE;
... ... @@ -84,6 +89,12 @@ public class EdgeController extends BaseController {
84 89
85 90 Edge result = checkNotNull(edgeService.saveEdge(edge));
86 91
  92 + if (created) {
  93 + RuleChain rootTenantRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
  94 + ruleChainService.assignRuleChainToEdge(tenantId, rootTenantRuleChain.getId(), result.getId());
  95 + edgeService.setRootRuleChain(tenantId, result, rootTenantRuleChain.getId());
  96 + }
  97 +
87 98 logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
88 99 return result;
89 100 } catch (Exception e) {
... ... @@ -250,6 +261,36 @@ public class EdgeController extends BaseController {
250 261 }
251 262 }
252 263
  264 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  265 + @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
  266 + @ResponseBody
  267 + public Edge setRootRuleChain(@PathVariable(EDGE_ID) String strEdgeId,
  268 + @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException {
  269 + checkParameter(EDGE_ID, strEdgeId);
  270 + checkParameter("ruleChainId", strRuleChainId);
  271 + try {
  272 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  273 + checkRuleChain(ruleChainId, Operation.WRITE);
  274 +
  275 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  276 + Edge edge = checkEdgeId(edgeId, Operation.WRITE);
  277 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE,
  278 + edge.getId(), edge);
  279 +
  280 + Edge updatedEdge = edgeService.setRootRuleChain(getTenantId(), edge, ruleChainId);
  281 +
  282 + logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);
  283 +
  284 + return updatedEdge;
  285 + } catch (Exception e) {
  286 + logEntityAction(emptyId(EntityType.EDGE),
  287 + null,
  288 + null,
  289 + ActionType.UPDATED, e, strEdgeId);
  290 + throw handleException(e);
  291 + }
  292 + }
  293 +
253 294 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
254 295 @RequestMapping(value = "/customer/{customerId}/edges", params = {"limit"}, method = RequestMethod.GET)
255 296 @ResponseBody
... ...
... ... @@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
59 59 import org.thingsboard.server.common.msg.TbMsg;
60 60 import org.thingsboard.server.common.msg.TbMsgMetaData;
61 61 import org.thingsboard.server.dao.event.EventService;
  62 +import org.thingsboard.server.dao.exception.DataValidationException;
62 63 import org.thingsboard.server.service.script.JsInvokeService;
63 64 import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
64 65 import org.thingsboard.server.service.security.permission.Operation;
... ...
... ... @@ -18,10 +18,12 @@ package org.thingsboard.server.dao.edge;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.EntitySubtype;
20 20 import org.thingsboard.server.common.data.Event;
  21 +import org.thingsboard.server.common.data.Tenant;
21 22 import org.thingsboard.server.common.data.edge.Edge;
22 23 import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
23 24 import org.thingsboard.server.common.data.id.CustomerId;
24 25 import org.thingsboard.server.common.data.id.EdgeId;
  26 +import org.thingsboard.server.common.data.id.RuleChainId;
25 27 import org.thingsboard.server.common.data.id.TenantId;
26 28 import org.thingsboard.server.common.data.page.TextPageData;
27 29 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -73,6 +75,8 @@ public interface EdgeService {
73 75 void pushEventToEdge(TenantId tenantId, TbMsg tbMsg);
74 76
75 77 TimePageData<Event> findQueueEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
  78 +
  79 + Edge setRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId);
76 80 }
77 81
78 82
... ...
... ... @@ -124,7 +124,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
124 124 }
125 125
126 126 public boolean isAssignedToEdge(EdgeId edgeId) {
127   - return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null));
  127 + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null));
128 128 }
129 129
130 130 public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) {
... ...
... ... @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
19 19 import lombok.Getter;
20 20 import lombok.Setter;
21 21 import org.thingsboard.server.common.data.id.EdgeId;
  22 +import org.thingsboard.server.common.data.id.RuleChainId;
22 23
23 24 @AllArgsConstructor
24 25 public class ShortEdgeInfo {
... ... @@ -29,6 +30,9 @@ public class ShortEdgeInfo {
29 30 @Getter @Setter
30 31 private String title;
31 32
  33 + @Getter @Setter
  34 + private RuleChainId rootRuleChainId;
  35 +
32 36 @Override
33 37 public boolean equals(Object o) {
34 38 if (this == o) return true;
... ...
... ... @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo;
29 29 import org.thingsboard.server.common.data.ShortEdgeInfo;
30 30 import org.thingsboard.server.common.data.id.CustomerId;
31 31 import org.thingsboard.server.common.data.id.EdgeId;
  32 +import org.thingsboard.server.common.data.id.RuleChainId;
32 33 import org.thingsboard.server.common.data.id.TenantId;
33 34
34 35 @EqualsAndHashCode(callSuper = true)
... ... @@ -41,6 +42,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
41 42
42 43 private TenantId tenantId;
43 44 private CustomerId customerId;
  45 + private RuleChainId rootRuleChainId;
44 46 private String name;
45 47 private String type;
46 48 private String label;
... ... @@ -60,6 +62,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
60 62 super(edge);
61 63 this.tenantId = edge.getTenantId();
62 64 this.customerId = edge.getCustomerId();
  65 + this.rootRuleChainId = edge.getRootRuleChainId();
63 66 this.type = edge.getType();
64 67 this.name = edge.getName();
65 68 this.routingKey = edge.getRoutingKey();
... ... @@ -69,7 +72,7 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
69 72
70 73 @JsonIgnore
71 74 public ShortEdgeInfo toShortEdgeInfo() {
72   - return new ShortEdgeInfo(id, name);
  75 + return new ShortEdgeInfo(id, name, rootRuleChainId);
73 76 }
74 77
75 78 @Override
... ...
... ... @@ -94,7 +94,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
94 94 }
95 95
96 96 public boolean isAssignedToEdge(EdgeId edgeId) {
97   - return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null));
  97 + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null));
98 98 }
99 99
100 100 public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) {
... ...
... ... @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
44 44 import org.thingsboard.server.common.data.id.CustomerId;
45 45 import org.thingsboard.server.common.data.id.EdgeId;
46 46 import org.thingsboard.server.common.data.id.EntityId;
  47 +import org.thingsboard.server.common.data.id.RuleChainId;
47 48 import org.thingsboard.server.common.data.id.TenantId;
48 49 import org.thingsboard.server.common.data.page.TextPageData;
49 50 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -472,6 +473,14 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic
472 473 return eventService.findEvents(tenantId, edgeId, DataConstants.EDGE_QUEUE_EVENT_TYPE, pageLink);
473 474 }
474 475
  476 + @Override
  477 + public Edge setRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) {
  478 + edge.setRootRuleChainId(ruleChainId);
  479 + Edge saveEdge = saveEdge(edge);
  480 + ruleChainService.updateEdgeRuleChains(tenantId, saveEdge.getId());
  481 + return saveEdge;
  482 + }
  483 +
475 484 private DataValidator<Edge> edgeValidator =
476 485 new DataValidator<Edge>() {
477 486
... ...
... ... @@ -359,6 +359,7 @@ public class ModelConstants {
359 359 public static final String EDGE_COLUMN_FAMILY_NAME = "edge";
360 360 public static final String EDGE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
361 361 public static final String EDGE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
  362 + public static final String EDGE_ROOT_RULE_CHAIN_ID_PROPERTY = "root_rule_chain_id";
362 363 public static final String EDGE_NAME_PROPERTY = "name";
363 364 public static final String EDGE_LABEL_PROPERTY = "label";
364 365 public static final String EDGE_TYPE_PROPERTY = "type";
... ...
... ... @@ -25,6 +25,7 @@ import lombok.Data;
25 25 import org.thingsboard.server.common.data.edge.Edge;
26 26 import org.thingsboard.server.common.data.id.CustomerId;
27 27 import org.thingsboard.server.common.data.id.EdgeId;
  28 +import org.thingsboard.server.common.data.id.RuleChainId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
29 30 import org.thingsboard.server.dao.model.SearchTextEntity;
30 31 import org.thingsboard.server.dao.model.type.JsonCodec;
... ... @@ -37,6 +38,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION
37 38 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY;
38 39 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY;
39 40 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY;
40 42 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY;
41 43 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY;
42 44 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
... ... @@ -60,6 +62,9 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
60 62 @Column(name = EDGE_CUSTOMER_ID_PROPERTY)
61 63 private UUID customerId;
62 64
  65 + @Column(name = EDGE_ROOT_RULE_CHAIN_ID_PROPERTY)
  66 + private UUID rootRuleChainId;
  67 +
63 68 @Column(name = EDGE_TYPE_PROPERTY)
64 69 private String type;
65 70
... ... @@ -95,6 +100,12 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
95 100 if (edge.getTenantId() != null) {
96 101 this.tenantId = edge.getTenantId().getId();
97 102 }
  103 + if (edge.getCustomerId() != null) {
  104 + this.customerId = edge.getCustomerId().getId();
  105 + }
  106 + if (edge.getRootRuleChainId() != null) {
  107 + this.rootRuleChainId = edge.getRootRuleChainId().getId();
  108 + }
98 109 this.type = edge.getType();
99 110 this.name = edge.getName();
100 111 this.label = edge.getLabel();
... ... @@ -119,6 +130,9 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
119 130 if (customerId != null) {
120 131 edge.setCustomerId(new CustomerId(customerId));
121 132 }
  133 + if (rootRuleChainId != null) {
  134 + edge.setRootRuleChainId(new RuleChainId(rootRuleChainId));
  135 + }
122 136 edge.setType(type);
123 137 edge.setName(name);
124 138 edge.setLabel(label);
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.UUIDConverter;
25 25 import org.thingsboard.server.common.data.edge.Edge;
26 26 import org.thingsboard.server.common.data.id.CustomerId;
27 27 import org.thingsboard.server.common.data.id.EdgeId;
  28 +import org.thingsboard.server.common.data.id.RuleChainId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
29 30 import org.thingsboard.server.dao.model.BaseSqlEntity;
30 31 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -39,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY
39 40 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY;
40 41 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY;
41 42 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY;
42 44 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY;
43 45 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY;
44 46 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
... ... @@ -58,6 +60,9 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
58 60 @Column(name = EDGE_CUSTOMER_ID_PROPERTY)
59 61 private String customerId;
60 62
  63 + @Column(name = EDGE_ROOT_RULE_CHAIN_ID_PROPERTY)
  64 + private String rootRuleChainId;
  65 +
61 66 @Column(name = EDGE_TYPE_PROPERTY)
62 67 private String type;
63 68
... ... @@ -98,6 +103,9 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
98 103 if (edge.getCustomerId() != null) {
99 104 this.customerId = UUIDConverter.fromTimeUUID(edge.getCustomerId().getId());
100 105 }
  106 + if (edge.getRootRuleChainId() != null) {
  107 + this.rootRuleChainId = UUIDConverter.fromTimeUUID(edge.getRootRuleChainId().getId());
  108 + }
101 109 this.type = edge.getType();
102 110 this.name = edge.getName();
103 111 this.label = edge.getLabel();
... ... @@ -131,6 +139,9 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
131 139 if (customerId != null) {
132 140 edge.setCustomerId(new CustomerId(UUIDConverter.fromString(customerId)));
133 141 }
  142 + if (rootRuleChainId != null) {
  143 + edge.setRootRuleChainId(new RuleChainId(UUIDConverter.fromString(rootRuleChainId)));
  144 + }
134 145 edge.setType(type);
135 146 edge.setName(name);
136 147 edge.setLabel(label);
... ...
... ... @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.stereotype.Service;
26 26 import org.thingsboard.server.common.data.BaseData;
27 27 import org.thingsboard.server.common.data.EntityType;
  28 +import org.thingsboard.server.common.data.ShortEdgeInfo;
28 29 import org.thingsboard.server.common.data.Tenant;
29 30 import org.thingsboard.server.common.data.edge.Edge;
30 31 import org.thingsboard.server.common.data.id.EdgeId;
... ... @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
42 43 import org.thingsboard.server.common.data.rule.RuleChain;
43 44 import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
44 45 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  46 +import org.thingsboard.server.common.data.rule.RuleChainType;
45 47 import org.thingsboard.server.common.data.rule.RuleNode;
46 48 import org.thingsboard.server.dao.edge.EdgeDao;
47 49 import org.thingsboard.server.dao.edge.EdgeService;
... ... @@ -116,6 +118,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
116 118 createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
117 119 EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
118 120 ruleChain.setRoot(true);
  121 + ruleChain.setType(RuleChainType.SYSTEM);
119 122 ruleChainDao.save(tenantId, ruleChain);
120 123 return true;
121 124 } catch (ExecutionException | InterruptedException e) {
... ... @@ -359,8 +362,17 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
359 362 public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) {
360 363 Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
361 364 RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
362   - if (ruleChain != null && ruleChain.isRoot()) {
363   - throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
  365 + if (ruleChain != null) {
  366 + if (ruleChain.isRoot()) {
  367 + throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
  368 + }
  369 + if (ruleChain.getAssignedEdges() != null && !ruleChain.getAssignedEdges().isEmpty()) {
  370 + for (ShortEdgeInfo assignedEdge : ruleChain.getAssignedEdges()) {
  371 + if (assignedEdge.getRootRuleChainId() != null && assignedEdge.getRootRuleChainId().equals(ruleChainId)) {
  372 + 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!");
  373 + }
  374 + }
  375 + }
364 376 }
365 377 checkRuleNodesAndDelete(tenantId, ruleChainId);
366 378 }
... ... @@ -398,13 +410,16 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
398 410 RuleChain ruleChain = findRuleChainById(tenantId, ruleChainId);
399 411 Edge edge = edgeDao.findById(tenantId, edgeId.getId());
400 412 if (edge == null) {
401   - throw new DataValidationException("Can't unassign ruleChain from non-existent edge!");
  413 + throw new DataValidationException("Can't unassign rule chain from non-existent edge!");
  414 + }
  415 + if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) {
  416 + throw new DataValidationException("Can't unassign root rule chain from edge [" + edge.getName() + "]. Please assign another root rule chain first!");
402 417 }
403 418 if (ruleChain.removeAssignedEdge(edge)) {
404 419 try {
405 420 deleteRelation(tenantId, new EntityRelation(edgeId, ruleChainId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
406 421 } catch (ExecutionException | InterruptedException e) {
407   - log.warn("[{}] Failed to delete ruleChain relation. Edge Id: [{}]", ruleChainId, edgeId);
  422 + log.warn("[{}] Failed to delete rule chain relation. Edge Id: [{}]", ruleChainId, edgeId);
408 423 throw new RuntimeException(e);
409 424 }
410 425 return saveRuleChain(ruleChain);
... ... @@ -442,13 +457,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
442 457 Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
443 458 Validator.validateId(edgeId, "Incorrect customerId " + edgeId);
444 459 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
445   - ListenableFuture<List<RuleChain>> dashboards = ruleChainDao.findRuleChainsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
  460 + ListenableFuture<List<RuleChain>> ruleChains = ruleChainDao.findRuleChainsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
446 461
447   - return Futures.transform(dashboards, new Function<List<RuleChain>, TimePageData<RuleChain>>() {
  462 + return Futures.transform(ruleChains, new Function<List<RuleChain>, TimePageData<RuleChain>>() {
448 463 @Nullable
449 464 @Override
450   - public TimePageData<RuleChain> apply(@Nullable List<RuleChain> RuleChain) {
451   - return new TimePageData<>(RuleChain, pageLink);
  465 + public TimePageData<RuleChain> apply(@Nullable List<RuleChain> ruleChain) {
  466 + return new TimePageData<>(ruleChain, pageLink);
452 467 }
453 468 });
454 469 }
... ...
... ... @@ -47,5 +47,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
47 47 * @return the list of rule chain objects
48 48 */
49 49 ListenableFuture<List<RuleChain>> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink);
50   -
51 50 }
... ...
... ... @@ -88,5 +88,4 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
88 88 return Futures.successfulAsList(ruleChainFutures);
89 89 });
90 90 }
91   -
92 91 }
... ...
... ... @@ -254,6 +254,7 @@ CREATE TABLE IF NOT EXISTS edge (
254 254 id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
255 255 additional_info varchar,
256 256 customer_id varchar(31),
  257 + root_rule_chain_id varchar(31),
257 258 configuration varchar(10000000),
258 259 type varchar(255),
259 260 name varchar(255),
... ...
... ... @@ -31,7 +31,8 @@ function EdgeService($http, $q, customerService) {
31 31 getCustomerEdges: getCustomerEdges,
32 32 assignEdgeToCustomer: assignEdgeToCustomer,
33 33 unassignEdgeFromCustomer: unassignEdgeFromCustomer,
34   - makeEdgePublic: makeEdgePublic
  34 + makeEdgePublic: makeEdgePublic,
  35 + setRootRuleChain: setRootRuleChain
35 36 };
36 37
37 38 return service;
... ... @@ -229,4 +230,15 @@ function EdgeService($http, $q, customerService) {
229 230 });
230 231 return deferred.promise;
231 232 }
  233 +
  234 + function setRootRuleChain(edgeId, ruleChainId) {
  235 + var deferred = $q.defer();
  236 + var url = '/api/edge/' + edgeId + '/' + ruleChainId + '/root';
  237 + $http.post(url).then(function success(response) {
  238 + deferred.resolve(response.data);
  239 + }, function fail() {
  240 + deferred.reject();
  241 + });
  242 + return deferred.promise;
  243 + }
232 244 }
... ...
... ... @@ -19,6 +19,7 @@ import addEdgeTemplate from './add-edge.tpl.html';
19 19 import edgeCard from './edge-card.tpl.html';
20 20 import assignToCustomerTemplate from './assign-to-customer.tpl.html';
21 21 import addEdgesToCustomerTemplate from './add-edges-to-customer.tpl.html';
  22 +import setRootRuleChainToEdgesTemplate from './set-root-rule-chain-to-edges.tpl.html';
22 23
23 24 /* eslint-enable import/no-unresolved, import/default */
24 25
... ... @@ -47,8 +48,8 @@ export function EdgeCardController(types) {
47 48
48 49
49 50 /*@ngInject*/
50   -export function EdgeController($rootScope, userService, edgeService, customerService, $state, $stateParams,
51   - $document, $mdDialog, $q, $translate, types, importExport) {
  51 +export function EdgeController($rootScope, userService, edgeService, customerService, ruleChainService,
  52 + $state, $stateParams, $document, $mdDialog, $q, $translate, types, importExport) {
52 53
53 54 var customerId = $stateParams.customerId;
54 55
... ... @@ -295,6 +296,19 @@ export function EdgeController($rootScope, userService, edgeService, customerSer
295 296 edgeGroupActionsList.push(
296 297 {
297 298 onAction: function ($event, items) {
  299 + setRootRuleChainToEdges($event, items);
  300 + },
  301 + name: function() { return $translate.instant('edge.set-root-rule-chain-to-edges') },
  302 + details: function(selectedCount) {
  303 + return $translate.instant('edge.set-root-rule-chain-to-edges-text', {count: selectedCount}, "messageformat");
  304 + },
  305 + icon: "flag"
  306 + }
  307 + );
  308 +
  309 + edgeGroupActionsList.push(
  310 + {
  311 + onAction: function ($event, items) {
298 312 assignEdgesToCustomer($event, items);
299 313 },
300 314 name: function() { return $translate.instant('edge.assign-edges') },
... ... @@ -305,8 +319,6 @@ export function EdgeController($rootScope, userService, edgeService, customerSer
305 319 }
306 320 );
307 321
308   -
309   -
310 322 edgeGroupActionsList.push(
311 323 {
312 324 onAction: function ($event) {
... ... @@ -318,9 +330,7 @@ export function EdgeController($rootScope, userService, edgeService, customerSer
318 330 }
319 331 );
320 332
321   -
322   -
323   - } else if (vm.edgesScope === 'customer' || vm.edgesScope === 'customer_user') {
  333 + } else if (vm.edgesScope === 'customer' || vm.edgesScope === 'customer_user') {
324 334 fetchEdgesFunction = function (pageLink, edgeType) {
325 335 return edgeService.getCustomerEdges(customerId, pageLink, true, null, edgeType);
326 336 };
... ... @@ -524,6 +534,50 @@ export function EdgeController($rootScope, userService, edgeService, customerSer
524 534 });
525 535 }
526 536
  537 + function setRootRuleChainToEdges($event, items) {
  538 + var edgeIds = [];
  539 + for (var id in items.selections) {
  540 + edgeIds.push(id);
  541 + }
  542 + setRootRuleChain($event, edgeIds);
  543 + }
  544 +
  545 + function setRootRuleChain($event, edgeIds) {
  546 + if ($event) {
  547 + $event.stopPropagation();
  548 + }
  549 + var pageSize = 10;
  550 + ruleChainService.getRuleChains({limit: pageSize, textSearch: ''}).then(
  551 + function success(_ruleChains) {
  552 + var ruleChains = {
  553 + pageSize: pageSize,
  554 + data: _ruleChains.data,
  555 + nextPageLink: _ruleChains.nextPageLink,
  556 + selection: null,
  557 + hasNext: _ruleChains.hasNext,
  558 + pending: false
  559 + };
  560 + if (ruleChains.hasNext) {
  561 + ruleChains.nextPageLink.limit = pageSize;
  562 + }
  563 + $mdDialog.show({
  564 + controller: 'SetRootRuleChainToEdgesController',
  565 + controllerAs: 'vm',
  566 + templateUrl: setRootRuleChainToEdgesTemplate,
  567 + locals: {edgeIds: edgeIds, ruleChains: ruleChains},
  568 + parent: angular.element($document[0].body),
  569 + fullscreen: true,
  570 + targetEvent: $event
  571 + }).then(function () {
  572 + vm.grid.refreshList();
  573 + }, function () {
  574 + });
  575 + },
  576 + function fail() {
  577 + });
  578 + }
  579 +
  580 +
527 581 function assignEdgesToCustomer($event, items) {
528 582 var edgeIds = [];
529 583 for (var id in items.selections) {
... ...
... ... @@ -23,6 +23,7 @@ import EdgeRoutes from './edge.routes';
23 23 import {EdgeController, EdgeCardController} from './edge.controller';
24 24 import AssignEdgeToCustomerController from './assign-to-customer.controller';
25 25 import AddEdgesToCustomerController from './add-edges-to-customer.controller';
  26 +import SetRootRuleChainToEdgesController from './set-root-rule-chain-to-edges.controller';
26 27 import EdgeDirective from './edge.directive';
27 28
28 29 export default angular.module('thingsboard.edge', [
... ... @@ -37,5 +38,6 @@ export default angular.module('thingsboard.edge', [
37 38 .controller('EdgeCardController', EdgeCardController)
38 39 .controller('AssignEdgeToCustomerController', AssignEdgeToCustomerController)
39 40 .controller('AddEdgesToCustomerController', AddEdgesToCustomerController)
  41 + .controller('SetRootRuleChainToEdgesController', SetRootRuleChainToEdgesController)
40 42 .directive('tbEdge', EdgeDirective)
41 43 .name;
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/*@ngInject*/
  17 +export default function SetRootRuleChainToEdgesController(ruleChainService, edgeService, $mdDialog, $q, edgeIds, ruleChains) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.ruleChains = ruleChains;
  22 + vm.searchText = '';
  23 +
  24 + vm.assign = assign;
  25 + vm.cancel = cancel;
  26 + vm.isRuleChainSelected = isRuleChainSelected;
  27 + vm.hasData = hasData;
  28 + vm.noData = noData;
  29 + vm.searchRuleChainTextUpdated = searchRuleChainTextUpdated;
  30 + vm.toggleRuleChainSelection = toggleRuleChainSelection;
  31 +
  32 + vm.theRuleChains = {
  33 + getItemAtIndex: function (index) {
  34 + if (index > vm.ruleChains.data.length) {
  35 + vm.theRuleChains.fetchMoreItems_(index);
  36 + return null;
  37 + }
  38 + var item = vm.ruleChains.data[index];
  39 + if (item) {
  40 + item.indexNumber = index + 1;
  41 + }
  42 + return item;
  43 + },
  44 +
  45 + getLength: function () {
  46 + if (vm.ruleChains.hasNext) {
  47 + return vm.ruleChains.data.length + vm.ruleChains.nextPageLink.limit;
  48 + } else {
  49 + return vm.ruleChains.data.length;
  50 + }
  51 + },
  52 +
  53 + fetchMoreItems_: function () {
  54 + if (vm.ruleChains.hasNext && !vm.ruleChains.pending) {
  55 + vm.ruleChains.pending = true;
  56 + ruleChainService.getRuleChains(vm.ruleChains.nextPageLink).then(
  57 + function success(ruleChains) {
  58 + vm.ruleChains.data = vm.ruleChains.data.concat(ruleChains.data);
  59 + vm.ruleChains.nextPageLink = ruleChains.nextPageLink;
  60 + vm.ruleChains.hasNext = ruleChains.hasNext;
  61 + if (vm.ruleChains.hasNext) {
  62 + vm.ruleChains.nextPageLink.limit = vm.ruleChains.pageSize;
  63 + }
  64 + vm.ruleChains.pending = false;
  65 + },
  66 + function fail() {
  67 + vm.ruleChains.hasNext = false;
  68 + vm.ruleChains.pending = false;
  69 + });
  70 + }
  71 + }
  72 + };
  73 +
  74 + function cancel() {
  75 + $mdDialog.cancel();
  76 + }
  77 +
  78 + function assign() {
  79 + var assignTasks = [];
  80 + for (var i=0;i<edgeIds.length;i++) {
  81 + assignTasks.push(ruleChainService.assignRuleChainToEdge(edgeIds[i], vm.ruleChains.selection.id.id));
  82 + }
  83 + $q.all(assignTasks).then(function () {
  84 + var setRootTasks = [];
  85 + for (var j=0;j<edgeIds.length;j++) {
  86 + setRootTasks.push(edgeService.setRootRuleChain(edgeIds[j], vm.ruleChains.selection.id.id));
  87 + }
  88 + $q.all(setRootTasks).then(function () {
  89 + $mdDialog.hide();
  90 + });
  91 + });
  92 + }
  93 +
  94 + function noData() {
  95 + return vm.ruleChains.data.length == 0 && !vm.ruleChains.hasNext;
  96 + }
  97 +
  98 + function hasData() {
  99 + return vm.ruleChains.data.length > 0;
  100 + }
  101 +
  102 + function toggleRuleChainSelection($event, ruleChain) {
  103 + $event.stopPropagation();
  104 + if (vm.isRuleChainSelected(ruleChain)) {
  105 + vm.ruleChains.selection = null;
  106 + } else {
  107 + vm.ruleChains.selection = ruleChain;
  108 + }
  109 + }
  110 +
  111 + function isRuleChainSelected(ruleChain) {
  112 + return vm.ruleChains.selection != null && ruleChain &&
  113 + ruleChain.id.id === vm.ruleChains.selection.id.id;
  114 + }
  115 +
  116 + function searchRuleChainTextUpdated() {
  117 + vm.ruleChains = {
  118 + pageSize: vm.ruleChains.pageSize,
  119 + data: [],
  120 + nextPageLink: {
  121 + limit: vm.ruleChains.pageSize,
  122 + textSearch: vm.searchText
  123 + },
  124 + selection: null,
  125 + hasNext: true,
  126 + pending: false
  127 + };
  128 + }
  129 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'edge.set-root-rule-chain-to-edges' | translate }}">
  19 + <form name="theForm" ng-submit="vm.assign()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>edge.set-root-rule-chain-to-edges</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <fieldset>
  34 + <span translate>edge.set-root-rule-chain-text</span>
  35 + <md-input-container class="md-block" style='margin-bottom: 0px;'>
  36 + <label>&nbsp;</label>
  37 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
  38 + search
  39 + </md-icon>
  40 + <input id="rule-chain-search" autofocus ng-model="vm.searchText"
  41 + ng-change="vm.searchRuleChainTextUpdated()"
  42 + placeholder="{{ 'common.enter-search' | translate }}"/>
  43 + </md-input-container>
  44 + <div style='min-height: 150px;'>
  45 + <span translate layout-align="center center"
  46 + style="text-transform: uppercase; display: flex; height: 150px;"
  47 + class="md-subhead"
  48 + ng-show="vm.noData()">rulechain.no-rulechains-text</span>
  49 + <md-virtual-repeat-container ng-show="vm.hasData()"
  50 + tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
  51 + style='min-height: 150px; width: 100%;'>
  52 + <md-list>
  53 + <md-list-item md-virtual-repeat="ruleChain in vm.theRuleChains" md-on-demand
  54 + class="repeated-item" flex>
  55 + <md-checkbox ng-click="vm.toggleRuleChainSelection($event, ruleChain)"
  56 + aria-label="{{ 'item.selected' | translate }}"
  57 + ng-checked="vm.isRuleChainSelected(ruleChain)"></md-checkbox>
  58 + <span> {{ ruleChain.name }} </span>
  59 + </md-list-item>
  60 + </md-list>
  61 + </md-virtual-repeat-container>
  62 + </div>
  63 + </fieldset>
  64 + </div>
  65 + </md-dialog-content>
  66 + <md-dialog-actions layout="row">
  67 + <span flex></span>
  68 + <md-button ng-disabled="$root.loading || vm.ruleChains.selection==null" type="submit" class="md-raised md-primary">
  69 + {{ 'action.assign' | translate }}
  70 + </md-button>
  71 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
  72 + translate }}
  73 + </md-button>
  74 + </md-dialog-actions>
  75 + </form>
  76 +</md-dialog>
\ No newline at end of file
... ...
... ... @@ -792,6 +792,7 @@
792 792 "dashboards": "Edge Dashboards",
793 793 "manage-edge-rulechains": "Manage edge rule chains",
794 794 "rulechains": "Edge Rule Chains",
  795 + "rulechain": "Edge Rule Chain",
795 796 "edge-key": "Edge key",
796 797 "copy-edge-key": "Copy edge key",
797 798 "edge-key-copied-message": "Edge key has been copied to clipboard",
... ... @@ -803,7 +804,10 @@
803 804 "manage-edge-entity-views": "Manage edge entity views",
804 805 "assets": "Edge assets",
805 806 "devices": "Edge devices",
806   - "entity-views": "Edge entity views"
  807 + "entity-views": "Edge entity views",
  808 + "set-root-rule-chain-text": "Please select root rule chain for edge(s)",
  809 + "set-root-rule-chain-to-edges": "Set root rule chain for Edge(s)",
  810 + "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }"
807 811 },
808 812 "error": {
809 813 "unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
... ...
... ... @@ -31,7 +31,7 @@
31 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
32 32 <md-dialog-content>
33 33 <div class="md-dialog-content">
34   - <tb-rule-chain rule-chain="vm.item" is-edit="true" the-form="theForm"></tb-rule-chain>
  34 + <tb-rule-chain rule-chain="vm.item" is-edit="true" rule-chain-scope="'tenant'" the-form="theForm"></tb-rule-chain>
35 35 </div>
36 36 </md-dialog-content>
37 37 <md-dialog-actions layout="row">
... ...
... ... @@ -54,7 +54,7 @@ export default function AddRuleChainsToEdgeController(ruleChainService, $mdDialo
54 54 vm.ruleChains.pending = true;
55 55 ruleChainService.getRuleChains(vm.ruleChains.nextPageLink).then(
56 56 function success(ruleChains) {
57   - vm.ruleChains.data = vm.ruleChains.data.concat(filterNonRootRuleChains(ruleChains.data));
  57 + vm.ruleChains.data = ruleChains.data;
58 58 vm.ruleChains.nextPageLink = ruleChains.nextPageLink;
59 59 vm.ruleChains.hasNext = ruleChains.hasNext;
60 60 if (vm.ruleChains.hasNext) {
... ... @@ -119,12 +119,4 @@ export default function AddRuleChainsToEdgeController(ruleChainService, $mdDialo
119 119 pending: false
120 120 };
121 121 }
122   -
123   - function filterNonRootRuleChains(ruleChains) {
124   - return $filter('filter')(ruleChains, isNonRootRuleChain);
125   - }
126   -
127   - function isNonRootRuleChain(ruleChain) {
128   - return ruleChain && !ruleChain.root;
129   - }
130 122 }
\ No newline at end of file
... ...
... ... @@ -16,4 +16,4 @@
16 16
17 17 -->
18 18 <div class="tb-small tb-rule-chain-assigned-edges" ng-show="vm.parentCtl.ruleChainsScope === 'tenant' && vm.item.assignedEdgesText">{{'rulechain.assigned-to-edges' | translate}}: '{{vm.item.assignedEdgesText}}'</div>
19   -<div ng-if="item && item.root" translate>rulechain.root</div>
  19 +<div ng-if="(vm.parentCtl.ruleChainsScope === 'tenant' && item && item.root) || (vm.parentCtl.ruleChainsScope === 'edge' && vm.parentCtl.isRootRuleChain(item))" translate>rulechain.root</div>
... ...
... ... @@ -51,7 +51,7 @@
51 51 </md-input-container>
52 52 <md-input-container class="md-block">
53 53 <label translate>rulechain.type</label>
54   - <md-select ng-disabled="$root.loading || !isEdit" name="type" ng-model="ruleChain.type">
  54 + <md-select ng-disabled="$root.loading || !isEdit || ruleChainScope !== 'tenant' || ruleChain.root === true" name="type" ng-model="ruleChain.type">
55 55 <md-option ng-repeat="ruleChainType in ruleChainTypes" value="{{ruleChainType}}">
56 56 {{ruleChainType}}
57 57 </md-option>
... ...
... ... @@ -49,7 +49,7 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog,
49 49 theForm: '=',
50 50 onSetRootRuleChain: '&',
51 51 onExportRuleChain: '&',
52   - onDeleteRuleChain: '&',
  52 + onDeleteRuleChain: '&'
53 53 }
54 54 };
55 55 }
... ...
... ... @@ -145,5 +145,43 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
145 145 ncyBreadcrumb: {
146 146 label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}'
147 147 }
  148 + })
  149 + .state('home.edges.ruleChains.ruleChain', {
  150 + url: '/:ruleChainId',
  151 + reloadOnSearch: false,
  152 + module: 'private',
  153 + auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
  154 + views: {
  155 + "content@home": {
  156 + templateUrl: ruleChainTemplate,
  157 + controller: 'RuleChainController',
  158 + controllerAs: 'vm'
  159 + }
  160 + },
  161 + resolve: {
  162 + ruleChain:
  163 + /*@ngInject*/
  164 + function($stateParams, ruleChainService) {
  165 + return ruleChainService.getRuleChain($stateParams.ruleChainId);
  166 + },
  167 + ruleChainMetaData:
  168 + /*@ngInject*/
  169 + function($stateParams, ruleChainService) {
  170 + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
  171 + },
  172 + ruleNodeComponents:
  173 + /*@ngInject*/
  174 + function($stateParams, ruleChainService) {
  175 + return ruleChainService.getRuleNodeComponents();
  176 + }
  177 + },
  178 + data: {
  179 + import: false,
  180 + searchEnabled: false,
  181 + pageTitle: 'edge.rulechain'
  182 + },
  183 + ncyBreadcrumb: {
  184 + label: '{"icon": "settings_ethernet", "label": "edge.rulechain"}'
  185 + }
148 186 });
149 187 }
\ No newline at end of file
... ...
... ... @@ -25,7 +25,7 @@ import addRuleChainsToEdgeTemplate from "./add-rulechains-to-edge.tpl.html";
25 25 import './rulechain-card.scss';
26 26
27 27 /*@ngInject*/
28   -export default function RuleChainsController(ruleChainService, userService, importExport, $state,
  28 +export default function RuleChainsController(ruleChainService, userService, edgeService, importExport, $state,
29 29 $stateParams, $filter, $translate, $mdDialog, $document, $q, types) {
30 30
31 31 var vm = this;
... ... @@ -107,6 +107,11 @@ export default function RuleChainsController(ruleChainService, userService, impo
107 107
108 108 if (edgeId) {
109 109 vm.edgeRuleChainsTitle = $translate.instant('edge.rulechains');
  110 + edgeService.getEdge(edgeId).then(
  111 + function success(edge) {
  112 + vm.edge = edge;
  113 + }
  114 + );
110 115 }
111 116
112 117 if (vm.ruleChainsScope === 'tenant') {
... ... @@ -133,8 +138,7 @@ export default function RuleChainsController(ruleChainService, userService, impo
133 138 },
134 139 name: function() { return $translate.instant('action.assign') },
135 140 details: function() { return $translate.instant('rulechain.manage-assigned-edges') },
136   - icon: "wifi_tethering",
137   - isEnabled: isNonRootRuleChain
  141 + icon: "wifi_tethering"
138 142 });
139 143
140 144 ruleChainActionsList.push({
... ... @@ -214,6 +218,16 @@ export default function RuleChainsController(ruleChainService, userService, impo
214 218 return ruleChainService.unassignRuleChainFromEdge(edgeId, ruleChainId);
215 219 };
216 220
  221 + ruleChainActionsList.push({
  222 + onAction: function ($event, item) {
  223 + setRootRuleChain($event, item);
  224 + },
  225 + name: function() { return $translate.instant('rulechain.set-root') },
  226 + details: function() { return $translate.instant('rulechain.set-root') },
  227 + icon: "flag",
  228 + isEnabled: isNonRootRuleChain
  229 + });
  230 +
217 231 ruleChainActionsList.push(
218 232 {
219 233 onAction: function ($event, item) {
... ... @@ -221,7 +235,8 @@ export default function RuleChainsController(ruleChainService, userService, impo
221 235 },
222 236 name: function() { return $translate.instant('action.unassign') },
223 237 details: function() { return $translate.instant('rulechain.unassign-from-edge') },
224   - icon: "assignment_return"
  238 + icon: "assignment_return",
  239 + isEnabled: isNonRootRuleChain
225 240 }
226 241 );
227 242
... ... @@ -288,7 +303,13 @@ export default function RuleChainsController(ruleChainService, userService, impo
288 303 if ($event) {
289 304 $event.stopPropagation();
290 305 }
291   - $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id});
  306 + if (vm.ruleChainsScope === 'edge') {
  307 + $state.go('home.edges.ruleChains.ruleChain', {
  308 + ruleChainId: ruleChain.id.id
  309 + });
  310 + } else {
  311 + $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id});
  312 + }
292 313 }
293 314
294 315 function deleteRuleChain(ruleChainId) {
... ... @@ -300,11 +321,19 @@ export default function RuleChainsController(ruleChainService, userService, impo
300 321 }
301 322
302 323 function isRootRuleChain(ruleChain) {
303   - return ruleChain && ruleChain.root;
  324 + if (angular.isDefined(vm.edge) && vm.edge != null) {
  325 + return angular.isDefined(vm.edge.rootRuleChainId) && vm.edge.rootRuleChainId != null && vm.edge.rootRuleChainId.id === ruleChain.id.id;
  326 + } else {
  327 + return ruleChain && ruleChain.root;
  328 + }
304 329 }
305 330
306 331 function isNonRootRuleChain(ruleChain) {
307   - return ruleChain && !ruleChain.root;
  332 + if (angular.isDefined(vm.edge) && vm.edge != null) {
  333 + return angular.isDefined(vm.edge.rootRuleChainId) && vm.edge.rootRuleChainId != null && vm.edge.rootRuleChainId.id !== ruleChain.id.id;
  334 + } else {
  335 + return ruleChain && !ruleChain.root;
  336 + }
308 337 }
309 338
310 339 function exportRuleChain($event, ruleChain) {
... ... @@ -322,11 +351,20 @@ export default function RuleChainsController(ruleChainService, userService, impo
322 351 .cancel($translate.instant('action.no'))
323 352 .ok($translate.instant('action.yes'));
324 353 $mdDialog.show(confirm).then(function () {
325   - ruleChainService.setRootRuleChain(ruleChain.id.id).then(
326   - () => {
327   - vm.grid.refreshList();
328   - }
329   - );
  354 + if (angular.isDefined(vm.edge) && vm.edge != null) {
  355 + edgeService.setRootRuleChain(vm.edge.id.id, ruleChain.id.id).then(
  356 + (edge) => {
  357 + vm.edge = edge;
  358 + vm.grid.refreshList();
  359 + }
  360 + );
  361 + } else {
  362 + ruleChainService.setRootRuleChain(ruleChain.id.id).then(
  363 + () => {
  364 + vm.grid.refreshList();
  365 + }
  366 + );
  367 + }
330 368 });
331 369 }
332 370
... ... @@ -396,7 +434,7 @@ export default function RuleChainsController(ruleChainService, userService, impo
396 434 function success(_ruleChains) {
397 435 var ruleChains = {
398 436 pageSize: pageSize,
399   - data: filterNonRootRuleChains(_ruleChains.data),
  437 + data: _ruleChains.data,
400 438 nextPageLink: _ruleChains.nextPageLink,
401 439 selections: {},
402 440 selectedCount: 0,
... ... @@ -423,10 +461,6 @@ export default function RuleChainsController(ruleChainService, userService, impo
423 461 });
424 462 }
425 463
426   - function filterNonRootRuleChains(ruleChains) {
427   - return $filter('filter')(ruleChains, isNonRootRuleChain);
428   - }
429   -
430 464 function unassignFromEdge($event, ruleChain, edgeId) {
431 465 if ($event) {
432 466 $event.stopPropagation();
... ...