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 10df2fc09c..818298fa8c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -55,6 +55,7 @@ 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; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -231,13 +232,19 @@ public class RuleChainController extends BaseController { @ResponseBody public TextPageData getRuleChains( @RequestParam int limit, + @RequestParam(value = "type", required = false) String typeStr, @RequestParam(required = false) String textSearch, @RequestParam(required = false) String idOffset, @RequestParam(required = false) String textOffset) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); - return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink)); + if (typeStr != null && typeStr.trim().length() > 0) { + RuleChainType type = RuleChainType.valueOf(typeStr); + return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink)); + } else { + return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink)); + } } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 6355de537c..7620335aed 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeQueueEntry; import org.thingsboard.server.common.data.id.AssetId; @@ -44,10 +45,12 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; @@ -59,6 +62,7 @@ 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.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -91,6 +95,7 @@ import org.thingsboard.server.gen.edge.UplinkResponseMsg; import org.thingsboard.server.gen.edge.UserUpdateMsg; import org.thingsboard.server.service.edge.EdgeContextComponent; +import javax.swing.text.html.parser.Entity; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -102,6 +107,9 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import java.util.function.Consumer; +import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE; +import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; + @Slf4j @Data public final class EdgeGrpcSession implements Cloneable { @@ -175,7 +183,9 @@ public final class EdgeGrpcSession implements Cloneable { do { pageData = ctx.getEdgeService().findQueueEvents(edge.getTenantId(), edge.getId(), pageLink); if (!pageData.getData().isEmpty()) { + log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size()); for (Event event : pageData.getData()) { + log.trace("[{}] Processing event [{}]", this.sessionId, event); EdgeQueueEntry entry; try { entry = objectMapper.treeToValue(event.getBody(), EdgeQueueEntry.class); @@ -193,6 +203,9 @@ public final class EdgeGrpcSession implements Cloneable { processCustomDownlinkMessage(entry); break; } + if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { + pushEntityAttributesToEdge(entry); + } } catch (Exception e) { log.error("Exception during processing records from queue", e); } @@ -215,6 +228,63 @@ public final class EdgeGrpcSession implements Cloneable { } } + private void pushEntityAttributesToEdge(EdgeQueueEntry entry) throws IOException { + EntityId entityId = null; + String entityName = null; + switch (entry.getEntityType()) { + case EDGE: + entityId = objectMapper.readValue(entry.getData(), Edge.class).getId(); + break; + case DEVICE: + entityId = objectMapper.readValue(entry.getData(), Device.class).getId(); + break; + case ASSET: + entityId = objectMapper.readValue(entry.getData(), Asset.class).getId(); + break; + case ENTITY_VIEW: + entityId = objectMapper.readValue(entry.getData(), EntityView.class).getId(); + break; + case DASHBOARD: + entityId = objectMapper.readValue(entry.getData(), Dashboard.class).getId(); + break; + } + if (entityId != null) { + ListenableFuture> ssAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SERVER_SCOPE); + Futures.transform(ssAttrFuture, ssAttributes -> { + if (ssAttributes != null && !ssAttributes.isEmpty()) { + try { + TbMsgMetaData metaData = new TbMsgMetaData(); + ObjectNode entityNode = objectMapper.createObjectNode(); + metaData.putValue("scope", DataConstants.SERVER_SCOPE); + for (AttributeKvEntry attr : ssAttributes) { + if (attr.getDataType() == DataType.BOOLEAN) { + entityNode.put(attr.getKey(), attr.getBooleanValue().get()); + } else if (attr.getDataType() == DataType.DOUBLE) { + entityNode.put(attr.getKey(), attr.getDoubleValue().get()); + } else if (attr.getDataType() == DataType.LONG) { + entityNode.put(attr.getKey(), attr.getLongValue().get()); + } else { + entityNode.put(attr.getKey(), attr.getValueAsString()); + } + } + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, TbMsgDataType.JSON + , objectMapper.writeValueAsString(entityNode) + , null, null, 0L); + log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg); + outputStream.onNext(ResponseMsg.newBuilder() + .setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, tbMsg)) + .build()); + } catch (Exception e) { + log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e); + } + } + return null; + }); + ListenableFuture> shAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SHARED_SCOPE); + ListenableFuture> clAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.CLIENT_SCOPE); + } + } + private void processCustomDownlinkMessage(EdgeQueueEntry entry) throws IOException { log.trace("Executing processCustomDownlinkMessage, entry [{}]", entry); TbMsg tbMsg = objectMapper.readValue(entry.getData(), TbMsg.class); @@ -411,7 +481,7 @@ public final class EdgeGrpcSession implements Cloneable { return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; case DataConstants.ENTITY_CREATED: case DataConstants.ENTITY_ASSIGNED_TO_EDGE: - return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE; + return ENTITY_CREATED_RPC_MESSAGE; case DataConstants.ENTITY_DELETED: case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE: return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE; @@ -521,6 +591,8 @@ public final class EdgeGrpcSession implements Cloneable { private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException { return RuleNodeProto.newBuilder() + .setIdMSB(node.getId().getId().getMostSignificantBits()) + .setIdLSB(node.getId().getId().getLeastSignificantBits()) .setType(node.getType()) .setName(node.getName()) .setDebugMode(node.isDebugMode()) 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 798b92ec66..eca4c94111 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 @@ -27,6 +27,7 @@ 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; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import java.util.List; @@ -62,6 +63,8 @@ public interface RuleChainService { TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); + TextPageData findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink); + void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId); void deleteRuleChainsByTenantId(TenantId tenantId); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 1b54e0902a..51e1ed3df3 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -125,11 +125,13 @@ message RuleChainMetadataUpdateMsg { } message RuleNodeProto { - string type = 1; - string name = 2; - bool debugMode = 3; - string configuration = 4; - string additionalInfo = 5; + int64 idMSB = 1; + int64 idLSB = 2; + string type = 3; + string name = 4; + bool debugMode = 5; + string configuration = 6; + string additionalInfo = 7; } message NodeConnectionInfoProto { diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java index b12e114d93..73b519ec60 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java @@ -113,16 +113,16 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic @Override protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) { if (plugin.getType() == null) { - throw new DataValidationException("Component type should be specified!."); + throw new DataValidationException("Component type should be specified!"); } if (plugin.getScope() == null) { - throw new DataValidationException("Component scope should be specified!."); + throw new DataValidationException("Component scope should be specified!"); } if (StringUtils.isEmpty(plugin.getName())) { - throw new DataValidationException("Component name should be specified!."); + throw new DataValidationException("Component name should be specified!"); } if (StringUtils.isEmpty(plugin.getClazz())) { - throw new DataValidationException("Component clazz should be specified!."); + throw new DataValidationException("Component clazz should be specified!"); } } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 7691484d68..fe859a2f1b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -54,7 +54,7 @@ public class BaseEventService implements EventService { public Optional saveIfNotExists(Event event) { eventValidator.validate(event, Event::getTenantId); if (StringUtils.isEmpty(event.getUid())) { - throw new DataValidationException("Event uid should be specified!."); + throw new DataValidationException("Event uid should be specified!"); } return eventDao.saveIfNotExists(event); } @@ -62,16 +62,16 @@ public class BaseEventService implements EventService { @Override public Optional findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid) { if (tenantId == null) { - throw new DataValidationException("Tenant id should be specified!."); + throw new DataValidationException("Tenant id should be specified!"); } if (entityId == null) { - throw new DataValidationException("Entity id should be specified!."); + throw new DataValidationException("Entity id should be specified!"); } if (StringUtils.isEmpty(eventType)) { - throw new DataValidationException("Event type should be specified!."); + throw new DataValidationException("Event type should be specified!"); } if (StringUtils.isEmpty(eventUid)) { - throw new DataValidationException("Event uid should be specified!."); + throw new DataValidationException("Event uid should be specified!"); } Event event = eventDao.findEvent(tenantId.getId(), entityId, eventType, eventUid); return event != null ? Optional.of(event) : Optional.empty(); @@ -99,13 +99,13 @@ public class BaseEventService implements EventService { @Override protected void validateDataImpl(TenantId tenantId, Event event) { if (event.getEntityId() == null) { - throw new DataValidationException("Entity id should be specified!."); + throw new DataValidationException("Entity id should be specified!"); } if (StringUtils.isEmpty(event.getType())) { - throw new DataValidationException("Event type should be specified!."); + throw new DataValidationException("Event type should be specified!"); } if (event.getBody() == null) { - throw new DataValidationException("Event body should be specified!."); + throw new DataValidationException("Event body should be specified!"); } } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index d64d620b7c..5c9d24d265 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -343,6 +343,7 @@ public class ModelConstants { public static final String RULE_CHAIN_ASSIGNED_EDGES_PROPERTY = "assigned_edges"; public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text"; + public static final String RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_by_type_and_search_text"; /** * Cassandra rule node constants. 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 268dd1a02e..433648a751 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 @@ -106,11 +106,7 @@ public class RuleChainEntity implements SearchTextEntity { } this.tenantId = DaoUtil.getId(ruleChain.getTenantId()); this.name = ruleChain.getName(); - if (ruleChain.getType() != null) { - this.type = ruleChain.getType(); - } else { - this.type = RuleChainType.SYSTEM; - } + this.type = ruleChain.getType(); this.searchText = ruleChain.getName(); this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId()); this.root = ruleChain.isRoot(); @@ -204,11 +200,7 @@ public class RuleChainEntity implements SearchTextEntity { ruleChain.setCreatedTime(UUIDs.unixTimestamp(id)); ruleChain.setTenantId(new TenantId(tenantId)); ruleChain.setName(name); - if (type != null) { - ruleChain.setType(type); - } else { - ruleChain.setType(RuleChainType.SYSTEM); - } + ruleChain.setType(type); if (this.firstRuleNodeId != null) { ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId)); } 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 cb17e783e1..29e7cf6750 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 @@ -103,11 +103,7 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT } this.tenantId = toString(DaoUtil.getId(ruleChain.getTenantId())); this.name = ruleChain.getName(); - if (ruleChain.getType() != null) { - this.type = ruleChain.getType(); - } else { - this.type = RuleChainType.SYSTEM; - } + this.type = ruleChain.getType(); this.searchText = ruleChain.getName(); if (ruleChain.getFirstRuleNodeId() != null) { this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId()); @@ -141,11 +137,7 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT ruleChain.setCreatedTime(UUIDs.unixTimestamp(getId())); ruleChain.setTenantId(new TenantId(toUUID(tenantId))); ruleChain.setName(name); - if (type != null) { - ruleChain.setType(type); - } else { - ruleChain.setType(RuleChainType.SYSTEM); - } + ruleChain.setType(type); if (firstRuleNodeId != null) { ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId))); } 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 b42445fae3..3c3433eb3c 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 @@ -63,6 +63,8 @@ import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.dao.service.Validator.validateString; + /** * Created by igor on 3/12/18. */ @@ -358,6 +360,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return new TextPageData<>(ruleChains, pageLink); } + @Override + public TextPageData findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink) { + Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request."); + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request."); + List ruleChains = ruleChainDao.findRuleChainsByTenantIdAndType(tenantId.getId(), type, pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + @Override public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); @@ -514,7 +524,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) { if (StringUtils.isEmpty(ruleChain.getName())) { - throw new DataValidationException("Rule chain name should be specified!."); + throw new DataValidationException("Rule chain name should be specified!"); + } + if (ruleChain.getType() == null) { + throw new DataValidationException("Rule chain type should be specified!"); } if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) { throw new DataValidationException("Rule chain should be assigned to tenant!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java index ce72819f0a..44613cb6fe 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 @@ -28,6 +28,7 @@ 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.common.data.rule.RuleChainType; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.nosql.RuleChainEntity; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; @@ -35,14 +36,19 @@ import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.util.NoSqlDao; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TENANT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TYPE_PROPERTY; @Component @Slf4j @@ -73,6 +79,17 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink); + List ruleChainEntities = findPageWithTextSearch(new TenantId(tenantId), RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Arrays.asList(eq(RULE_CHAIN_TYPE_PROPERTY, type), + eq(RULE_CHAIN_TENANT_ID_PROPERTY, tenantId)), + pageLink); + log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); + return DaoUtil.convertDataList(ruleChainEntities); + } + @Override public ListenableFuture> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) { log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 7c0747284a..d024951756 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 @@ -19,6 +19,7 @@ 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.common.data.rule.RuleChainType; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -38,6 +39,16 @@ public interface RuleChainDao extends Dao { */ List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + /** + * Find rule chains by tenantId, type and page link. + * + * @param tenantId the tenantId + * @param type the type + * @param pageLink the page link + * @return the list of rule chain objects + */ + List findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink); + /** * Find rule chains by tenantId, edgeId and page link. * 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 3cf7bdb860..f325b7c84b 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 @@ -31,6 +31,7 @@ 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.common.data.rule.RuleChainType; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.RuleChainEntity; import org.thingsboard.server.dao.relation.RelationDao; @@ -68,6 +69,7 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); return DaoUtil.convertDataList(ruleChainRepository .findByTenantId( UUIDConverter.fromTimeUUID(tenantId), @@ -76,6 +78,18 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink); + return DaoUtil.convertDataList(ruleChainRepository + .findByTenantIdAndType( + UUIDConverter.fromTimeUUID(tenantId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } + @Override public ListenableFuture> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) { log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index b1e8200132..9276fb2d99 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -19,6 +19,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.model.sql.RuleChainEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -35,4 +36,13 @@ public interface RuleChainRepository extends CrudRepository :idOffset ORDER BY rc.id") + List findByTenantIdAndType(@Param("tenantId") String tenantId, + @Param("type") RuleChainType type, + @Param("searchText") String searchText, + @Param("idOffset") String idOffset, + Pageable pageable); } diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql index afc9f96877..3bb1476a34 100644 --- a/dao/src/main/resources/cassandra/schema-entities.cql +++ b/dao/src/main/resources/cassandra/schema-entities.cql @@ -597,22 +597,30 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( id uuid, tenant_id uuid, name text, + type text, search_text text, first_rule_node_id uuid, root boolean, debug_mode boolean, configuration text, additional_info text, - PRIMARY KEY (id, tenant_id) + PRIMARY KEY (id, tenant_id, type) ); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS SELECT * from thingsboard.rule_chain - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL + PRIMARY KEY ( tenant_id, search_text, id, type ) WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_by_type_and_search_text AS + SELECT * + from thingsboard.rule_chain + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL + PRIMARY KEY ( tenant_id, type, search_text, id ) + WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC ); + CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( id uuid, rule_chain_id uuid, diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 4700b06e99..e32821458f 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -277,7 +277,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } break; case types.entityType.rulechain: - promise = ruleChainService.getRuleChains(pageLink, config); + promise = ruleChainService.getRuleChains(pageLink, config, subType); break; case types.entityType.dashboard: if (user.authority === 'CUSTOMER_USER') { diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index c511f4ac05..31f0ade766 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -46,7 +46,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return service; - function getRuleChains (pageLink, config) { + function getRuleChains (pageLink, config, type) { var deferred = $q.defer(); var url = '/api/ruleChains?limit=' + pageLink.limit; if (angular.isDefined(pageLink.textSearch)) { @@ -58,6 +58,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co if (angular.isDefined(pageLink.textOffset)) { url += '&textOffset=' + pageLink.textOffset; } + if (angular.isDefined(type) && type.length) { + url += '&type=' + type; + } $http.get(url, config).then(function success(response) { deferred.resolve(prepareRuleChains(response.data)); }, function fail() { diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 678f72e309..3613a2befc 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -607,7 +607,7 @@ export default angular.module('thingsboard.types', []) } }, systemRuleChainType: "SYSTEM", - ruleChainTypes: ["SYSTEM", "EDGE"], + edgeRuleChainType: "EDGE", ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"], ruleChainNodeComponent: { type: 'RULE_CHAIN', diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 7f3cf09e21..b0fdc8255d 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1391,10 +1391,11 @@ "rulechain": { "rulechain": "Rule chain", "rulechains": "Rule chains", + "system-rulechains": "System Rule chains", + "edge-rulechains": "Edge Rule chains", "root": "Root", "delete": "Delete rule chain", "name": "Name", - "type": "Type", "name-required": "Name is required.", "description": "Description", "add": "Add Rule Chain", diff --git a/ui/src/app/rulechain/add-rulechain.tpl.html b/ui/src/app/rulechain/add-rulechain.tpl.html index f27b8a8c9c..afa765fc43 100644 --- a/ui/src/app/rulechain/add-rulechain.tpl.html +++ b/ui/src/app/rulechain/add-rulechain.tpl.html @@ -31,7 +31,7 @@
- +
diff --git a/ui/src/app/rulechain/add-rulenode.tpl.html b/ui/src/app/rulechain/add-rulenode.tpl.html index 8af24d8bd2..bb8eab90ee 100644 --- a/ui/src/app/rulechain/add-rulenode.tpl.html +++ b/ui/src/app/rulechain/add-rulenode.tpl.html @@ -31,7 +31,7 @@
- +
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index 69a8089fd1..e0718273eb 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -49,14 +49,6 @@
rulechain.name-required
- - - - - {{ruleChainType}} - - - {{ 'rulechain.debug-mode' | translate }} diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 195dcffef5..96065aab68 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -1179,6 +1179,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time function saveRuleChain() { var saveRuleChainPromise; if (vm.isImport) { + if (angular.isUndefined(vm.ruleChain.type)) { + vm.ruleChain.type = types.systemRuleChainType; + } saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain); } else { saveRuleChainPromise = $q.when(vm.ruleChain); @@ -1286,6 +1289,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null; + var ruleChainType = vm.ruleChain.type ? vm.ruleChain.type : types.systemRuleChainType; vm.enableHotKeys = false; @@ -1294,7 +1298,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time controllerAs: 'vm', templateUrl: addRuleNodeTemplate, parent: angular.element($document[0].body), - locals: {ruleNode: ruleNode, ruleChainId: ruleChainId}, + locals: {ruleNode: ruleNode, ruleChainId: ruleChainId, ruleChainType: ruleChainType}, fullscreen: true, targetEvent: $event }).then(function (ruleNode) { @@ -1365,13 +1369,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time } /*@ngInject*/ -export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) { +export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, ruleChainType, helpLinks) { var vm = this; vm.helpLinks = helpLinks; vm.ruleNode = ruleNode; vm.ruleChainId = ruleChainId; + vm.ruleChainType = ruleChainType; vm.add = add; vm.cancel = cancel; diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js index 31c2817d99..899e1ade5d 100644 --- a/ui/src/app/rulechain/rulechain.directive.js +++ b/ui/src/app/rulechain/rulechain.directive.js @@ -26,12 +26,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, var template = $templateCache.get(ruleChainFieldsetTemplate); element.html(template); - scope.ruleChainTypes = types.ruleChainTypes; - - if (angular.isDefined(scope.ruleChain) && scope.ruleChain != null && angular.isUndefined(scope.ruleChain.type)) { - scope.ruleChain.type = types.systemRuleChainType; - } - scope.onRuleChainIdCopied = function() { toast.showSuccess($translate.instant('rulechain.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); }; @@ -44,7 +38,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, scope: { ruleChain: '=', isEdit: '=', - ruleChainScope: '=', isReadOnly: '=', theForm: '=', onSetRootRuleChain: '&', diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 2236be2796..114e458b1c 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -29,6 +29,15 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider $stateProvider .state('home.ruleChains', { url: '/ruleChains', + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + redirectTo: 'home.ruleChains.system', + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' + } + }) + .state('home.ruleChains.system', { + url: '/ruleChains/system', params: {'topIndex': 0}, module: 'private', auth: ['SYS_ADMIN', 'TENANT_ADMIN'], @@ -41,11 +50,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider }, data: { searchEnabled: true, - pageTitle: 'rulechain.rulechains', + pageTitle: 'rulechain.system-rulechains', ruleChainsType: 'tenant' }, ncyBreadcrumb: { - label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' + label: '{"icon": "settings_ethernet", "label": "rulechain.system-rulechains"}' } }).state('home.ruleChains.ruleChain', { url: '/:ruleChainId', @@ -124,8 +133,64 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' } - }) - .state('home.edges.ruleChains', { + }).state('home.ruleChains.edge', { + url: '/ruleChains/edge', + params: {'topIndex': 0}, + module: 'private', + auth: ['TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainsTemplate, + controllerAs: 'vm', + controller: 'RuleChainsController' + } + }, + data: { + searchEnabled: true, + pageTitle: 'rulechain.edge-rulechains', + ruleChainsType: 'edges' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "rulechain.edge-rulechains"}' + } + }).state('home.ruleChains.edge.ruleChain', { + url: '/:ruleChainId', + reloadOnSearch: false, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainTemplate, + controller: 'RuleChainController', + controllerAs: 'vm' + } + }, + resolve: { + ruleChain: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChain($stateParams.ruleChainId); + }, + ruleChainMetaData: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); + }, + ruleNodeComponents: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleNodeComponents(); + } + }, + data: { + import: false, + searchEnabled: false, + pageTitle: 'edge.rulechain' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' + } + }).state('home.edges.ruleChains', { url: '/:edgeId/ruleChains', params: {'topIndex': 0}, module: 'private', @@ -138,15 +203,14 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider } }, data: { - ruleChainsType: 'edge', searchEnabled: true, - pageTitle: 'edge.rulechains' + pageTitle: 'edge.rulechains', + ruleChainsType: 'edge' }, ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}' } - }) - .state('home.edges.ruleChains.ruleChain', { + }).state('home.edges.ruleChains.ruleChain', { url: '/:ruleChainId', reloadOnSearch: false, module: 'private', @@ -181,7 +245,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider pageTitle: 'edge.rulechain' }, ncyBreadcrumb: { - label: '{"icon": "settings_ethernet", "label": "edge.rulechain"}' + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "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 3662b08db5..b33a992742 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -116,7 +116,7 @@ export default function RuleChainsController(ruleChainService, userService, edge if (vm.ruleChainsScope === 'tenant') { fetchRuleChainsFunction = function (pageLink) { - return fetchRuleChains(pageLink); + return fetchRuleChains(pageLink, 'SYSTEM'); }; deleteRuleChainFunction = function (ruleChainId) { return deleteRuleChain(ruleChainId); @@ -132,6 +132,57 @@ export default function RuleChainsController(ruleChainService, userService, edge 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) { + 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 === 'edges') { + fetchRuleChainsFunction = function (pageLink) { + return fetchRuleChains(pageLink, 'EDGE'); + }; + deleteRuleChainFunction = function (ruleChainId) { + return deleteRuleChain(ruleChainId); + }; + ruleChainActionsList.push({ onAction: function ($event, item) { manageAssignedEdges($event, item); @@ -209,7 +260,6 @@ export default function RuleChainsController(ruleChainService, userService, edge details: function() { return $translate.instant('rulechain.import') }, icon: "file_upload" }); - } else if (vm.ruleChainsScope === 'edge') { fetchRuleChainsFunction = function (pageLink) { return ruleChainService.getEdgeRuleChains(edgeId, pageLink); @@ -291,11 +341,18 @@ export default function RuleChainsController(ruleChainService, userService, edge vm.grid = grid; } - function fetchRuleChains(pageLink) { - return ruleChainService.getRuleChains(pageLink); + function fetchRuleChains(pageLink, type) { + return ruleChainService.getRuleChains(pageLink, null, type); } function saveRuleChain(ruleChain) { + if (angular.isUndefined(ruleChain.type)) { + if (vm.ruleChainsScope === 'edges') { + ruleChain.type = types.edgeRuleChainType; + } else { + ruleChain.type = types.systemRuleChainType; + } + } return ruleChainService.saveRuleChain(ruleChain); } @@ -303,10 +360,11 @@ export default function RuleChainsController(ruleChainService, userService, edge if ($event) { $event.stopPropagation(); } + if (vm.ruleChainsScope === 'edge') { - $state.go('home.edges.ruleChains.ruleChain', { - ruleChainId: ruleChain.id.id - }); + $state.go('home.edges.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id, edgeId: vm.edge.id.id}); + } else if (vm.ruleChainsScope === 'edges') { + $state.go('home.ruleChains.edge.ruleChain', {ruleChainId: ruleChain.id.id}); } else { $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id}); } diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html index c2463c07f3..5106cc4f1c 100644 --- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html @@ -53,6 +53,7 @@ tb-required="true" exclude-entity-ids="[ruleChainId]" entity-type="types.entityType.rulechain" + entity-subtype="ruleChainType" ng-model="params.targetRuleChainId"> diff --git a/ui/src/app/rulechain/rulenode.directive.js b/ui/src/app/rulechain/rulenode.directive.js index 926aea4376..0899f38edc 100644 --- a/ui/src/app/rulechain/rulenode.directive.js +++ b/ui/src/app/rulechain/rulenode.directive.js @@ -70,6 +70,7 @@ export default function RuleNodeDirective($compile, $templateCache, ruleChainSer link: linker, scope: { ruleChainId: '=', + ruleChainType: '=', ruleNode: '=', isEdit: '=', isReadOnly: '=', diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js index 4a3c43b75a..a7e80ebcd8 100644 --- a/ui/src/app/services/menu.service.js +++ b/ui/src/app/services/menu.service.js @@ -156,9 +156,24 @@ function Menu(userService, $state, $rootScope) { }, { name: 'rulechain.rulechains', - type: 'link', + type: 'toggle', state: 'home.ruleChains', - icon: 'settings_ethernet' + height: '80px', + icon: 'settings_ethernet', + pages: [ + { + name: 'rulechain.system-rulechains', + type: 'link', + state: 'home.ruleChains.system', + icon: 'settings_ethernet' + }, + { + name: 'rulechain.edge-rulechains', + type: 'link', + state: 'home.ruleChains.edge', + icon: 'router' + } + ] }, { name: 'customer.customers', @@ -214,9 +229,14 @@ function Menu(userService, $state, $rootScope) { name: 'rulechain.management', places: [ { - name: 'rulechain.rulechains', + name: 'rulechain.system-rulechains', icon: 'settings_ethernet', state: 'home.ruleChains' + }, + { + name: 'rulechain.edge-rulechains', + icon: 'router', + state: 'home.edgesRuleChains' } ] },