diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index a08dc3489c..93cb5fbf7f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -69,14 +69,18 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor filters; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 7bf70f4ed9..fc65e43bf5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -43,7 +43,9 @@ import static org.thingsboard.server.common.data.DataConstants.*; nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + "metadata.cs.temperature or metadata.shared.limit " + - "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") public class TbGetAttributesNode implements TbNode { private TbGetAttributesNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index c59a65e27d..b092badaf4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -30,7 +30,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbEnrichmentNodeCustomerAttributesConfig") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java index 82119926af..dccd87879a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -16,18 +16,19 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; -import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.EntityTypeFilter; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @Data public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { - private String relationType; - private EntitySearchDirection direction; + private RelationsQuery relationsQuery; @Override public TbGetRelatedAttrNodeConfiguration defaultConfiguration() { @@ -36,8 +37,14 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig attrMapping.putIfAbsent("temperature", "tempo"); configuration.setAttrMapping(attrMapping); configuration.setTelemetry(true); - configuration.setRelationType(EntityRelation.CONTAINS_TYPE); - configuration.setDirection(EntitySearchDirection.FROM); + + RelationsQuery relationsQuery = new RelationsQuery(); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); + configuration.setRelationsQuery(relationsQuery); + return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 603746ef29..8f65c31aa4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType; "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbEnrichmentNodeRelatedAttributesConfig") + public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; @@ -45,6 +48,6 @@ public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { @Override protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType()); + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getRelationsQuery()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 3165385587..f0d28d3290 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -32,7 +32,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "metadata.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbEnrichmentNodeTenantAttributesConfig") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index cf761f295f..05ad49c144 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -39,7 +39,9 @@ import java.util.HashSet; configClazz = TbChangeOriginatorNodeConfiguration.class, nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + - "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbTransformationNodeChangeOriginatorConfig") public class TbChangeOriginatorNode extends TbAbstractTransformNode { protected static final String CUSTOMER_SOURCE = "CUSTOMER"; @@ -68,7 +70,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { case TENANT_SOURCE: return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, original); case RELATED_SOURCE: - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getDirection(), config.getRelationType()); + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getRelationsQuery()); default: return Futures.immediateFailedFuture(new IllegalStateException("Unexpected originator source " + config.getOriginatorSource())); } @@ -82,9 +84,9 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { } if (conf.getOriginatorSource().equals(RELATED_SOURCE)) { - if (conf.getDirection() == null || StringUtils.isBlank(conf.getRelationType())) { - log.error("Related source for TbChangeOriginatorNode should have direction and relationType. Actual [{}] [{}]", - conf.getDirection(), conf.getRelationType()); + if (conf.getRelationsQuery() == null) { + log.error("Related source for TbChangeOriginatorNode should have relations query. Actual [{}]", + conf.getRelationsQuery()); throw new IllegalArgumentException("Wrong config for RElated Source in TbChangeOriginatorNode" + conf.getOriginatorSource()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java index 3370408231..7cd77bfc59 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java @@ -17,22 +17,32 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.EntityTypeFilter; + +import java.util.Collections; @Data public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { private String originatorSource; - private EntitySearchDirection direction; - private String relationType; + + private RelationsQuery relationsQuery; @Override public TbChangeOriginatorNodeConfiguration defaultConfiguration() { TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration(); configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); - configuration.setDirection(EntitySearchDirection.FROM); - configuration.setRelationType(EntityRelation.CONTAINS_TYPE); + + RelationsQuery relationsQuery = new RelationsQuery(); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); + configuration.setRelationsQuery(relationsQuery); + configuration.setStartNewChain(false); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 27e46fe091..626d05784a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -31,7 +31,9 @@ import javax.script.Bindings; nodeDescription = "Change Message payload and Metadata using JavaScript", nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.
" + "metadata - is a Message metadata.
" + - "msg - is a Message payload.
Any properties can be changed/removed/added in those objects.") + "msg - is a Message payload.
Any properties can be changed/removed/added in those objects.", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbTransformationNodeScriptConfig") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java index ac69c5dd59..08ce38e591 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java @@ -20,32 +20,41 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.dao.relation.RelationService; import java.util.List; -import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; - public class EntitiesRelatedEntityIdAsyncLoader { public static ListenableFuture findEntityAsync(TbContext ctx, EntityId originator, - EntitySearchDirection direction, String relationType) { + RelationsQuery relationsQuery) { RelationService relationService = ctx.getRelationService(); - if (direction == EntitySearchDirection.FROM) { - ListenableFuture> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON); + EntityRelationsQuery query = buildQuery(originator, relationsQuery); + ListenableFuture> asyncRelation = relationService.findByQuery(query); + if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); - } else if (direction == EntitySearchDirection.TO) { - ListenableFuture> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON); + } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); } - return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); } + + private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) { + EntityRelationsQuery query = new EntityRelationsQuery(); + RelationsSearchParameters parameters = new RelationsSearchParameters(originator, + relationsQuery.getDirection(), relationsQuery.getMaxLevel()); + query.setParameters(parameters); + query.setFilters(relationsQuery.getFilters()); + return query; + } } diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css index a6103c1d56..c91d7da72d 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css @@ -1,2 +1,2 @@ -.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important} +.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0} /*# sourceMappingURL=rulenode-core-config.css.map*/ \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 3fe859d90f..69ff7aec90 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,2 +1,2 @@ -!function(e){function t(s){if(a[s])return a[s].exports;var n=a[s]={exports:{},id:s,loaded:!1};return e[s].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a={};return t.m=e,t.c=a,t.p="/static/",t(0)}([function(e,t,a){e.exports=a(8)},function(e,t){},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
"},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t,a){var s=function(s,n,r,l){function u(){if(l.$viewValue){for(var e=[],t=0;t {{ 'tb.rulenode.latest-telemetry' | translate }} "},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},3,function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
{{ \'tb.rulenode.clone-message\' | translate }}
'},function(e,t){e.exports="
{{ 'tb.rulenode.clone-message' | translate }}
"},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(3),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(16),i=a(r),l=n(17),o=a(l),s=n(14),u=a(s),d=n(18),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",o.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var o=l.default;a.html(o);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(4),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(21),i=a(r),l=n(20),o=a(l),s=n(22),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",o.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,o){function s(){if(o.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function o(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=l.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=o,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),l=a(i);n(2)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var o=l.default;a.html(o),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var o=l.default;a.html(o),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),l=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(25),i=a(r),l=n(27),o=a(l);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",o.default).name},[32,13],function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(31),i=a(r),l=n(19),o=a(l),s=n(15),u=a(s),d=n(26),c=a(d),m=n(24),f=a(m),g=n(23),p=a(g),b=n(30),y=a(b);t.default=angular.module("thingsboard.ruleChain.config",[i.default,o.default,u.default,c.default]).directive("tbRelationsQueryConfig",f.default).directive("tbKvMapConfig",p.default).config(y.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){(0,l.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),l=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name},function(e,t,n,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var l=n(a),o=r(l)}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index af14a3f582..b930cf18fa 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -253,7 +253,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co if (ruleChainConnections && ruleChainConnections.length) { var tasks = []; for (var i = 0; i < ruleChainConnections.length; i++) { - tasks.push(getRuleChain(ruleChainConnections[i].targetRuleChainId.id)); + tasks.push(resolveRuleChain(ruleChainConnections[i].targetRuleChainId.id)); } $q.all(tasks).then( (ruleChains) => { @@ -273,6 +273,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } + function resolveRuleChain(ruleChainId) { + var deferred = $q.defer(); + getRuleChain(ruleChainId, {ignoreErrors: true}).then( + (ruleChain) => { + deferred.resolve(ruleChain); + }, + () => { + deferred.resolve({ + id: {id: ruleChainId, entityType: types.entityType.rulechain} + }); + } + ); + return deferred.promise; + } + function loadRuleNodeComponents() { return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes); } diff --git a/ui/src/app/entity/relation/relation-filters.directive.js b/ui/src/app/entity/relation/relation-filters.directive.js index 00d3b26035..9ab66cac72 100644 --- a/ui/src/app/entity/relation/relation-filters.directive.js +++ b/ui/src/app/entity/relation/relation-filters.directive.js @@ -46,6 +46,7 @@ export default function RelationFilters($compile, $templateCache) { ngModelCtrl.$render = function () { if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; + scope.relationFilters.length = 0; value.forEach(function (filter) { scope.relationFilters.push(filter); }); diff --git a/ui/src/app/event/event-header-debug-rulenode.tpl.html b/ui/src/app/event/event-header-debug-rulenode.tpl.html index b412a0c50a..34f4513655 100644 --- a/ui/src/app/event/event-header-debug-rulenode.tpl.html +++ b/ui/src/app/event/event-header-debug-rulenode.tpl.html @@ -15,13 +15,13 @@ limitations under the License. --> -
event.event-time
+
event.event-time
event.server
-
event.type
-
event.entity
+
event.type
+
event.entity
event.message-id
event.message-type
-
event.data-type
-
event.data
-
event.metadata
-
event.error
+
event.data-type
+
event.data
+
event.metadata
+
event.error
diff --git a/ui/src/app/event/event-row-debug-rulenode.tpl.html b/ui/src/app/event/event-row-debug-rulenode.tpl.html index 5b96bafd62..bb832b15d8 100644 --- a/ui/src/app/event/event-row-debug-rulenode.tpl.html +++ b/ui/src/app/event/event-row-debug-rulenode.tpl.html @@ -15,14 +15,14 @@ limitations under the License. --> -
{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}
+
{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}
{{event.body.server}}
-
{{event.body.type}}
-
{{event.body.entityName}}
-
{{event.body.msgId}}
-
{{event.body.msgType}}
-
{{event.body.dataType}}
-
+
{{event.body.type}}
+
{{event.body.entityName}}
+
{{event.body.msgId}}
+
{{event.body.msgType}}
+
{{event.body.dataType}}
+
@@ -35,7 +35,7 @@
-
+
@@ -48,7 +48,7 @@
-
+
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js index 4643761b39..b808fb8fff 100644 --- a/ui/src/app/event/event-row.directive.js +++ b/ui/src/app/event/event-row.directive.js @@ -86,6 +86,14 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ }); } + scope.checkTooltip = function($event) { + var el = $event.target; + var $el = angular.element(el); + if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ + $el.attr('title', $el.text()); + } + } + $compile(element.contents())(scope); } diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss index b3be35c19f..b0fc46fea5 100644 --- a/ui/src/app/event/event.scss +++ b/ui/src/app/event/event.scss @@ -24,6 +24,17 @@ md-list.tb-event-table { height: 48px; padding: 0px; overflow: hidden; + .tb-cell { + text-overflow: ellipsis; + &.tb-scroll { + white-space: nowrap; + overflow-y: hidden; + overflow-x: auto; + } + &.tb-nowrap { + white-space: nowrap; + } + } } .tb-row:hover { @@ -39,13 +50,19 @@ md-list.tb-event-table { color: rgba(0,0,0,.54); font-size: 12px; font-weight: 700; - white-space: nowrap; background: none; + white-space: nowrap; } } .tb-cell { - padding: 0 24px; + &:first-child { + padding-left: 14px; + } + &:last-child { + padding-right: 14px; + } + padding: 0 6px; margin: auto 0; color: rgba(0,0,0,.87); font-size: 13px; @@ -53,8 +70,8 @@ md-list.tb-event-table { text-align: left; overflow: hidden; .md-button { - padding: 0; - margin: 0; + padding: 0; + margin: 0; } } diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 19c249f869..88c6353401 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -281,39 +281,63 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, function exportRuleChain(ruleChainId) { ruleChainService.getRuleChain(ruleChainId).then( - function success(ruleChain) { - var name = ruleChain.name; - name = name.toLowerCase().replace(/\W/g,"_"); - exportToPc(prepareExport(ruleChain), name + '.json'); - //TODO: metadata + (ruleChain) => { + ruleChainService.getRuleChainMetaData(ruleChainId).then( + (ruleChainMetaData) => { + var ruleChainExport = { + ruleChain: prepareRuleChain(ruleChain), + metadata: prepareRuleChainMetaData(ruleChainMetaData) + }; + var name = ruleChain.name; + name = name.toLowerCase().replace(/\W/g,"_"); + exportToPc(ruleChainExport, name + '.json'); + }, + (rejection) => { + processExportRuleChainRejection(rejection); + } + ); }, - function fail(rejection) { - var message = rejection; - if (!message) { - message = $translate.instant('error.unknown-error'); - } - toast.showError($translate.instant('rulechain.export-failed-error', {error: message})); + (rejection) => { + processExportRuleChainRejection(rejection); } ); } + function prepareRuleChain(ruleChain) { + ruleChain = prepareExport(ruleChain); + if (ruleChain.firstRuleNodeId) { + ruleChain.firstRuleNodeId = null; + } + ruleChain.root = false; + return ruleChain; + } + + function prepareRuleChainMetaData(ruleChainMetaData) { + delete ruleChainMetaData.ruleChainId; + for (var i=0;i { + vm.isConfirmOnExit = val; + }); + + vm.errorTooltips = {}; + + vm.isFullscreen = false; + vm.editingRuleNode = null; vm.isEditingRuleNode = false; @@ -57,6 +75,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }; vm.ruleNodeTypesModel = {}; + vm.ruleNodeTypesCanvasControl = {}; vm.ruleChainLibraryLoaded = false; for (var type in types.ruleNodeType) { if (!types.ruleNodeType[type].special) { @@ -67,9 +86,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }, selectedObjects: [] }; + vm.ruleNodeTypesCanvasControl[type] = {}; } } + + vm.selectedObjects = []; vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); @@ -145,8 +167,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $scope.$broadcast('form-submit'); if (theForm.$valid) { theForm.$setPristine(); + if (vm.editingRuleNode.error) { + delete vm.editingRuleNode.error; + } vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; vm.editingRuleNode = angular.copy(vm.editingRuleNode); + updateRuleNodesHighlight(); } }; @@ -203,7 +229,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } var instances = angular.element.tooltipster.instances(); instances.forEach((instance) => { - instance.destroy(); + if (!instance.isErrorTooltip) { + instance.destroy(); + } }); } @@ -249,6 +277,71 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }, 500); } + function updateNodeErrorTooltip(node) { + if (node.error) { + var element = angular.element('#' + node.id); + var tooltip = vm.errorTooltips[node.id]; + if (!tooltip || !element.hasClass("tooltipstered")) { + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 0, + animationDuration: 0, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: false, + tap: false, + scroll: false + }, + side: 'top', + trackOrigin: true + } + ); + var content = '
' + + '
' + + '
' + node.error + '
' + + '
' + + '
'; + var contentElement = angular.element(content); + $compile(contentElement)($scope); + tooltip = element.tooltipster('instance'); + tooltip.isErrorTooltip = true; + tooltip.content(contentElement); + vm.errorTooltips[node.id] = tooltip; + } + $mdUtil.nextTick(() => { + tooltip.open(); + }); + } else { + if (vm.errorTooltips[node.id]) { + tooltip = vm.errorTooltips[node.id]; + tooltip.destroy(); + delete vm.errorTooltips[node.id]; + } + } + } + + function updateErrorTooltips(hide) { + for (var nodeId in vm.errorTooltips) { + var tooltip = vm.errorTooltips[nodeId]; + if (hide) { + tooltip.close(); + } else { + tooltip.open(); + } + } + } + + $scope.$watch(function() { + return vm.isEditingRuleNode || vm.isEditingRuleNodeLink; + }, (val) => { + updateErrorTooltips(val); + }); + vm.editCallbacks = { edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); @@ -313,12 +406,28 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } }; - loadRuleChainLibrary(); + loadRuleChainLibrary(ruleNodeComponents, true); - function loadRuleChainLibrary() { + $scope.$watch('vm.ruleNodeSearch', + function (newVal, oldVal) { + if (!angular.equals(newVal, oldVal)) { + var res = $filter('filter')(ruleNodeComponents, {name: vm.ruleNodeSearch}); + loadRuleChainLibrary(res); + } + } + ); + + $scope.$on('searchTextUpdated', function () { + updateRuleNodesHighlight(); + }); + + function loadRuleChainLibrary(ruleNodeComponents, loadRuleChain) { + for (var componentType in vm.ruleNodeTypesModel) { + vm.ruleNodeTypesModel[componentType].model.nodes.length = 0; + } for (var i=0;i { + for (componentType in vm.ruleNodeTypesCanvasControl) { + if (vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize) { + vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize(true); + } + } + for (componentType in vm.ruleNodeTypesModel) { + var panel = vm.$mdExpansionPanel(componentType); + if (panel) { + if (!vm.ruleNodeTypesModel[componentType].model.nodes.length) { + panel.collapse(); + } else { + panel.expand(); + } + } + } + }); } function prepareRuleChain() { @@ -480,11 +608,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, ruleChainNode = { id: 'rule-chain-node-' + vm.nextNodeID++, additionalInfo: ruleChainConnection.additionalInfo, - targetRuleChainId: ruleChainConnection.targetRuleChainId.id, x: ruleChainConnection.additionalInfo.layoutX, y: ruleChainConnection.additionalInfo.layoutY, component: types.ruleChainNodeComponent, - name: ruleChain.name, nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass, icon: vm.types.ruleNodeType.RULE_CHAIN.icon, connectors: [ @@ -494,6 +620,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } ] }; + if (ruleChain.name) { + ruleChainNode.name = ruleChain.name; + ruleChainNode.targetRuleChainId = ruleChainConnection.targetRuleChainId.id; + } else { + ruleChainNode.name = "Unresolved"; + ruleChainNode.targetRuleChainId = null; + ruleChainNode.error = $translate.instant('rulenode.invalid-target-rulechain'); + } ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; vm.ruleChainModel.nodes.push(ruleChainNode); } @@ -519,89 +653,141 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, vm.isDirty = false; + updateRuleNodesHighlight(); + + validate(); + $mdUtil.nextTick(() => { vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', function (newVal, oldVal) { - if (!vm.isDirty && !angular.equals(newVal, oldVal)) { - vm.isDirty = true; + if (!angular.equals(newVal, oldVal)) { + validate(); + if (!vm.isDirty) { + vm.isDirty = true; + } } }, true ); }); } + function updateRuleNodesHighlight() { + for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) { + vm.ruleChainModel.nodes[i].highlighted = false; + } + if ($scope.searchConfig.searchText) { + var res = $filter('filter')(vm.ruleChainModel.nodes, {name: $scope.searchConfig.searchText}); + if (res) { + for (i = 0; i < res.length; i++) { + res[i].highlighted = true; + } + } + } + } + + function validate() { + $mdUtil.nextTick(() => { + vm.isInvalid = false; + for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) { + if (vm.ruleChainModel.nodes[i].error) { + vm.isInvalid = true; + } + updateNodeErrorTooltip(vm.ruleChainModel.nodes[i]); + } + }); + } + function saveRuleChain() { - var ruleChainMetaData = { - ruleChainId: vm.ruleChain.id, - nodes: [], - connections: [], - ruleChainConnections: [] - }; - - var nodes = []; - - for (var i=0;i { + vm.ruleChain = ruleChain; + var ruleChainMetaData = { + ruleChainId: vm.ruleChain.id, + nodes: [], + connections: [], + ruleChainConnections: [] + }; + + var nodes = []; + + for (var i=0;i { - vm.ruleChainMetaData = ruleChainMetaData; - prepareRuleChain(); + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); + if (res && res.length) { + var firstNodeEdge = res[0]; + var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination); + ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); + } + for (i=0;i { + vm.ruleChainMetaData = ruleChainMetaData; + if (vm.isImport) { + vm.isDirty = false; + vm.isImport = false; + $mdUtil.nextTick(() => { + $state.go('home.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); + }); + } else { + prepareRuleChain(); + } + } + ); } ); } @@ -614,12 +800,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); + var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null; + $mdDialog.show({ controller: 'AddRuleNodeController', controllerAs: 'vm', templateUrl: addRuleNodeTemplate, parent: angular.element($document[0].body), - locals: {ruleNode: ruleNode, ruleChainId: vm.ruleChain.id.id}, + locals: {ruleNode: ruleNode, ruleChainId: ruleChainId}, fullscreen: true, targetEvent: $event }).then(function (ruleNode) { @@ -642,6 +830,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, ); } vm.ruleChainModel.nodes.push(ruleNode); + updateRuleNodesHighlight(); }, function () { }); } diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index f9578effd3..2aefd82379 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -76,11 +76,52 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider } }, data: { - searchEnabled: false, + import: false, + searchEnabled: true, pageTitle: 'rulechain.rulechain' }, ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' } + }).state('home.ruleChains.importRuleChain', { + url: '/ruleChain/import', + reloadOnSearch: false, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainTemplate, + controller: 'RuleChainController', + controllerAs: 'vm' + } + }, + params: { + ruleChainImport: {} + }, + resolve: { + ruleChain: + /*@ngInject*/ + function($stateParams) { + return $stateParams.ruleChainImport.ruleChain; + }, + ruleChainMetaData: + /*@ngInject*/ + function($stateParams) { + return $stateParams.ruleChainImport.metadata; + }, + ruleNodeComponents: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleNodeComponents(); + } + }, + data: { + import: true, + searchEnabled: true, + pageTitle: 'rulechain.rulechain' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' + } }); } diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 38f785a4d2..187a722e0f 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -125,6 +125,16 @@ color: #333; border: solid 1px #777; font-size: 12px; + &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { + box-shadow: 0 0 10px 6px #51cbee; + .tb-node-title { + text-decoration: underline; + font-weight: bold; + } + } + &.tb-rule-node-invalid { + box-shadow: 0 0 10px 6px #ff5c50; + } &.tb-input-type { background-color: #a3eaa9; user-select: none; @@ -156,7 +166,7 @@ } .tb-node-title { - font-weight: 600; + font-weight: 500; } .tb-node-type, .tb-node-title { overflow: hidden; @@ -380,6 +390,14 @@ font-size: 14px; width: 300px; color: #333; +} + +.tb-rule-node-error-tooltip { + font-size: 16px; + color: #ea0d0d; +} + +.tb-rule-node-tooltip, .tb-rule-node-error-tooltip { #tooltip-content { .tb-node-title { font-weight: 600; diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index ddc1a90b9d..37fcd9bfb3 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -16,20 +16,20 @@ --> - + ng-keyup="vm.keyUp($event)" on-fullscreen-changed="vm.isFullscreen = expanded">
- - {{ 'action.apply-changes' | translate }} + + {{ 'rulenode.open-node-library' | translate }} @@ -43,7 +43,7 @@
search - + {{'rulenode.search' | translate}} @@ -53,15 +53,17 @@
- + close - - {{ 'action.close' | translate }} + + {{ 'action.clear-search' | translate }} chevron_left - + {{ 'action.close' | translate }} @@ -90,6 +92,7 @@ callbacks="vm.nodeLibCallbacks" node-width="170" node-height="50" + control="vm.ruleNodeTypesCanvasControl[typeId]" drop-target-id="'tb-rulchain-canvas'"> @@ -182,7 +185,7 @@ - diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 2a30b0c98f..7c857f23b6 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -63,8 +63,8 @@ export default function RuleChainsController(ruleChainService, userService, impo { onAction: function ($event) { importExport.importRuleChain($event).then( - function() { - vm.grid.refreshList(); + function(ruleChainImport) { + $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport}); } ); }, diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index 55ee3d3c2b..973ea1fa9e 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -23,7 +23,7 @@ ng-mouseenter="callbacks.mouseEnter($event, node)" ng-mouseleave="callbacks.mouseLeave($event, node)">
-
+
{{node.icon}}