diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 077714a8aa..53b8e72685 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -690,7 +690,7 @@ public class DashboardController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @ResponseBody public TimePageData getEdgeDashboards( 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 14fffc7486..57ab0ae642 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -40,13 +40,18 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -59,6 +64,7 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -371,4 +377,239 @@ public class RuleChainController extends BaseController { return objectMapper.writeValueAsString(msgData); } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST) + @ResponseBody + public RuleChain assignRuleChainToEdge(@PathVariable("edgeId") String strEdgeId, + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { + checkParameter("edgeId", strEdgeId); + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); + + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE); + + RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + + logEntityAction(ruleChainId, savedRuleChain, + null, + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, strEdgeId, edge.getName()); + + + return savedRuleChain; + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.RULE_CHAIN), null, + null, + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId, strEdgeId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE) + @ResponseBody + public RuleChain unassignRuleChainFromEdge(@PathVariable("edgeId") String strEdgeId, + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { + checkParameter("edgeId", strEdgeId); + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.UNASSIGN_FROM_EDGE); + + RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + + logEntityAction(ruleChainId, ruleChain, + null, + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edge.getId().toString(), edge.getName()); + + return savedRuleChain; + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.RULE_CHAIN), null, + null, + ActionType.UNASSIGNED_FROM_EDGE, e, strRuleChainId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges", method = RequestMethod.POST) + @ResponseBody + public RuleChain updateRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId, + @RequestBody String[] strEdgeIds) throws ThingsboardException { + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE); + + Set edgeIds = new HashSet<>(); + if (strEdgeIds != null) { + for (String strEdgeId : strEdgeIds) { + edgeIds.add(new EdgeId(toUUID(strEdgeId))); + } + } + + Set addedEdgeIds = new HashSet<>(); + Set removedEdgeIds = new HashSet<>(); + for (EdgeId edgeId : edgeIds) { + if (!ruleChain.isAssignedToEdge(edgeId)) { + addedEdgeIds.add(edgeId); + } + } + + Set assignedEdges = ruleChain.getAssignedEdges(); + if (assignedEdges != null) { + for (ShortEdgeInfo edgeInfo : assignedEdges) { + if (!edgeIds.contains(edgeInfo.getEdgeId())) { + removedEdgeIds.add(edgeInfo.getEdgeId()); + } + } + } + + if (addedEdgeIds.isEmpty() && removedEdgeIds.isEmpty()) { + return ruleChain; + } else { + RuleChain savedRuleChain = null; + for (EdgeId edgeId : addedEdgeIds) { + savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + ShortEdgeInfo edgeInfo = savedRuleChain.getAssignedEdgeInfo(edgeId); + logEntityAction(ruleChainId, savedRuleChain, + null, + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle()); + } + for (EdgeId edgeId : removedEdgeIds) { + ShortEdgeInfo edgeInfo = ruleChain.getAssignedEdgeInfo(edgeId); + savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + logEntityAction(ruleChainId, ruleChain, + null, + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle()); + + } + return savedRuleChain; + } + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.RULE_CHAIN), null, + null, + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges/add", method = RequestMethod.POST) + @ResponseBody + public RuleChain addRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId, + @RequestBody String[] strEdgeIds) throws ThingsboardException { + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE); + + Set edgeIds = new HashSet<>(); + if (strEdgeIds != null) { + for (String strEdgeId : strEdgeIds) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + if (!ruleChain.isAssignedToEdge(edgeId)) { + edgeIds.add(edgeId); + } + } + } + + if (edgeIds.isEmpty()) { + return ruleChain; + } else { + RuleChain savedRuleChain = null; + for (EdgeId edgeId : edgeIds) { + savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + ShortEdgeInfo edgeInfo = savedRuleChain.getAssignedEdgeInfo(edgeId); + logEntityAction(ruleChainId, savedRuleChain, + null, + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle()); + } + return savedRuleChain; + } + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.RULE_CHAIN), null, + null, + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges/remove", method = RequestMethod.POST) + @ResponseBody + public RuleChain removeRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId, + @RequestBody String[] strEdgeIds) throws ThingsboardException { + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.UNASSIGN_FROM_EDGE); + + Set edgeIds = new HashSet<>(); + if (strEdgeIds != null) { + for (String strEdgeId : strEdgeIds) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + if (ruleChain.isAssignedToEdge(edgeId)) { + edgeIds.add(edgeId); + } + } + } + + if (edgeIds.isEmpty()) { + return ruleChain; + } else { + RuleChain savedRuleChain = null; + for (EdgeId edgeId : edgeIds) { + ShortEdgeInfo edgeInfo = ruleChain.getAssignedEdgeInfo(edgeId); + savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId)); + logEntityAction(ruleChainId, ruleChain, + null, + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle()); + + } + return savedRuleChain; + } + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.RULE_CHAIN), null, + null, + ActionType.UNASSIGNED_FROM_EDGE, e, strRuleChainId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = { "limit" }, method = RequestMethod.GET) + @ResponseBody + public TimePageData getEdgeRuleChains( + @PathVariable("edgeId") String strEdgeId, + @RequestParam int limit, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime, + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, + @RequestParam(required = false) String offset) throws ThingsboardException { + checkParameter("edgeId", strEdgeId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get()); + } catch (Exception e) { + throw handleException(e); + } + } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 5a1949e699..798b92ec66 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -16,11 +16,14 @@ package org.thingsboard.server.dao.rule; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -63,4 +66,13 @@ public interface RuleChainService { void deleteRuleChainsByTenantId(TenantId tenantId); + RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId); + + RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId); + + void unassignEdgeRuleChains(TenantId tenantId, EdgeId edgeId); + + void updateEdgeRuleChains(TenantId tenantId, EdgeId edgeId); + + ListenableFuture> findRuleChainsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink); } 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 2187de04f3..4ea158322a 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 @@ -23,10 +23,16 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.ShortEdgeInfo; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import java.util.HashSet; +import java.util.Set; + @Data @EqualsAndHashCode(callSuper = true) @Slf4j @@ -40,6 +46,8 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im private boolean root; private boolean debugMode; private transient JsonNode configuration; + private Set assignedEdges; + @JsonIgnore private byte[] configurationBytes; @@ -57,6 +65,11 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im this.name = ruleChain.getName(); this.firstRuleNodeId = ruleChain.getFirstRuleNodeId(); this.root = ruleChain.isRoot(); + + // TODO: VOBA - check that this is needed + // this.debugMode = ruleChain.isDebugMode(); + + this.assignedEdges = ruleChain.getAssignedEdges(); this.setConfiguration(ruleChain.getConfiguration()); } @@ -78,4 +91,52 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes); } + public boolean isAssignedToEdge(EdgeId edgeId) { + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null)); + } + + public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) { + if (this.assignedEdges != null) { + for (ShortEdgeInfo edgeInfo : this.assignedEdges) { + if (edgeInfo.getEdgeId().equals(edgeId)) { + return edgeInfo; + } + } + } + return null; + } + + public boolean addAssignedEdge(Edge edge) { + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo(); + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) { + return false; + } else { + if (this.assignedEdges == null) { + this.assignedEdges = new HashSet<>(); + } + this.assignedEdges.add(edgeInfo); + return true; + } + } + + public boolean updateAssignedEdge(Edge edge) { + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo(); + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) { + this.assignedEdges.remove(edgeInfo); + this.assignedEdges.add(edgeInfo); + return true; + } else { + return false; + } + } + + public boolean removeAssignedEdge(Edge edge) { + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo(); + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) { + this.assignedEdges.remove(edgeInfo); + return true; + } else { + return false; + } + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java index 25bc752b3a..af02a680fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java @@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.SearchTextEntity; @@ -37,6 +38,7 @@ import java.util.HashSet; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_ASSIGNED_EDGES_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TITLE_PROPERTY; @@ -52,6 +54,8 @@ public class DashboardInfoEntity implements SearchTextEntity { private static final ObjectMapper objectMapper = new ObjectMapper(); private static final JavaType assignedCustomersType = objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class); + private static final JavaType assignedEdgesType = + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortEdgeInfo.class); @PartitionKey(value = 0) @Column(name = ID_PROPERTY) @@ -70,6 +74,9 @@ public class DashboardInfoEntity implements SearchTextEntity { @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) private String assignedCustomers; + @Column(name = DASHBOARD_ASSIGNED_EDGES_PROPERTY) + private String assignedEdges; + public DashboardInfoEntity() { super(); } @@ -89,6 +96,13 @@ public class DashboardInfoEntity implements SearchTextEntity { log.error("Unable to serialize assigned customers to string!", e); } } + if (dashboardInfo.getAssignedEdges() != null) { + try { + this.assignedEdges = objectMapper.writeValueAsString(dashboardInfo.getAssignedEdges()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned edges to string!", e); + } + } } public UUID getId() { @@ -123,6 +137,14 @@ public class DashboardInfoEntity implements SearchTextEntity { this.assignedCustomers = assignedCustomers; } + public String getAssignedEdges() { + return assignedEdges; + } + + public void setAssignedEdges(String assignedEdges) { + this.assignedEdges = assignedEdges; + } + @Override public String getSearchTextSource() { return getTitle(); @@ -152,6 +174,13 @@ public class DashboardInfoEntity implements SearchTextEntity { log.warn("Unable to parse assigned customers!", e); } } + if (!StringUtils.isEmpty(assignedEdges)) { + try { + dashboardInfo.setAssignedEdges(objectMapper.readValue(assignedEdges, assignedEdgesType)); + } catch (IOException e) { + log.warn("Unable to parse assigned edges!", e); + } + } return dashboardInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java index c4ead363c2..442e1f2f22 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java @@ -20,11 +20,17 @@ import com.datastax.driver.mapping.annotations.ClusteringColumn; import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.Table; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -33,11 +39,14 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.model.type.JsonCodec; +import java.io.IOException; +import java.util.HashSet; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ADDITIONAL_INFO_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.DEBUG_MODE; import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_ASSIGNED_EDGES_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY; @@ -46,11 +55,16 @@ import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_ROOT_PR import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; +@Slf4j @Table(name = RULE_CHAIN_COLUMN_FAMILY_NAME) @EqualsAndHashCode @ToString public class RuleChainEntity implements SearchTextEntity { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final JavaType assignedEdgesType = + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortEdgeInfo.class); + @PartitionKey @Column(name = ID_PROPERTY) private UUID id; @@ -74,6 +88,10 @@ public class RuleChainEntity implements SearchTextEntity { @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) private JsonNode additionalInfo; + @Getter @Setter + @Column(name = RULE_CHAIN_ASSIGNED_EDGES_PROPERTY) + private String assignedEdges; + public RuleChainEntity() { } @@ -89,6 +107,13 @@ public class RuleChainEntity implements SearchTextEntity { this.debugMode = ruleChain.isDebugMode(); this.configuration = ruleChain.getConfiguration(); this.additionalInfo = ruleChain.getAdditionalInfo(); + if (ruleChain.getAssignedEdges() != null) { + try { + this.assignedEdges = objectMapper.writeValueAsString(ruleChain.getAssignedEdges()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned edges to string!", e); + } + } } @Override @@ -176,6 +201,13 @@ public class RuleChainEntity implements SearchTextEntity { ruleChain.setDebugMode(this.debugMode); ruleChain.setConfiguration(this.configuration); ruleChain.setAdditionalInfo(this.additionalInfo); + if (!StringUtils.isEmpty(assignedEdges)) { + try { + ruleChain.setAssignedEdges(objectMapper.readValue(assignedEdges, assignedEdgesType)); + } catch (IOException e) { + log.warn("Unable to parse assigned edges!", e); + } + } return ruleChain; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java index 22287dc0e8..92ea3b8529 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -16,11 +16,17 @@ package org.thingsboard.server.dao.model.sql; import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.ShortEdgeInfo; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -35,14 +41,21 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.io.IOException; +import java.util.HashSet; @Data +@Slf4j @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME) public class RuleChainEntity extends BaseSqlEntity implements SearchTextEntity { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final JavaType assignedEdgesType = + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortEdgeInfo.class); + @Column(name = ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY) private String tenantId; @@ -69,6 +82,9 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.RULE_CHAIN_ASSIGNED_EDGES_PROPERTY) + private String assignedEdges; + public RuleChainEntity() { } @@ -86,6 +102,13 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT this.debugMode = ruleChain.isDebugMode(); this.configuration = ruleChain.getConfiguration(); this.additionalInfo = ruleChain.getAdditionalInfo(); + if (ruleChain.getAssignedEdges() != null) { + try { + this.assignedEdges = objectMapper.writeValueAsString(ruleChain.getAssignedEdges()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned edges to string!", e); + } + } } @Override @@ -111,6 +134,13 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT ruleChain.setDebugMode(debugMode); ruleChain.setConfiguration(configuration); ruleChain.setAdditionalInfo(additionalInfo); + if (!StringUtils.isEmpty(assignedEdges)) { + try { + ruleChain.setAssignedEdges(objectMapper.readValue(assignedEdges, assignedEdgesType)); + } catch (IOException e) { + log.warn("Unable to parse assigned edges!", e); + } + } return ruleChain; } } 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 3852ec175e..28af5707ba 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 @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.rule; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -23,12 +25,16 @@ 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.Tenant; +import org.thingsboard.server.common.data.edge.Edge; +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.RuleNodeId; 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.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; @@ -36,13 +42,16 @@ 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.RuleNode; +import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.service.TimePaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.tenant.TenantDao; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -66,6 +75,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Autowired private TenantDao tenantDao; + @Autowired + private EdgeDao edgeDao; + @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); @@ -352,6 +364,89 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC tenantRuleChainsRemover.removeEntities(tenantId, tenantId); } + @Override + public RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId) { + RuleChain ruleChain = findRuleChainById(tenantId, ruleChainId); + Edge edge = edgeDao.findById(tenantId, edgeId.getId()); + if (edge == null) { + throw new DataValidationException("Can't assign ruleChain to non-existent edge!"); + } + if (!edge.getTenantId().getId().equals(ruleChain.getTenantId().getId())) { + throw new DataValidationException("Can't assign ruleChain to edge from different tenant!"); + } + if (ruleChain.addAssignedEdge(edge)) { + try { + createRelation(tenantId, new EntityRelation(edgeId, ruleChainId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create ruleChain relation. Edge Id: [{}]", ruleChainId, edgeId); + throw new RuntimeException(e); + } + return saveRuleChain(ruleChain); + } else { + return ruleChain; + } + } + + @Override + public RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId) { + 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!"); + } + 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); + throw new RuntimeException(e); + } + return saveRuleChain(ruleChain); + } else { + return ruleChain; + } + } + + @Override + public void unassignEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { + log.trace("Executing unassignEdgeRuleChains, edgeId [{}]", edgeId); + Validator.validateId(edgeId, "Incorrect edgeId " + edgeId); + Edge edge = edgeDao.findById(tenantId, edgeId.getId()); + if (edge == null) { + throw new DataValidationException("Can't unassign ruleChains from non-existent edge!"); + } + new EdgeRuleChainsUnassigner(edge).removeEntities(tenantId, edge); + } + + @Override + public void updateEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { + log.trace("Executing updateEdgeRuleChains, edgeId [{}]", edgeId); + Validator.validateId(edgeId, "Incorrect edgeId " + edgeId); + Edge edge = edgeDao.findById(tenantId, edgeId.getId()); + if (edge == null) { + throw new DataValidationException("Can't update ruleChains for non-existent edge!"); + } + new EdgeRuleChainsUpdater(edge).removeEntities(tenantId, edge); + } + + + @Override + public ListenableFuture> findRuleChainsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) { + log.trace("Executing findRuleChainsByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink); + 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); + + return Futures.transform(dashboards, new Function, TimePageData>() { + @Nullable + @Override + public TimePageData apply(@Nullable List RuleChain) { + return new TimePageData<>(RuleChain, pageLink); + } + }); + } + private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) { List nodeRelations = getRuleChainToNodeRelations(tenantId, ruleChainId); for (EntityRelation relation : nodeRelations) { @@ -384,6 +479,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC relationService.deleteRelation(tenantId, relation); } + private RuleChain updateAssignedEdge(TenantId tenantId, RuleChainId ruleChainId, Edge edge) { + RuleChain ruleChain = findRuleChainById(tenantId, ruleChainId); + if (ruleChain.updateAssignedEdge(edge)) { + return saveRuleChain(ruleChain); + } else { + return ruleChain; + } + } + private DataValidator ruleChainValidator = new DataValidator() { @Override @@ -420,4 +524,54 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC checkRuleNodesAndDelete(tenantId, entity.getId()); } }; + + private class EdgeRuleChainsUnassigner extends TimePaginatedRemover { + + private Edge edge; + + EdgeRuleChainsUnassigner(Edge edge) { + this.edge = edge; + } + + @Override + protected List findEntities(TenantId tenantId, Edge edge, TimePageLink pageLink) { + try { + return ruleChainDao.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId().getId(), edge.getId().getId(), pageLink).get(); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to get ruleChains by tenantId [{}] and edgeId [{}].", edge.getTenantId().getId(), edge.getId().getId()); + throw new RuntimeException(e); + } + } + + @Override + protected void removeEntity(TenantId tenantId, RuleChain entity) { + unassignRuleChainFromEdge(edge.getTenantId(), new RuleChainId(entity.getUuidId()), this.edge.getId()); + } + + } + + private class EdgeRuleChainsUpdater extends TimePaginatedRemover { + + private Edge edge; + + EdgeRuleChainsUpdater(Edge edge) { + this.edge = edge; + } + + @Override + protected List findEntities(TenantId tenantId, Edge edge, TimePageLink pageLink) { + try { + return ruleChainDao.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId().getId(), edge.getId().getId(), pageLink).get(); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to get ruleChains by tenantId [{}] and edgeId [{}].", edge.getTenantId().getId(), edge.getId().getId()); + throw new RuntimeException(e); + } + } + + @Override + protected void removeEntity(TenantId tenantId, RuleChain entity) { + updateAssignedEdge(edge.getTenantId(), new RuleChainId(entity.getUuidId()), this.edge); + } + + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java index 44c068d9cd..ce72819f0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java @@ -15,16 +15,26 @@ */ package org.thingsboard.server.dao.rule; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.nosql.RuleChainEntity; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; +import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.util.NoSqlDao; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -39,6 +49,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ @NoSqlDao public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao implements RuleChainDao { + @Autowired + private RelationDao relationDao; + @Override protected Class getColumnFamilyClass() { return RuleChainEntity.class; @@ -60,4 +73,17 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); + ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DASHBOARD, pageLink); + return Futures.transformAsync(relations, input -> { + List> ruleChainFutures = new ArrayList<>(input.size()); + for (EntityRelation relation : input) { + ruleChainFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); + } + return Futures.successfulAsList(ruleChainFutures); + }); + } + } 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 0b1bcd633d..fd319c480e 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 @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.rule; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.Dao; @@ -36,4 +38,14 @@ public interface RuleChainDao extends Dao { */ List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + /** + * Find rule chains by tenantId, edgeId and page link. + * + * @param tenantId the tenantId + * @param edgeId the edgeId + * @param pageLink the page link + * @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 3c26af35a0..f30063943c 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 @@ -15,20 +15,30 @@ */ package org.thingsboard.server.dao.sql.rule; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.RuleChainEntity; +import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -43,6 +53,9 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); + ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.RULE_CHAIN, pageLink); + return Futures.transformAsync(relations, input -> { + List> ruleChainFutures = new ArrayList<>(input.size()); + for (EntityRelation relation : input) { + ruleChainFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); + } + return Futures.successfulAsList(ruleChainFutures); + }); + } + } diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js index af314998da..b70c2b312b 100644 --- a/ui/src/app/api/dashboard.service.js +++ b/ui/src/app/api/dashboard.service.js @@ -309,7 +309,7 @@ function DashboardService($rootScope, $http, $q, $location, $filter) { delete dashboard.publicCustomerId; delete dashboard.assignedCustomersText; delete dashboard.assignedCustomersIds; - delete dashboard.assignedEdgeIds; + delete dashboard.assignedEdgesIds; return dashboard; } diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index 83d84ebdef..51b2ff9e1b 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -35,7 +35,13 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks, resolveTargetRuleChains: resolveTargetRuleChains, testScript: testScript, - getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput + getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput, + updateRuleChainEdges: updateRuleChainEdges, + addRuleChainEdges: addRuleChainEdges, + removeRuleChainEdges: removeRuleChainEdges, + getEdgeRuleChains: getEdgeRuleChains, + assignRuleChainToEdge: assignRuleChainToEdge, + unassignRuleChainFromEdge: unassignRuleChainFromEdge }; return service; @@ -53,7 +59,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co url += '&textOffset=' + pageLink.textOffset; } $http.get(url, config).then(function success(response) { - deferred.resolve(response.data); + deferred.resolve(prepareRuleChains(response.data)); }, function fail() { deferred.reject(); }); @@ -64,7 +70,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co var deferred = $q.defer(); var url = '/api/ruleChain/' + ruleChainId; $http.get(url, config).then(function success(response) { - deferred.resolve(response.data); + deferred.resolve(prepareRuleChain(response.data)); }, function fail() { deferred.reject(); }); @@ -74,8 +80,8 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co function saveRuleChain(ruleChain) { var deferred = $q.defer(); var url = '/api/ruleChain'; - $http.post(url, ruleChain).then(function success(response) { - deferred.resolve(response.data); + $http.post(url, cleanRuleChain(ruleChain)).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); }, function fail() { deferred.reject(); }); @@ -262,7 +268,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co var deferred = $q.defer(); getRuleChain(ruleChainId, {ignoreErrors: true}).then( (ruleChain) => { - deferred.resolve(ruleChain); + deferred.resolve(prepareRuleChain(ruleChain)); }, () => { deferred.resolve({ @@ -299,4 +305,101 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } + function updateRuleChainEdges(ruleChainId, edgeIds) { + var deferred = $q.defer(); + var url = '/api/ruleChain/' + ruleChainId + '/edges'; + $http.post(url, edgeIds).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function addRuleChainEdges(ruleChainId, edgeIds) { + var deferred = $q.defer(); + var url = '/api/ruleChain/' + ruleChainId + '/edges/add'; + $http.post(url, edgeIds).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function removeRuleChainEdges(ruleChainId, edgeIds) { + var deferred = $q.defer(); + var url = '/api/ruleChain/' + ruleChainId + '/edges/remove'; + $http.post(url, edgeIds).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function getEdgeRuleChains(edgeId, pageLink, config) { + var deferred = $q.defer(); + var url = '/api/edge/' + edgeId + '/ruleChains?limit=' + pageLink.limit; + if (angular.isDefined(pageLink.idOffset)) { + url += '&offset=' + pageLink.idOffset; + } + $http.get(url, config).then(function success(response) { + response.data = prepareRuleChains(response.data); + if (pageLink.textSearch) { + response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch}); + } + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function assignRuleChainToEdge(edgeId, ruleChainId) { + var deferred = $q.defer(); + var url = '/api/edge/' + edgeId + '/ruleChain/' + ruleChainId; + $http.post(url, null).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function unassignRuleChainFromEdge(edgeId, ruleChainId) { + var deferred = $q.defer(); + var url = '/api/edge/' + edgeId + '/ruleChain/' + ruleChainId; + $http.delete(url).then(function success(response) { + deferred.resolve(prepareRuleChain(response.data)); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function prepareRuleChains(ruleChainsData) { + if (ruleChainsData.data) { + for (var i = 0; i < ruleChainsData.data.length; i++) { + ruleChainsData.data[i] = prepareRuleChain(ruleChainsData.data[i]); + } + } + return ruleChainsData; + } + + function prepareRuleChain(ruleChain) { + ruleChain.assignedEdgesIds = []; + if (ruleChain.assignedEdges && ruleChain.assignedEdges.length) { + for (var j = 0; j < ruleChain.assignedEdges.length; j++) { + var assignedEdge = ruleChain.assignedEdges[j]; + ruleChain.assignedEdgesIds.push(assignedEdge.edgeId.id); + } + } + return ruleChain; + } + + function cleanRuleChain(ruleChain) { + delete ruleChain.assignedEdgesIds; + return ruleChain; + } } diff --git a/ui/src/app/dashboard/dashboards.controller.js b/ui/src/app/dashboard/dashboards.controller.js index 8f2c80a2c5..fab11b0b9c 100644 --- a/ui/src/app/dashboard/dashboards.controller.js +++ b/ui/src/app/dashboard/dashboards.controller.js @@ -157,6 +157,10 @@ export function DashboardsController(userService, dashboardService, customerServ ); } + if (edgeId) { + vm.edgeDashboardsTitle = $translate.instant('edge.dashboards'); + } + if (vm.dashboardsScope === 'tenant') { fetchDashboardsFunction = function (pageLink) { return dashboardService.getTenantDashboards(pageLink); @@ -217,10 +221,7 @@ export function DashboardsController(userService, dashboardService, customerServ }, name: function() { return $translate.instant('action.assign') }, details: function() { return $translate.instant('dashboard.manage-assigned-edges') }, - icon: "wifi_tethering", - isEnabled: function(dashboard) { - return dashboard; - } + icon: "wifi_tethering" }); /*dashboardActionsList.push({ @@ -750,7 +751,7 @@ export function DashboardsController(userService, dashboardService, customerServ $event.stopPropagation(); } $mdDialog.show({ - controller: 'ManageAssignedEdgesController', + controller: 'ManageAssignedEdgesToDashboardController', controllerAs: 'vm', templateUrl: manageAssignedEdgesTemplate, locals: {actionType: actionType, dashboardIds: dashboardIds, assignedEdges: assignedEdges}, diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js index 1d85abaf11..488cee3b39 100644 --- a/ui/src/app/dashboard/index.js +++ b/ui/src/app/dashboard/index.js @@ -42,7 +42,7 @@ import DashboardController from './dashboard.controller'; import DashboardSettingsController from './dashboard-settings.controller'; import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller'; import ManageAssignedCustomersController from './manage-assigned-customers.controller'; -import ManageAssignedEdgesController from './manage-assigned-edges.controller'; +import ManageAssignedEdgesToDashboardController from './manage-assigned-edges.controller'; import AddWidgetController from './add-widget.controller'; import DashboardDirective from './dashboard.directive'; import EditWidgetDirective from './edit-widget.directive'; @@ -78,7 +78,7 @@ export default angular.module('thingsboard.dashboard', [ .controller('DashboardSettingsController', DashboardSettingsController) .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController) .controller('ManageAssignedCustomersController', ManageAssignedCustomersController) - .controller('ManageAssignedEdgesController', ManageAssignedEdgesController) + .controller('ManageAssignedEdgesToDashboardController', ManageAssignedEdgesToDashboardController) .controller('AddWidgetController', AddWidgetController) .controller('AddDashboardsToEdgeController', AddDashboardsToEdgeController) .directive('tbDashboardDetails', DashboardDirective) diff --git a/ui/src/app/dashboard/manage-assigned-edges.controller.js b/ui/src/app/dashboard/manage-assigned-edges.controller.js index bb558ece36..13da16c87d 100644 --- a/ui/src/app/dashboard/manage-assigned-edges.controller.js +++ b/ui/src/app/dashboard/manage-assigned-edges.controller.js @@ -14,7 +14,7 @@ * limitations under the License. */ /*@ngInject*/ -export default function ManageAssignedEdgesController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedEdges) { +export default function ManageAssignedEdgesToDashboardController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedEdges) { var vm = this; diff --git a/ui/src/app/edge/edge.controller.js b/ui/src/app/edge/edge.controller.js index a9bef21ee0..6c200aebb6 100644 --- a/ui/src/app/edge/edge.controller.js +++ b/ui/src/app/edge/edge.controller.js @@ -229,6 +229,19 @@ export function EdgeController($rootScope, userService, edgeService, customerSer } ); + edgeActionsList.push( + { + onAction: function ($event, item) { + openEdgeRuleChains($event, item); + }, + name: function() { return $translate.instant('rulechain.rulechains') }, + details: function() { + return $translate.instant('edge.manage-edge-rulechains'); + }, + icon: "settings_ethernet" + } + ); + edgeActionsList.push( { onAction: function ($event, item) { @@ -554,10 +567,10 @@ export function EdgeController($rootScope, userService, edgeService, customerSer $state.go('home.edges.dashboards', {edgeId: edge.id.id}); } - // function openEdgeRuleChains($event, edge) { - // if ($event) { - // $event.stopPropagation(); - // } - // $state.go('home.edges.rule-chains', {edgeId: edge.id.id}); - // } + function openEdgeRuleChains($event, edge) { + if ($event) { + $event.stopPropagation(); + } + $state.go('home.edges.ruleChains', {edgeId: edge.id.id}); + } } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 1e17728095..8add2421dd 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -775,7 +775,9 @@ "assign-new-edge": "Assign new edge", "manage-edge-dashboards": "Manage edge dashboards", "unassign-from-edge": "Unassign from edge", - "dashboards": "Edge Dashboards" + "dashboards": "Edge Dashboards", + "manage-edge-rulechains": "Manage edge rule chains", + "rulechains": "Edge Rule Chains" }, "error": { "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", @@ -1380,7 +1382,28 @@ "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", "rulechain-required": "Rule chain is required", "management": "Rules management", - "debug-mode": "Debug mode" + "debug-mode": "Debug mode", + "assign-rulechains": "Assign rulechains", + "assign-new-rulechain": "Assign new rulechain", + "delete-rulechains": "Delete rulechains", + "unassign-rulechain": "Unassign rulechain", + "unassign-rulechains": "Unassign rulechains", + "unassign-rulechain-title": "Are you sure you want to unassign the rulechain '{{ruleChainTitle}}'?", + "unassign-rulechains-title": "Are you sure you want to unassign { count, plural, 1 {1 rulechain} other {# rulechains} }?", + "manage-assigned-edges": "Manage assigned edges", + "unassign-rulechain-from-edge-text": "After the confirmation the rulechain will be unassigned and won't be accessible by the edge.", + "assigned-edges": "Assigned edges", + "unassign-from-edge": "Unassign from edge", + "unassign-rulechains-from-edge-action-title": "Unassign { count, plural, 1 {1 rulechain} other {# rulechains} } from edge", + "unassign-rulechains-from-edge-text": "After the confirmation all selected rulechains will be unassigned and won't be accessible by the edge.", + "assign-rulechains-to-edge-text": "Assign { count, plural, 1 {1 rulechain} other {# rulechains} } to edges", + "assign-rulechain-to-edge": "Assign Rule Chain(s) To Edge", + "assign-rulechain-to-edge-text": "Please select the rulechains to assign to the edge", + "unassign-rulechains-from-edge-action-text": "Unassign { count, plural, 1 {1 rulechain} other {# rulechains} } from edges", + "assign-to-edges": "Assign Rule Chain(s) To Edges", + "assign-to-edges-text": "Please select the edges to assign the rulechain(s)", + "unassign-from-edges": "Unassign Rule Chain(s) From Edges", + "unassign-from-edges-text": "Please select the edges to unassign from the rulechain(s)" }, "rulenode": { "details": "Details", diff --git a/ui/src/app/rulechain/add-rulechains-to-edge.controller.js b/ui/src/app/rulechain/add-rulechains-to-edge.controller.js new file mode 100644 index 0000000000..5bc6e400ea --- /dev/null +++ b/ui/src/app/rulechain/add-rulechains-to-edge.controller.js @@ -0,0 +1,130 @@ +/* + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*@ngInject*/ +export default function AddRuleChainsToEdgeController(ruleChainService, $mdDialog, $q, $filter, edgeId, ruleChains) { + + var vm = this; + + vm.ruleChains = ruleChains; + vm.searchText = ''; + + vm.assign = assign; + vm.cancel = cancel; + vm.hasData = hasData; + vm.noData = noData; + vm.searchRuleChainTextUpdated = searchRuleChainTextUpdated; + vm.toggleRuleChainSelection = toggleRuleChainSelection; + + vm.theRuleChains = { + getItemAtIndex: function (index) { + if (index > 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(filterNonRootRuleChains(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 tasks = []; + for (var ruleChainId in vm.ruleChains.selections) { + tasks.push(ruleChainService.assignRuleChainToEdge(edgeId, ruleChainId)); + } + $q.all(tasks).then(function () { + $mdDialog.hide(); + }); + } + + function noData () { + return vm.ruleChains.data.length == 0 && !vm.ruleChains.hasNext; + } + + function hasData () { + return vm.ruleChains.data.length > 0; + } + + function toggleRuleChainSelection ($event, ruleChain) { + $event.stopPropagation(); + var selected = angular.isDefined(ruleChain.selected) && ruleChain.selected; + ruleChain.selected = !selected; + if (ruleChain.selected) { + vm.ruleChains.selections[ruleChain.id.id] = true; + vm.ruleChains.selectedCount++; + } else { + delete vm.ruleChains.selections[ruleChain.id.id]; + vm.ruleChains.selectedCount--; + } + } + + function searchRuleChainTextUpdated () { + vm.ruleChains = { + pageSize: vm.ruleChains.pageSize, + data: [], + nextPageLink: { + limit: vm.ruleChains.pageSize, + textSearch: vm.searchText + }, + selections: {}, + selectedCount: 0, + hasNext: true, + 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/add-rulechains-to-edge.tpl.html b/ui/src/app/rulechain/add-rulechains-to-edge.tpl.html new file mode 100644 index 0000000000..bc1ed253f6 --- /dev/null +++ b/ui/src/app/rulechain/add-rulechains-to-edge.tpl.html @@ -0,0 +1,77 @@ + + +
+ +
+

rulechain.assign-rulechain-to-edge

+ + + + +
+
+ + + +
+
+ rulechain.assign-rulechain-to-edge-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/rulechain/index.js b/ui/src/app/rulechain/index.js index b6af309a2a..64842983f9 100644 --- a/ui/src/app/rulechain/index.js +++ b/ui/src/app/rulechain/index.js @@ -24,6 +24,8 @@ import RuleNodeDirective from './rulenode.directive'; import LinkDirective from './link.directive'; import MessageTypeAutocompleteDirective from './message-type-autocomplete.directive'; import NodeScriptTest from './script/node-script-test.service'; +import AddRuleChainsToEdgeController from './add-rulechains-to-edge.controller'; +import ManageAssignedEdgesToRuleChainController from "./manage-assigned-edges.controller"; export default angular.module('thingsboard.ruleChain', []) .config(RuleChainRoutes) @@ -32,6 +34,8 @@ export default angular.module('thingsboard.ruleChain', []) .controller('AddRuleNodeController', AddRuleNodeController) .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) .controller('NodeScriptTestController', NodeScriptTestController) + .controller('ManageAssignedEdgesToRuleChainController', ManageAssignedEdgesToRuleChainController) + .controller('AddRuleChainsToEdgeController', AddRuleChainsToEdgeController) .directive('tbRuleChain', RuleChainDirective) .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective) .directive('tbRuleNodeConfig', RuleNodeConfigDirective) diff --git a/ui/src/app/rulechain/manage-assigned-edges.controller.js b/ui/src/app/rulechain/manage-assigned-edges.controller.js new file mode 100644 index 0000000000..0fcf91e7e3 --- /dev/null +++ b/ui/src/app/rulechain/manage-assigned-edges.controller.js @@ -0,0 +1,69 @@ +/* + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*@ngInject*/ +export default function ManageAssignedEdgesToRuleChainController($mdDialog, $q, types, ruleChainService, actionType, ruleChainIds, assignedEdges) { + + var vm = this; + + vm.types = types; + vm.actionType = actionType; + vm.ruleChainIds = ruleChainIds; + vm.assignedEdges = assignedEdges; + if (actionType != 'manage') { + vm.assignedEdges = []; + } + + if (actionType == 'manage') { + vm.titleText = 'rulechain.manage-assigned-edges'; + vm.labelText = 'rulechain.assigned-edges'; + vm.actionName = 'action.update'; + } else if (actionType == 'assign') { + vm.titleText = 'rulechain.assign-to-edges'; + vm.labelText = 'rulechain.assign-to-edges-text'; + vm.actionName = 'action.assign'; + } else if (actionType == 'unassign') { + vm.titleText = 'rulechain.unassign-from-edges'; + vm.labelText = 'rulechain.unassign-from-edges-text'; + vm.actionName = 'action.unassign'; + } + + vm.submit = submit; + vm.cancel = cancel; + + function cancel () { + $mdDialog.cancel(); + } + + function submit () { + var tasks = []; + for (var i=0;i + +
+ +
+

{{vm.titleText}}

+ + + + +
+
+ + + +
+
+ {{vm.labelText}} + +
+
+
+ + + + {{ vm.actionName | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index e5e76843fa..8a11c3dde1 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -41,7 +41,8 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider }, data: { searchEnabled: true, - pageTitle: 'rulechain.rulechains' + pageTitle: 'rulechain.rulechains', + ruleChainsType: 'tenant' }, ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' @@ -123,5 +124,26 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' } + }) + .state('home.edges.ruleChains', { + url: '/:edgeId/ruleChains', + params: {'topIndex': 0}, + module: 'private', + auth: ['TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainsTemplate, + controllerAs: 'vm', + controller: 'RuleChainsController' + } + }, + data: { + ruleChainsType: 'edge', + searchEnabled: true, + pageTitle: 'edge.rulechains' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}' + } }); -} +} \ 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 bbd9a4d69b..3ccee70f1b 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -17,12 +17,19 @@ import addRuleChainTemplate from './add-rulechain.tpl.html'; import ruleChainCard from './rulechain-card.tpl.html'; +import manageAssignedEdgesTemplate from "./manage-assigned-edges.tpl.html"; +import addRuleChainsToEdgeTemplate from "./add-rulechains-to-edge.tpl.html"; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ export default function RuleChainsController(ruleChainService, userService, importExport, $state, - $stateParams, $filter, $translate, $mdDialog, types) { + $stateParams, $filter, $translate, $mdDialog, $document, $q, types) { + + var vm = this; + var edgeId = $stateParams.edgeId; + + vm.ruleChainsScope = $state.$current.data.ruleChainsType; var ruleChainActionsList = [ { @@ -40,52 +47,10 @@ export default function RuleChainsController(ruleChainService, userService, impo name: function() { $translate.instant('action.export') }, details: function() { return $translate.instant('rulechain.export') }, icon: "file_download" - }, - { - onAction: function ($event, item) { - setRootRuleChain($event, item); - }, - name: function() { return $translate.instant('rulechain.set-root') }, - details: function() { return $translate.instant('rulechain.set-root') }, - icon: "flag", - isEnabled: isNonRootRuleChain - }, - { - onAction: function ($event, item) { - vm.grid.deleteItem($event, item); - }, - name: function() { return $translate.instant('action.delete') }, - details: function() { return $translate.instant('rulechain.delete') }, - icon: "delete", - isEnabled: isNonRootRuleChain } ]; - var ruleChainAddItemActionsList = [ - { - onAction: function ($event) { - vm.grid.addItem($event); - }, - name: function() { return $translate.instant('action.create') }, - details: function() { return $translate.instant('rulechain.create-new-rulechain') }, - icon: "insert_drive_file" - }, - { - onAction: function ($event) { - importExport.importRuleChain($event).then( - function(ruleChainImport) { - $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport}); - } - ); - }, - name: function() { return $translate.instant('action.import') }, - details: function() { return $translate.instant('rulechain.import') }, - icon: "file_upload" - } - ]; - - var vm = this; - + var ruleChainGroupActionsList = []; vm.types = types; vm.ruleChainGridConfig = { @@ -98,17 +63,15 @@ export default function RuleChainsController(ruleChainService, userService, impo deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, deleteItemsContentFunc: deleteRuleChainsText, - fetchItemsFunc: fetchRuleChains, saveItemFunc: saveRuleChain, clickItemFunc: openRuleChain, - deleteItemFunc: deleteRuleChain, getItemTitleFunc: getRuleChainTitle, itemCardTemplateUrl: ruleChainCard, parentCtl: vm, actionsList: ruleChainActionsList, - addItemActions: ruleChainAddItemActionsList, + groupActionsList: ruleChainGroupActionsList, onGridInited: gridInited, @@ -133,7 +96,160 @@ export default function RuleChainsController(ruleChainService, userService, impo vm.exportRuleChain = exportRuleChain; vm.setRootRuleChain = setRootRuleChain; + + initController(); + function initController() { + var fetchRuleChainsFunction = null; + var deleteRuleChainFunction = null; + + if (edgeId) { + vm.edgeRuleChainsTitle = $translate.instant('edge.rulechains'); + } + + if (vm.ruleChainsScope === 'tenant') { + fetchRuleChainsFunction = function (pageLink) { + return fetchRuleChains(pageLink); + }; + deleteRuleChainFunction = function (ruleChainId) { + return deleteRuleChain(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) { + manageAssignedEdges($event, item); + }, + name: function() { return $translate.instant('action.assign') }, + details: function() { return $translate.instant('rulechain.manage-assigned-edges') }, + icon: "wifi_tethering", + isEnabled: isNonRootRuleChain + }); + + ruleChainActionsList.push({ + onAction: function ($event, item) { + vm.grid.deleteItem($event, item); + }, + name: function() { return $translate.instant('action.delete') }, + details: function() { return $translate.instant('rulechain.delete') }, + icon: "delete", + isEnabled: isNonRootRuleChain + }); + + ruleChainGroupActionsList.push( + { + onAction: function ($event, items) { + assignRuleChainsToEdges($event, items); + }, + name: function() { return $translate.instant('rulechain.assign-rulechains') }, + details: function(selectedCount) { + return $translate.instant('rulechain.assign-rulechains-to-edge-text', {count: selectedCount}, "messageformat"); + }, + icon: "wifi_tethering" + } + ); + + ruleChainGroupActionsList.push( + { + onAction: function ($event, items) { + unassignRuleChainsFromEdges($event, items); + }, + name: function() { return $translate.instant('rulechain.unassign-rulechains') }, + details: function(selectedCount) { + return $translate.instant('rulechain.unassign-rulechains-from-edge-action-text', {count: selectedCount}, "messageformat"); + }, + icon: "portable_wifi_off" + } + ); + + ruleChainGroupActionsList.push( + { + onAction: function ($event) { + vm.grid.deleteItems($event); + }, + name: function() { return $translate.instant('rulechain.delete-rulechains') }, + details: deleteRuleChainsActionTitle, + icon: "delete" + } + ); + + vm.ruleChainGridConfig.addItemActions = []; + vm.ruleChainGridConfig.addItemActions.push({ + onAction: function ($event) { + vm.grid.addItem($event); + }, + name: function() { return $translate.instant('action.create') }, + details: function() { return $translate.instant('rulechain.create-new-rulechain') }, + icon: "insert_drive_file" + }); + vm.ruleChainGridConfig.addItemActions.push({ + onAction: function ($event) { + importExport.importRuleChain($event).then( + function(ruleChainImport) { + $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport}); + } + ); + }, + name: function() { return $translate.instant('action.import') }, + details: function() { return $translate.instant('rulechain.import') }, + icon: "file_upload" + }); + + } else if (vm.ruleChainsScope === 'edge') { + fetchRuleChainsFunction = function (pageLink) { + return ruleChainService.getEdgeRuleChains(edgeId, pageLink); + }; + deleteRuleChainFunction = function (ruleChainId) { + return ruleChainService.unassignRuleChainFromEdge(edgeId, ruleChainId); + }; + + ruleChainActionsList.push( + { + onAction: function ($event, item) { + unassignFromEdge($event, item, edgeId); + }, + name: function() { return $translate.instant('action.unassign') }, + details: function() { return $translate.instant('rulechain.unassign-from-edge') }, + icon: "assignment_return" + } + ); + + ruleChainGroupActionsList.push( + { + onAction: function ($event, items) { + unassignRuleChainsFromEdge($event, items, edgeId); + }, + name: function() { return $translate.instant('rulechain.unassign-rulechains') }, + details: function(selectedCount) { + return $translate.instant('rulechain.unassign-rulechains-from-edge-action-title', {count: selectedCount}, "messageformat"); + }, + icon: "assignment_return" + } + ); + + vm.ruleChainGridConfig.addItemAction = { + onAction: function ($event) { + addRuleChainsToEdge($event); + }, + name: function() { return $translate.instant('rulechain.assign-rulechains') }, + details: function() { return $translate.instant('rulechain.assign-new-rulechain') }, + icon: "add" + } + } + + vm.ruleChainGridConfig.fetchItemsFunc = fetchRuleChainsFunction; + vm.ruleChainGridConfig.deleteItemFunc = deleteRuleChainFunction; + } + function deleteRuleChainTitle(ruleChain) { return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); } @@ -210,6 +326,123 @@ export default function RuleChainsController(ruleChainService, userService, impo } ); }); + } + + function manageAssignedEdges($event, ruleChain) { + showManageAssignedEdgesDialog($event, [ruleChain.id.id], 'manage', ruleChain.assignedEdgesIds); + } + + function assignRuleChainsToEdges($event, items) { + var ruleChainIds = []; + for (var id in items.selections) { + ruleChainIds.push(id); + } + showManageAssignedEdgesDialog($event, ruleChainIds, 'assign'); + } + + function unassignRuleChainsFromEdges($event, items) { + var ruleChainIds = []; + for (var id in items.selections) { + ruleChainIds.push(id); + } + showManageAssignedEdgesDialog($event, ruleChainIds, 'unassign'); + } + + function unassignRuleChainsFromEdge($event, items, edgeId) { + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('rulechain.unassign-rulechains-title', {count: items.selectedCount}, 'messageformat')) + .htmlContent($translate.instant('rulechain.unassign-rulechains-from-edge-text')) + .ariaLabel($translate.instant('rulechain.unassign-rulechains')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var tasks = []; + for (var id in items.selections) { + tasks.push(ruleChainService.unassignRuleChainFromEdge(edgeId, id)); + } + $q.all(tasks).then(function () { + vm.grid.refreshList(); + }); + }); + } + + function showManageAssignedEdgesDialog($event, ruleChainIds, actionType, assignedEdges) { + if ($event) { + $event.stopPropagation(); + } + $mdDialog.show({ + controller: 'ManageAssignedEdgesToRuleChainController', + controllerAs: 'vm', + templateUrl: manageAssignedEdgesTemplate, + locals: {actionType: actionType, ruleChainIds: ruleChainIds, assignedEdges: assignedEdges}, + parent: angular.element($document[0].body), + fullscreen: true, + targetEvent: $event + }).then(function () { + vm.grid.refreshList(); + }, function () { + }); + } + function addRuleChainsToEdge($event) { + if ($event) { + $event.stopPropagation(); + } + var pageSize = 10; + ruleChainService.getRuleChains({limit: pageSize, textSearch: ''}).then( + function success(_ruleChains) { + var ruleChains = { + pageSize: pageSize, + data: filterNonRootRuleChains(_ruleChains.data), + nextPageLink: _ruleChains.nextPageLink, + selections: {}, + selectedCount: 0, + hasNext: _ruleChains.hasNext, + pending: false + }; + if (ruleChains.hasNext) { + ruleChains.nextPageLink.limit = pageSize; + } + $mdDialog.show({ + controller: 'AddRuleChainsToEdgeController', + controllerAs: 'vm', + templateUrl: addRuleChainsToEdgeTemplate, + locals: {edgeId: edgeId, ruleChains: ruleChains}, + parent: angular.element($document[0].body), + fullscreen: true, + targetEvent: $event + }).then(function () { + vm.grid.refreshList(); + }, function () { + }); + }, + function fail() { + }); + } + + function filterNonRootRuleChains(ruleChains) { + return $filter('filter')(ruleChains, isNonRootRuleChain); + } + + function unassignFromEdge($event, ruleChain, edgeId) { + if ($event) { + $event.stopPropagation(); + } + var title = $translate.instant('rulechain.unassign-rulechain-title', {ruleChainTitle: ruleChain.name}); + var content = $translate.instant('rulechain.unassign-rulechain-from-edge-text'); + var label = $translate.instant('rulechain.unassign-rulechain'); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(label) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + ruleChainService.unassignRuleChainFromEdge(edgeId, ruleChain.id.id).then(function success() { + vm.grid.refreshList(); + }); + }); } }