Commit c409e2d9c1d05231c849649be479b8a20abd0ee0

Authored by Volodymyr Babak
1 parent 0123a352

Rule chains refactoring:

Showing 29 changed files with 365 additions and 85 deletions
... ... @@ -55,6 +55,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
55 55 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
56 56 import org.thingsboard.server.common.data.rule.RuleChain;
57 57 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  58 +import org.thingsboard.server.common.data.rule.RuleChainType;
58 59 import org.thingsboard.server.common.data.rule.RuleNode;
59 60 import org.thingsboard.server.common.msg.TbMsg;
60 61 import org.thingsboard.server.common.msg.TbMsgMetaData;
... ... @@ -231,13 +232,19 @@ public class RuleChainController extends BaseController {
231 232 @ResponseBody
232 233 public TextPageData<RuleChain> getRuleChains(
233 234 @RequestParam int limit,
  235 + @RequestParam(value = "type", required = false) String typeStr,
234 236 @RequestParam(required = false) String textSearch,
235 237 @RequestParam(required = false) String idOffset,
236 238 @RequestParam(required = false) String textOffset) throws ThingsboardException {
237 239 try {
238 240 TenantId tenantId = getCurrentUser().getTenantId();
239 241 TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
240   - return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
  242 + if (typeStr != null && typeStr.trim().length() > 0) {
  243 + RuleChainType type = RuleChainType.valueOf(typeStr);
  244 + return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
  245 + } else {
  246 + return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
  247 + }
241 248 } catch (Exception e) {
242 249 throw handleException(e);
243 250 }
... ...
... ... @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
37 37 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 38 import org.thingsboard.server.common.data.alarm.AlarmStatus;
39 39 import org.thingsboard.server.common.data.asset.Asset;
  40 +import org.thingsboard.server.common.data.audit.ActionType;
40 41 import org.thingsboard.server.common.data.edge.Edge;
41 42 import org.thingsboard.server.common.data.edge.EdgeQueueEntry;
42 43 import org.thingsboard.server.common.data.id.AssetId;
... ... @@ -44,10 +45,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
44 45 import org.thingsboard.server.common.data.id.DeviceId;
45 46 import org.thingsboard.server.common.data.id.EdgeId;
46 47 import org.thingsboard.server.common.data.id.EntityId;
  48 +import org.thingsboard.server.common.data.id.EntityIdFactory;
47 49 import org.thingsboard.server.common.data.id.EntityViewId;
48 50 import org.thingsboard.server.common.data.id.TenantId;
49 51 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
50 52 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  53 +import org.thingsboard.server.common.data.kv.DataType;
51 54 import org.thingsboard.server.common.data.kv.LongDataEntry;
52 55 import org.thingsboard.server.common.data.page.TimePageData;
53 56 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -59,6 +62,7 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
59 62 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
60 63 import org.thingsboard.server.common.data.rule.RuleNode;
61 64 import org.thingsboard.server.common.msg.TbMsg;
  65 +import org.thingsboard.server.common.msg.TbMsgDataType;
62 66 import org.thingsboard.server.common.msg.TbMsgMetaData;
63 67 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
64 68 import org.thingsboard.server.common.msg.session.SessionMsgType;
... ... @@ -91,6 +95,7 @@ import org.thingsboard.server.gen.edge.UplinkResponseMsg;
91 95 import org.thingsboard.server.gen.edge.UserUpdateMsg;
92 96 import org.thingsboard.server.service.edge.EdgeContextComponent;
93 97
  98 +import javax.swing.text.html.parser.Entity;
94 99 import java.io.IOException;
95 100 import java.util.ArrayList;
96 101 import java.util.Collections;
... ... @@ -102,6 +107,9 @@ import java.util.concurrent.locks.ReentrantLock;
102 107 import java.util.function.BiConsumer;
103 108 import java.util.function.Consumer;
104 109
  110 +import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
  111 +import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
  112 +
105 113 @Slf4j
106 114 @Data
107 115 public final class EdgeGrpcSession implements Cloneable {
... ... @@ -175,7 +183,9 @@ public final class EdgeGrpcSession implements Cloneable {
175 183 do {
176 184 pageData = ctx.getEdgeService().findQueueEvents(edge.getTenantId(), edge.getId(), pageLink);
177 185 if (!pageData.getData().isEmpty()) {
  186 + log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
178 187 for (Event event : pageData.getData()) {
  188 + log.trace("[{}] Processing event [{}]", this.sessionId, event);
179 189 EdgeQueueEntry entry;
180 190 try {
181 191 entry = objectMapper.treeToValue(event.getBody(), EdgeQueueEntry.class);
... ... @@ -193,6 +203,9 @@ public final class EdgeGrpcSession implements Cloneable {
193 203 processCustomDownlinkMessage(entry);
194 204 break;
195 205 }
  206 + if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
  207 + pushEntityAttributesToEdge(entry);
  208 + }
196 209 } catch (Exception e) {
197 210 log.error("Exception during processing records from queue", e);
198 211 }
... ... @@ -215,6 +228,63 @@ public final class EdgeGrpcSession implements Cloneable {
215 228 }
216 229 }
217 230
  231 + private void pushEntityAttributesToEdge(EdgeQueueEntry entry) throws IOException {
  232 + EntityId entityId = null;
  233 + String entityName = null;
  234 + switch (entry.getEntityType()) {
  235 + case EDGE:
  236 + entityId = objectMapper.readValue(entry.getData(), Edge.class).getId();
  237 + break;
  238 + case DEVICE:
  239 + entityId = objectMapper.readValue(entry.getData(), Device.class).getId();
  240 + break;
  241 + case ASSET:
  242 + entityId = objectMapper.readValue(entry.getData(), Asset.class).getId();
  243 + break;
  244 + case ENTITY_VIEW:
  245 + entityId = objectMapper.readValue(entry.getData(), EntityView.class).getId();
  246 + break;
  247 + case DASHBOARD:
  248 + entityId = objectMapper.readValue(entry.getData(), Dashboard.class).getId();
  249 + break;
  250 + }
  251 + if (entityId != null) {
  252 + ListenableFuture<List<AttributeKvEntry>> ssAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SERVER_SCOPE);
  253 + Futures.transform(ssAttrFuture, ssAttributes -> {
  254 + if (ssAttributes != null && !ssAttributes.isEmpty()) {
  255 + try {
  256 + TbMsgMetaData metaData = new TbMsgMetaData();
  257 + ObjectNode entityNode = objectMapper.createObjectNode();
  258 + metaData.putValue("scope", DataConstants.SERVER_SCOPE);
  259 + for (AttributeKvEntry attr : ssAttributes) {
  260 + if (attr.getDataType() == DataType.BOOLEAN) {
  261 + entityNode.put(attr.getKey(), attr.getBooleanValue().get());
  262 + } else if (attr.getDataType() == DataType.DOUBLE) {
  263 + entityNode.put(attr.getKey(), attr.getDoubleValue().get());
  264 + } else if (attr.getDataType() == DataType.LONG) {
  265 + entityNode.put(attr.getKey(), attr.getLongValue().get());
  266 + } else {
  267 + entityNode.put(attr.getKey(), attr.getValueAsString());
  268 + }
  269 + }
  270 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, TbMsgDataType.JSON
  271 + , objectMapper.writeValueAsString(entityNode)
  272 + , null, null, 0L);
  273 + log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg);
  274 + outputStream.onNext(ResponseMsg.newBuilder()
  275 + .setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, tbMsg))
  276 + .build());
  277 + } catch (Exception e) {
  278 + log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e);
  279 + }
  280 + }
  281 + return null;
  282 + });
  283 + ListenableFuture<List<AttributeKvEntry>> shAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SHARED_SCOPE);
  284 + ListenableFuture<List<AttributeKvEntry>> clAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.CLIENT_SCOPE);
  285 + }
  286 + }
  287 +
218 288 private void processCustomDownlinkMessage(EdgeQueueEntry entry) throws IOException {
219 289 log.trace("Executing processCustomDownlinkMessage, entry [{}]", entry);
220 290 TbMsg tbMsg = objectMapper.readValue(entry.getData(), TbMsg.class);
... ... @@ -411,7 +481,7 @@ public final class EdgeGrpcSession implements Cloneable {
411 481 return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
412 482 case DataConstants.ENTITY_CREATED:
413 483 case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
414   - return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
  484 + return ENTITY_CREATED_RPC_MESSAGE;
415 485 case DataConstants.ENTITY_DELETED:
416 486 case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
417 487 return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
... ... @@ -521,6 +591,8 @@ public final class EdgeGrpcSession implements Cloneable {
521 591
522 592 private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException {
523 593 return RuleNodeProto.newBuilder()
  594 + .setIdMSB(node.getId().getId().getMostSignificantBits())
  595 + .setIdLSB(node.getId().getId().getLeastSignificantBits())
524 596 .setType(node.getType())
525 597 .setName(node.getName())
526 598 .setDebugMode(node.isDebugMode())
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
27 27 import org.thingsboard.server.common.data.relation.EntityRelation;
28 28 import org.thingsboard.server.common.data.rule.RuleChain;
29 29 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  30 +import org.thingsboard.server.common.data.rule.RuleChainType;
30 31 import org.thingsboard.server.common.data.rule.RuleNode;
31 32
32 33 import java.util.List;
... ... @@ -62,6 +63,8 @@ public interface RuleChainService {
62 63
63 64 TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink);
64 65
  66 + TextPageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink);
  67 +
65 68 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
66 69
67 70 void deleteRuleChainsByTenantId(TenantId tenantId);
... ...
... ... @@ -125,11 +125,13 @@ message RuleChainMetadataUpdateMsg {
125 125 }
126 126
127 127 message RuleNodeProto {
128   - string type = 1;
129   - string name = 2;
130   - bool debugMode = 3;
131   - string configuration = 4;
132   - string additionalInfo = 5;
  128 + int64 idMSB = 1;
  129 + int64 idLSB = 2;
  130 + string type = 3;
  131 + string name = 4;
  132 + bool debugMode = 5;
  133 + string configuration = 6;
  134 + string additionalInfo = 7;
133 135 }
134 136
135 137 message NodeConnectionInfoProto {
... ...
... ... @@ -113,16 +113,16 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
113 113 @Override
114 114 protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) {
115 115 if (plugin.getType() == null) {
116   - throw new DataValidationException("Component type should be specified!.");
  116 + throw new DataValidationException("Component type should be specified!");
117 117 }
118 118 if (plugin.getScope() == null) {
119   - throw new DataValidationException("Component scope should be specified!.");
  119 + throw new DataValidationException("Component scope should be specified!");
120 120 }
121 121 if (StringUtils.isEmpty(plugin.getName())) {
122   - throw new DataValidationException("Component name should be specified!.");
  122 + throw new DataValidationException("Component name should be specified!");
123 123 }
124 124 if (StringUtils.isEmpty(plugin.getClazz())) {
125   - throw new DataValidationException("Component clazz should be specified!.");
  125 + throw new DataValidationException("Component clazz should be specified!");
126 126 }
127 127 }
128 128 };
... ...
... ... @@ -54,7 +54,7 @@ public class BaseEventService implements EventService {
54 54 public Optional<Event> saveIfNotExists(Event event) {
55 55 eventValidator.validate(event, Event::getTenantId);
56 56 if (StringUtils.isEmpty(event.getUid())) {
57   - throw new DataValidationException("Event uid should be specified!.");
  57 + throw new DataValidationException("Event uid should be specified!");
58 58 }
59 59 return eventDao.saveIfNotExists(event);
60 60 }
... ... @@ -62,16 +62,16 @@ public class BaseEventService implements EventService {
62 62 @Override
63 63 public Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid) {
64 64 if (tenantId == null) {
65   - throw new DataValidationException("Tenant id should be specified!.");
  65 + throw new DataValidationException("Tenant id should be specified!");
66 66 }
67 67 if (entityId == null) {
68   - throw new DataValidationException("Entity id should be specified!.");
  68 + throw new DataValidationException("Entity id should be specified!");
69 69 }
70 70 if (StringUtils.isEmpty(eventType)) {
71   - throw new DataValidationException("Event type should be specified!.");
  71 + throw new DataValidationException("Event type should be specified!");
72 72 }
73 73 if (StringUtils.isEmpty(eventUid)) {
74   - throw new DataValidationException("Event uid should be specified!.");
  74 + throw new DataValidationException("Event uid should be specified!");
75 75 }
76 76 Event event = eventDao.findEvent(tenantId.getId(), entityId, eventType, eventUid);
77 77 return event != null ? Optional.of(event) : Optional.empty();
... ... @@ -99,13 +99,13 @@ public class BaseEventService implements EventService {
99 99 @Override
100 100 protected void validateDataImpl(TenantId tenantId, Event event) {
101 101 if (event.getEntityId() == null) {
102   - throw new DataValidationException("Entity id should be specified!.");
  102 + throw new DataValidationException("Entity id should be specified!");
103 103 }
104 104 if (StringUtils.isEmpty(event.getType())) {
105   - throw new DataValidationException("Event type should be specified!.");
  105 + throw new DataValidationException("Event type should be specified!");
106 106 }
107 107 if (event.getBody() == null) {
108   - throw new DataValidationException("Event body should be specified!.");
  108 + throw new DataValidationException("Event body should be specified!");
109 109 }
110 110 }
111 111 };
... ...
... ... @@ -343,6 +343,7 @@ public class ModelConstants {
343 343 public static final String RULE_CHAIN_ASSIGNED_EDGES_PROPERTY = "assigned_edges";
344 344
345 345 public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text";
  346 + 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";
346 347
347 348 /**
348 349 * Cassandra rule node constants.
... ...
... ... @@ -106,11 +106,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
106 106 }
107 107 this.tenantId = DaoUtil.getId(ruleChain.getTenantId());
108 108 this.name = ruleChain.getName();
109   - if (ruleChain.getType() != null) {
110   - this.type = ruleChain.getType();
111   - } else {
112   - this.type = RuleChainType.SYSTEM;
113   - }
  109 + this.type = ruleChain.getType();
114 110 this.searchText = ruleChain.getName();
115 111 this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId());
116 112 this.root = ruleChain.isRoot();
... ... @@ -204,11 +200,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
204 200 ruleChain.setCreatedTime(UUIDs.unixTimestamp(id));
205 201 ruleChain.setTenantId(new TenantId(tenantId));
206 202 ruleChain.setName(name);
207   - if (type != null) {
208   - ruleChain.setType(type);
209   - } else {
210   - ruleChain.setType(RuleChainType.SYSTEM);
211   - }
  203 + ruleChain.setType(type);
212 204 if (this.firstRuleNodeId != null) {
213 205 ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId));
214 206 }
... ...
... ... @@ -103,11 +103,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
103 103 }
104 104 this.tenantId = toString(DaoUtil.getId(ruleChain.getTenantId()));
105 105 this.name = ruleChain.getName();
106   - if (ruleChain.getType() != null) {
107   - this.type = ruleChain.getType();
108   - } else {
109   - this.type = RuleChainType.SYSTEM;
110   - }
  106 + this.type = ruleChain.getType();
111 107 this.searchText = ruleChain.getName();
112 108 if (ruleChain.getFirstRuleNodeId() != null) {
113 109 this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId());
... ... @@ -141,11 +137,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
141 137 ruleChain.setCreatedTime(UUIDs.unixTimestamp(getId()));
142 138 ruleChain.setTenantId(new TenantId(toUUID(tenantId)));
143 139 ruleChain.setName(name);
144   - if (type != null) {
145   - ruleChain.setType(type);
146   - } else {
147   - ruleChain.setType(RuleChainType.SYSTEM);
148   - }
  140 + ruleChain.setType(type);
149 141 if (firstRuleNodeId != null) {
150 142 ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId)));
151 143 }
... ...
... ... @@ -63,6 +63,8 @@ import java.util.Map;
63 63 import java.util.concurrent.ExecutionException;
64 64 import java.util.stream.Collectors;
65 65
  66 +import static org.thingsboard.server.dao.service.Validator.validateString;
  67 +
66 68 /**
67 69 * Created by igor on 3/12/18.
68 70 */
... ... @@ -359,6 +361,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
359 361 }
360 362
361 363 @Override
  364 + public TextPageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink) {
  365 + Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request.");
  366 + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request.");
  367 + List<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantIdAndType(tenantId.getId(), type, pageLink);
  368 + return new TextPageData<>(ruleChains, pageLink);
  369 + }
  370 +
  371 + @Override
362 372 public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) {
363 373 Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
364 374 RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
... ... @@ -514,7 +524,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
514 524 @Override
515 525 protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
516 526 if (StringUtils.isEmpty(ruleChain.getName())) {
517   - throw new DataValidationException("Rule chain name should be specified!.");
  527 + throw new DataValidationException("Rule chain name should be specified!");
  528 + }
  529 + if (ruleChain.getType() == null) {
  530 + throw new DataValidationException("Rule chain type should be specified!");
518 531 }
519 532 if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
520 533 throw new DataValidationException("Rule chain should be assigned to tenant!");
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
28 28 import org.thingsboard.server.common.data.relation.EntityRelation;
29 29 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
30 30 import org.thingsboard.server.common.data.rule.RuleChain;
  31 +import org.thingsboard.server.common.data.rule.RuleChainType;
31 32 import org.thingsboard.server.dao.DaoUtil;
32 33 import org.thingsboard.server.dao.model.nosql.RuleChainEntity;
33 34 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
... ... @@ -35,14 +36,19 @@ import org.thingsboard.server.dao.relation.RelationDao;
35 36 import org.thingsboard.server.dao.util.NoSqlDao;
36 37
37 38 import java.util.ArrayList;
  39 +import java.util.Arrays;
38 40 import java.util.Collections;
39 41 import java.util.List;
40 42 import java.util.UUID;
41 43
42 44 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TENANT_ID_PROPERTY;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
43 47 import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
  48 +import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
44 49 import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME;
45 50 import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY;
  51 +import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TYPE_PROPERTY;
46 52
47 53 @Component
48 54 @Slf4j
... ... @@ -74,6 +80,17 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao<RuleCh
74 80 }
75 81
76 82 @Override
  83 + public List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) {
  84 + log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
  85 + List<RuleChainEntity> ruleChainEntities = findPageWithTextSearch(new TenantId(tenantId), RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
  86 + Arrays.asList(eq(RULE_CHAIN_TYPE_PROPERTY, type),
  87 + eq(RULE_CHAIN_TENANT_ID_PROPERTY, tenantId)),
  88 + pageLink);
  89 + log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink);
  90 + return DaoUtil.convertDataList(ruleChainEntities);
  91 + }
  92 +
  93 + @Override
77 94 public ListenableFuture<List<RuleChain>> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
78 95 log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
79 96 ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DASHBOARD, pageLink);
... ...
... ... @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.page.TextPageLink;
20 20 import org.thingsboard.server.common.data.page.TimePageLink;
21 21 import org.thingsboard.server.common.data.rule.RuleChain;
  22 +import org.thingsboard.server.common.data.rule.RuleChainType;
22 23 import org.thingsboard.server.dao.Dao;
23 24
24 25 import java.util.List;
... ... @@ -39,6 +40,16 @@ public interface RuleChainDao extends Dao<RuleChain> {
39 40 List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
40 41
41 42 /**
  43 + * Find rule chains by tenantId, type and page link.
  44 + *
  45 + * @param tenantId the tenantId
  46 + * @param type the type
  47 + * @param pageLink the page link
  48 + * @return the list of rule chain objects
  49 + */
  50 + List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink);
  51 +
  52 + /**
42 53 * Find rule chains by tenantId, edgeId and page link.
43 54 *
44 55 * @param tenantId the tenantId
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
31 31 import org.thingsboard.server.common.data.relation.EntityRelation;
32 32 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
33 33 import org.thingsboard.server.common.data.rule.RuleChain;
  34 +import org.thingsboard.server.common.data.rule.RuleChainType;
34 35 import org.thingsboard.server.dao.DaoUtil;
35 36 import org.thingsboard.server.dao.model.sql.RuleChainEntity;
36 37 import org.thingsboard.server.dao.relation.RelationDao;
... ... @@ -68,6 +69,7 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
68 69
69 70 @Override
70 71 public List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
  72 + log.debug("Try to find rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
71 73 return DaoUtil.convertDataList(ruleChainRepository
72 74 .findByTenantId(
73 75 UUIDConverter.fromTimeUUID(tenantId),
... ... @@ -77,6 +79,18 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
77 79 }
78 80
79 81 @Override
  82 + public List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) {
  83 + log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
  84 + return DaoUtil.convertDataList(ruleChainRepository
  85 + .findByTenantIdAndType(
  86 + UUIDConverter.fromTimeUUID(tenantId),
  87 + type,
  88 + Objects.toString(pageLink.getTextSearch(), ""),
  89 + pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
  90 + new PageRequest(0, pageLink.getLimit())));
  91 + }
  92 +
  93 + @Override
80 94 public ListenableFuture<List<RuleChain>> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
81 95 log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
82 96 ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.RULE_CHAIN, pageLink);
... ...
... ... @@ -19,6 +19,7 @@ import org.springframework.data.domain.Pageable;
19 19 import org.springframework.data.jpa.repository.Query;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.data.repository.query.Param;
  22 +import org.thingsboard.server.common.data.rule.RuleChainType;
22 23 import org.thingsboard.server.dao.model.sql.RuleChainEntity;
23 24 import org.thingsboard.server.dao.util.SqlDao;
24 25
... ... @@ -35,4 +36,13 @@ public interface RuleChainRepository extends CrudRepository<RuleChainEntity, Str
35 36 @Param("idOffset") String idOffset,
36 37 Pageable pageable);
37 38
  39 + @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " +
  40 + "AND rc.type = :type " +
  41 + "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
  42 + "AND rc.id > :idOffset ORDER BY rc.id")
  43 + List<RuleChainEntity> findByTenantIdAndType(@Param("tenantId") String tenantId,
  44 + @Param("type") RuleChainType type,
  45 + @Param("searchText") String searchText,
  46 + @Param("idOffset") String idOffset,
  47 + Pageable pageable);
38 48 }
... ...
... ... @@ -597,22 +597,30 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
597 597 id uuid,
598 598 tenant_id uuid,
599 599 name text,
  600 + type text,
600 601 search_text text,
601 602 first_rule_node_id uuid,
602 603 root boolean,
603 604 debug_mode boolean,
604 605 configuration text,
605 606 additional_info text,
606   - PRIMARY KEY (id, tenant_id)
  607 + PRIMARY KEY (id, tenant_id, type)
607 608 );
608 609
609 610 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS
610 611 SELECT *
611 612 from thingsboard.rule_chain
612   - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
613   - PRIMARY KEY ( tenant_id, search_text, id )
  613 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL
  614 + PRIMARY KEY ( tenant_id, search_text, id, type )
614 615 WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
615 616
  617 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_by_type_and_search_text AS
  618 + SELECT *
  619 + from thingsboard.rule_chain
  620 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL
  621 + PRIMARY KEY ( tenant_id, type, search_text, id )
  622 + WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC );
  623 +
616 624 CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
617 625 id uuid,
618 626 rule_chain_id uuid,
... ...
... ... @@ -277,7 +277,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
277 277 }
278 278 break;
279 279 case types.entityType.rulechain:
280   - promise = ruleChainService.getRuleChains(pageLink, config);
  280 + promise = ruleChainService.getRuleChains(pageLink, config, subType);
281 281 break;
282 282 case types.entityType.dashboard:
283 283 if (user.authority === 'CUSTOMER_USER') {
... ...
... ... @@ -46,7 +46,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
46 46
47 47 return service;
48 48
49   - function getRuleChains (pageLink, config) {
  49 + function getRuleChains (pageLink, config, type) {
50 50 var deferred = $q.defer();
51 51 var url = '/api/ruleChains?limit=' + pageLink.limit;
52 52 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -58,6 +58,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
58 58 if (angular.isDefined(pageLink.textOffset)) {
59 59 url += '&textOffset=' + pageLink.textOffset;
60 60 }
  61 + if (angular.isDefined(type) && type.length) {
  62 + url += '&type=' + type;
  63 + }
61 64 $http.get(url, config).then(function success(response) {
62 65 deferred.resolve(prepareRuleChains(response.data));
63 66 }, function fail() {
... ...
... ... @@ -607,7 +607,7 @@ export default angular.module('thingsboard.types', [])
607 607 }
608 608 },
609 609 systemRuleChainType: "SYSTEM",
610   - ruleChainTypes: ["SYSTEM", "EDGE"],
  610 + edgeRuleChainType: "EDGE",
611 611 ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
612 612 ruleChainNodeComponent: {
613 613 type: 'RULE_CHAIN',
... ...
... ... @@ -1391,10 +1391,11 @@
1391 1391 "rulechain": {
1392 1392 "rulechain": "Rule chain",
1393 1393 "rulechains": "Rule chains",
  1394 + "system-rulechains": "System Rule chains",
  1395 + "edge-rulechains": "Edge Rule chains",
1394 1396 "root": "Root",
1395 1397 "delete": "Delete rule chain",
1396 1398 "name": "Name",
1397   - "type": "Type",
1398 1399 "name-required": "Name is required.",
1399 1400 "description": "Description",
1400 1401 "add": "Add Rule Chain",
... ...
... ... @@ -31,7 +31,7 @@
31 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
32 32 <md-dialog-content>
33 33 <div class="md-dialog-content">
34   - <tb-rule-chain rule-chain="vm.item" is-edit="true" rule-chain-scope="'tenant'" the-form="theForm"></tb-rule-chain>
  34 + <tb-rule-chain rule-chain="vm.item" is-edit="true" the-form="theForm"></tb-rule-chain>
35 35 </div>
36 36 </md-dialog-content>
37 37 <md-dialog-actions layout="row">
... ...
... ... @@ -31,7 +31,7 @@
31 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
32 32 <md-dialog-content>
33 33 <div class="md-dialog-content">
34   - <tb-rule-node rule-node="vm.ruleNode" rule-chain-id="vm.ruleChainId" is-edit="true" the-form="theForm"></tb-rule-node>
  34 + <tb-rule-node rule-node="vm.ruleNode" rule-chain-id="vm.ruleChainId" rule-chain-type="vm.ruleChainType" is-edit="true" the-form="theForm"></tb-rule-node>
35 35 </div>
36 36 </md-dialog-content>
37 37 <md-dialog-actions layout="row">
... ...
... ... @@ -50,14 +50,6 @@
50 50 </div>
51 51 </md-input-container>
52 52 <md-input-container class="md-block">
53   - <label translate>rulechain.type</label>
54   - <md-select ng-disabled="$root.loading || !isEdit || ruleChainScope !== 'tenant' || ruleChain.root === true" name="type" ng-model="ruleChain.type">
55   - <md-option ng-repeat="ruleChainType in ruleChainTypes" value="{{ruleChainType}}">
56   - {{ruleChainType}}
57   - </md-option>
58   - </md-select>
59   - </md-input-container>
60   - <md-input-container class="md-block">
61 53 <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulechain.debug-mode' | translate }}"
62 54 ng-model="ruleChain.debugMode">{{ 'rulechain.debug-mode' | translate }}
63 55 </md-checkbox>
... ...
... ... @@ -1179,6 +1179,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1179 1179 function saveRuleChain() {
1180 1180 var saveRuleChainPromise;
1181 1181 if (vm.isImport) {
  1182 + if (angular.isUndefined(vm.ruleChain.type)) {
  1183 + vm.ruleChain.type = types.systemRuleChainType;
  1184 + }
1182 1185 saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain);
1183 1186 } else {
1184 1187 saveRuleChainPromise = $q.when(vm.ruleChain);
... ... @@ -1286,6 +1289,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1286 1289 ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration);
1287 1290
1288 1291 var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null;
  1292 + var ruleChainType = vm.ruleChain.type ? vm.ruleChain.type : types.systemRuleChainType;
1289 1293
1290 1294 vm.enableHotKeys = false;
1291 1295
... ... @@ -1294,7 +1298,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1294 1298 controllerAs: 'vm',
1295 1299 templateUrl: addRuleNodeTemplate,
1296 1300 parent: angular.element($document[0].body),
1297   - locals: {ruleNode: ruleNode, ruleChainId: ruleChainId},
  1301 + locals: {ruleNode: ruleNode, ruleChainId: ruleChainId, ruleChainType: ruleChainType},
1298 1302 fullscreen: true,
1299 1303 targetEvent: $event
1300 1304 }).then(function (ruleNode) {
... ... @@ -1365,13 +1369,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1365 1369 }
1366 1370
1367 1371 /*@ngInject*/
1368   -export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) {
  1372 +export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, ruleChainType, helpLinks) {
1369 1373
1370 1374 var vm = this;
1371 1375
1372 1376 vm.helpLinks = helpLinks;
1373 1377 vm.ruleNode = ruleNode;
1374 1378 vm.ruleChainId = ruleChainId;
  1379 + vm.ruleChainType = ruleChainType;
1375 1380
1376 1381 vm.add = add;
1377 1382 vm.cancel = cancel;
... ...
... ... @@ -26,12 +26,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog,
26 26 var template = $templateCache.get(ruleChainFieldsetTemplate);
27 27 element.html(template);
28 28
29   - scope.ruleChainTypes = types.ruleChainTypes;
30   -
31   - if (angular.isDefined(scope.ruleChain) && scope.ruleChain != null && angular.isUndefined(scope.ruleChain.type)) {
32   - scope.ruleChain.type = types.systemRuleChainType;
33   - }
34   -
35 29 scope.onRuleChainIdCopied = function() {
36 30 toast.showSuccess($translate.instant('rulechain.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
37 31 };
... ... @@ -44,7 +38,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog,
44 38 scope: {
45 39 ruleChain: '=',
46 40 isEdit: '=',
47   - ruleChainScope: '=',
48 41 isReadOnly: '=',
49 42 theForm: '=',
50 43 onSetRootRuleChain: '&',
... ...
... ... @@ -29,6 +29,15 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
29 29 $stateProvider
30 30 .state('home.ruleChains', {
31 31 url: '/ruleChains',
  32 + module: 'private',
  33 + auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
  34 + redirectTo: 'home.ruleChains.system',
  35 + ncyBreadcrumb: {
  36 + label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}'
  37 + }
  38 + })
  39 + .state('home.ruleChains.system', {
  40 + url: '/ruleChains/system',
32 41 params: {'topIndex': 0},
33 42 module: 'private',
34 43 auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
... ... @@ -41,11 +50,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
41 50 },
42 51 data: {
43 52 searchEnabled: true,
44   - pageTitle: 'rulechain.rulechains',
  53 + pageTitle: 'rulechain.system-rulechains',
45 54 ruleChainsType: 'tenant'
46 55 },
47 56 ncyBreadcrumb: {
48   - label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}'
  57 + label: '{"icon": "settings_ethernet", "label": "rulechain.system-rulechains"}'
49 58 }
50 59 }).state('home.ruleChains.ruleChain', {
51 60 url: '/:ruleChainId',
... ... @@ -124,8 +133,64 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
124 133 ncyBreadcrumb: {
125 134 label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}'
126 135 }
127   - })
128   - .state('home.edges.ruleChains', {
  136 + }).state('home.ruleChains.edge', {
  137 + url: '/ruleChains/edge',
  138 + params: {'topIndex': 0},
  139 + module: 'private',
  140 + auth: ['TENANT_ADMIN'],
  141 + views: {
  142 + "content@home": {
  143 + templateUrl: ruleChainsTemplate,
  144 + controllerAs: 'vm',
  145 + controller: 'RuleChainsController'
  146 + }
  147 + },
  148 + data: {
  149 + searchEnabled: true,
  150 + pageTitle: 'rulechain.edge-rulechains',
  151 + ruleChainsType: 'edges'
  152 + },
  153 + ncyBreadcrumb: {
  154 + label: '{"icon": "settings_ethernet", "label": "rulechain.edge-rulechains"}'
  155 + }
  156 + }).state('home.ruleChains.edge.ruleChain', {
  157 + url: '/:ruleChainId',
  158 + reloadOnSearch: false,
  159 + module: 'private',
  160 + auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
  161 + views: {
  162 + "content@home": {
  163 + templateUrl: ruleChainTemplate,
  164 + controller: 'RuleChainController',
  165 + controllerAs: 'vm'
  166 + }
  167 + },
  168 + resolve: {
  169 + ruleChain:
  170 + /*@ngInject*/
  171 + function($stateParams, ruleChainService) {
  172 + return ruleChainService.getRuleChain($stateParams.ruleChainId);
  173 + },
  174 + ruleChainMetaData:
  175 + /*@ngInject*/
  176 + function($stateParams, ruleChainService) {
  177 + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
  178 + },
  179 + ruleNodeComponents:
  180 + /*@ngInject*/
  181 + function($stateParams, ruleChainService) {
  182 + return ruleChainService.getRuleNodeComponents();
  183 + }
  184 + },
  185 + data: {
  186 + import: false,
  187 + searchEnabled: false,
  188 + pageTitle: 'edge.rulechain'
  189 + },
  190 + ncyBreadcrumb: {
  191 + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
  192 + }
  193 + }).state('home.edges.ruleChains', {
129 194 url: '/:edgeId/ruleChains',
130 195 params: {'topIndex': 0},
131 196 module: 'private',
... ... @@ -138,15 +203,14 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
138 203 }
139 204 },
140 205 data: {
141   - ruleChainsType: 'edge',
142 206 searchEnabled: true,
143   - pageTitle: 'edge.rulechains'
  207 + pageTitle: 'edge.rulechains',
  208 + ruleChainsType: 'edge'
144 209 },
145 210 ncyBreadcrumb: {
146 211 label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}'
147 212 }
148   - })
149   - .state('home.edges.ruleChains.ruleChain', {
  213 + }).state('home.edges.ruleChains.ruleChain', {
150 214 url: '/:ruleChainId',
151 215 reloadOnSearch: false,
152 216 module: 'private',
... ... @@ -181,7 +245,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
181 245 pageTitle: 'edge.rulechain'
182 246 },
183 247 ncyBreadcrumb: {
184   - label: '{"icon": "settings_ethernet", "label": "edge.rulechain"}'
  248 + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
185 249 }
186 250 });
187 251 }
\ No newline at end of file
... ...
... ... @@ -116,7 +116,7 @@ export default function RuleChainsController(ruleChainService, userService, edge
116 116
117 117 if (vm.ruleChainsScope === 'tenant') {
118 118 fetchRuleChainsFunction = function (pageLink) {
119   - return fetchRuleChains(pageLink);
  119 + return fetchRuleChains(pageLink, 'SYSTEM');
120 120 };
121 121 deleteRuleChainFunction = function (ruleChainId) {
122 122 return deleteRuleChain(ruleChainId);
... ... @@ -134,6 +134,57 @@ export default function RuleChainsController(ruleChainService, userService, edge
134 134
135 135 ruleChainActionsList.push({
136 136 onAction: function ($event, item) {
  137 + vm.grid.deleteItem($event, item);
  138 + },
  139 + name: function() { return $translate.instant('action.delete') },
  140 + details: function() { return $translate.instant('rulechain.delete') },
  141 + icon: "delete",
  142 + isEnabled: isNonRootRuleChain
  143 + });
  144 +
  145 + ruleChainGroupActionsList.push(
  146 + {
  147 + onAction: function ($event) {
  148 + vm.grid.deleteItems($event);
  149 + },
  150 + name: function() { return $translate.instant('rulechain.delete-rulechains') },
  151 + details: deleteRuleChainsActionTitle,
  152 + icon: "delete"
  153 + }
  154 + );
  155 +
  156 + vm.ruleChainGridConfig.addItemActions = [];
  157 + vm.ruleChainGridConfig.addItemActions.push({
  158 + onAction: function ($event) {
  159 + vm.grid.addItem($event);
  160 + },
  161 + name: function() { return $translate.instant('action.create') },
  162 + details: function() { return $translate.instant('rulechain.create-new-rulechain') },
  163 + icon: "insert_drive_file"
  164 + });
  165 + vm.ruleChainGridConfig.addItemActions.push({
  166 + onAction: function ($event) {
  167 + importExport.importRuleChain($event).then(
  168 + function(ruleChainImport) {
  169 + $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport});
  170 + }
  171 + );
  172 + },
  173 + name: function() { return $translate.instant('action.import') },
  174 + details: function() { return $translate.instant('rulechain.import') },
  175 + icon: "file_upload"
  176 + });
  177 +
  178 + } else if (vm.ruleChainsScope === 'edges') {
  179 + fetchRuleChainsFunction = function (pageLink) {
  180 + return fetchRuleChains(pageLink, 'EDGE');
  181 + };
  182 + deleteRuleChainFunction = function (ruleChainId) {
  183 + return deleteRuleChain(ruleChainId);
  184 + };
  185 +
  186 + ruleChainActionsList.push({
  187 + onAction: function ($event, item) {
137 188 manageAssignedEdges($event, item);
138 189 },
139 190 name: function() { return $translate.instant('action.assign') },
... ... @@ -209,7 +260,6 @@ export default function RuleChainsController(ruleChainService, userService, edge
209 260 details: function() { return $translate.instant('rulechain.import') },
210 261 icon: "file_upload"
211 262 });
212   -
213 263 } else if (vm.ruleChainsScope === 'edge') {
214 264 fetchRuleChainsFunction = function (pageLink) {
215 265 return ruleChainService.getEdgeRuleChains(edgeId, pageLink);
... ... @@ -291,11 +341,18 @@ export default function RuleChainsController(ruleChainService, userService, edge
291 341 vm.grid = grid;
292 342 }
293 343
294   - function fetchRuleChains(pageLink) {
295   - return ruleChainService.getRuleChains(pageLink);
  344 + function fetchRuleChains(pageLink, type) {
  345 + return ruleChainService.getRuleChains(pageLink, null, type);
296 346 }
297 347
298 348 function saveRuleChain(ruleChain) {
  349 + if (angular.isUndefined(ruleChain.type)) {
  350 + if (vm.ruleChainsScope === 'edges') {
  351 + ruleChain.type = types.edgeRuleChainType;
  352 + } else {
  353 + ruleChain.type = types.systemRuleChainType;
  354 + }
  355 + }
299 356 return ruleChainService.saveRuleChain(ruleChain);
300 357 }
301 358
... ... @@ -303,10 +360,11 @@ export default function RuleChainsController(ruleChainService, userService, edge
303 360 if ($event) {
304 361 $event.stopPropagation();
305 362 }
  363 +
306 364 if (vm.ruleChainsScope === 'edge') {
307   - $state.go('home.edges.ruleChains.ruleChain', {
308   - ruleChainId: ruleChain.id.id
309   - });
  365 + $state.go('home.edges.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id, edgeId: vm.edge.id.id});
  366 + } else if (vm.ruleChainsScope === 'edges') {
  367 + $state.go('home.ruleChains.edge.ruleChain', {ruleChainId: ruleChain.id.id});
310 368 } else {
311 369 $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id});
312 370 }
... ...
... ... @@ -53,6 +53,7 @@
53 53 tb-required="true"
54 54 exclude-entity-ids="[ruleChainId]"
55 55 entity-type="types.entityType.rulechain"
  56 + entity-subtype="ruleChainType"
56 57 ng-model="params.targetRuleChainId">
57 58 </tb-entity-autocomplete>
58 59 <md-input-container class="md-block">
... ...
... ... @@ -70,6 +70,7 @@ export default function RuleNodeDirective($compile, $templateCache, ruleChainSer
70 70 link: linker,
71 71 scope: {
72 72 ruleChainId: '=',
  73 + ruleChainType: '=',
73 74 ruleNode: '=',
74 75 isEdit: '=',
75 76 isReadOnly: '=',
... ...
... ... @@ -156,9 +156,24 @@ function Menu(userService, $state, $rootScope) {
156 156 },
157 157 {
158 158 name: 'rulechain.rulechains',
159   - type: 'link',
  159 + type: 'toggle',
160 160 state: 'home.ruleChains',
161   - icon: 'settings_ethernet'
  161 + height: '80px',
  162 + icon: 'settings_ethernet',
  163 + pages: [
  164 + {
  165 + name: 'rulechain.system-rulechains',
  166 + type: 'link',
  167 + state: 'home.ruleChains.system',
  168 + icon: 'settings_ethernet'
  169 + },
  170 + {
  171 + name: 'rulechain.edge-rulechains',
  172 + type: 'link',
  173 + state: 'home.ruleChains.edge',
  174 + icon: 'router'
  175 + }
  176 + ]
162 177 },
163 178 {
164 179 name: 'customer.customers',
... ... @@ -214,9 +229,14 @@ function Menu(userService, $state, $rootScope) {
214 229 name: 'rulechain.management',
215 230 places: [
216 231 {
217   - name: 'rulechain.rulechains',
  232 + name: 'rulechain.system-rulechains',
218 233 icon: 'settings_ethernet',
219 234 state: 'home.ruleChains'
  235 + },
  236 + {
  237 + name: 'rulechain.edge-rulechains',
  238 + icon: 'router',
  239 + state: 'home.edgesRuleChains'
220 240 }
221 241 ]
222 242 },
... ...