Commit b38a7d7bda4d7175391973cc685efdb8a0dbfdae
Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5
Showing
27 changed files
with
570 additions
and
173 deletions
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
... | ... | @@ -69,14 +69,18 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
69 | 69 | && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration())); |
70 | 70 | this.ruleNode = newRuleNode; |
71 | 71 | if (restartRequired) { |
72 | - tbNode.destroy(); | |
72 | + if (tbNode != null) { | |
73 | + tbNode.destroy(); | |
74 | + } | |
73 | 75 | start(context); |
74 | 76 | } |
75 | 77 | } |
76 | 78 | |
77 | 79 | @Override |
78 | 80 | public void stop(ActorContext context) throws Exception { |
79 | - tbNode.destroy(); | |
81 | + if (tbNode != null) { | |
82 | + tbNode.destroy(); | |
83 | + } | |
80 | 84 | context.stop(self); |
81 | 85 | } |
82 | 86 | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.data; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.server.common.data.relation.EntitySearchDirection; | |
20 | +import org.thingsboard.server.common.data.relation.EntityTypeFilter; | |
21 | + | |
22 | +import java.util.List; | |
23 | + | |
24 | +@Data | |
25 | +public class RelationsQuery { | |
26 | + | |
27 | + private EntitySearchDirection direction; | |
28 | + private int maxLevel = 1; | |
29 | + private List<EntityTypeFilter> filters; | |
30 | + | |
31 | +} | ... | ... |
... | ... | @@ -43,7 +43,9 @@ import static org.thingsboard.server.common.data.DataConstants.*; |
43 | 43 | nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + |
44 | 44 | "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " + |
45 | 45 | "<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " + |
46 | - "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") | |
46 | + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.", | |
47 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
48 | + configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") | |
47 | 49 | public class TbGetAttributesNode implements TbNode { |
48 | 50 | |
49 | 51 | private TbGetAttributesNodeConfiguration config; | ... | ... |
... | ... | @@ -30,7 +30,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
30 | 30 | nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", |
31 | 31 | nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + |
32 | 32 | "To access those attributes in other nodes this template can be used " + |
33 | - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") | |
33 | + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", | |
34 | + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, | |
35 | + configDirective = "tbEnrichmentNodeCustomerAttributesConfig") | |
34 | 36 | public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { |
35 | 37 | |
36 | 38 | @Override | ... | ... |
... | ... | @@ -16,18 +16,19 @@ |
16 | 16 | package org.thingsboard.rule.engine.metadata; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | -import org.thingsboard.rule.engine.api.NodeConfiguration; | |
19 | +import org.thingsboard.rule.engine.data.RelationsQuery; | |
20 | 20 | import org.thingsboard.server.common.data.relation.EntityRelation; |
21 | 21 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
22 | +import org.thingsboard.server.common.data.relation.EntityTypeFilter; | |
22 | 23 | |
24 | +import java.util.Collections; | |
23 | 25 | import java.util.HashMap; |
24 | 26 | import java.util.Map; |
25 | 27 | |
26 | 28 | @Data |
27 | 29 | public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { |
28 | 30 | |
29 | - private String relationType; | |
30 | - private EntitySearchDirection direction; | |
31 | + private RelationsQuery relationsQuery; | |
31 | 32 | |
32 | 33 | @Override |
33 | 34 | public TbGetRelatedAttrNodeConfiguration defaultConfiguration() { |
... | ... | @@ -36,8 +37,14 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig |
36 | 37 | attrMapping.putIfAbsent("temperature", "tempo"); |
37 | 38 | configuration.setAttrMapping(attrMapping); |
38 | 39 | configuration.setTelemetry(true); |
39 | - configuration.setRelationType(EntityRelation.CONTAINS_TYPE); | |
40 | - configuration.setDirection(EntitySearchDirection.FROM); | |
40 | + | |
41 | + RelationsQuery relationsQuery = new RelationsQuery(); | |
42 | + relationsQuery.setDirection(EntitySearchDirection.FROM); | |
43 | + relationsQuery.setMaxLevel(1); | |
44 | + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); | |
45 | + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); | |
46 | + configuration.setRelationsQuery(relationsQuery); | |
47 | + | |
41 | 48 | return configuration; |
42 | 49 | } |
43 | 50 | } | ... | ... |
... | ... | @@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
32 | 32 | "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + |
33 | 33 | "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + |
34 | 34 | "To access those attributes in other nodes this template can be used " + |
35 | - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") | |
35 | + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", | |
36 | + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, | |
37 | + configDirective = "tbEnrichmentNodeRelatedAttributesConfig") | |
38 | + | |
36 | 39 | public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> { |
37 | 40 | |
38 | 41 | private TbGetRelatedAttrNodeConfiguration config; |
... | ... | @@ -45,6 +48,6 @@ public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> { |
45 | 48 | |
46 | 49 | @Override |
47 | 50 | protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) { |
48 | - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType()); | |
51 | + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getRelationsQuery()); | |
49 | 52 | } |
50 | 53 | } | ... | ... |
... | ... | @@ -32,7 +32,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; |
32 | 32 | nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", |
33 | 33 | nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + |
34 | 34 | "To access those attributes in other nodes this template can be used " + |
35 | - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") | |
35 | + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", | |
36 | + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, | |
37 | + configDirective = "tbEnrichmentNodeTenantAttributesConfig") | |
36 | 38 | public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { |
37 | 39 | |
38 | 40 | @Override | ... | ... |
... | ... | @@ -39,7 +39,9 @@ import java.util.HashSet; |
39 | 39 | configClazz = TbChangeOriginatorNodeConfiguration.class, |
40 | 40 | nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", |
41 | 41 | nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
42 | - "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") | |
42 | + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", | |
43 | + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, | |
44 | + configDirective = "tbTransformationNodeChangeOriginatorConfig") | |
43 | 45 | public class TbChangeOriginatorNode extends TbAbstractTransformNode { |
44 | 46 | |
45 | 47 | protected static final String CUSTOMER_SOURCE = "CUSTOMER"; |
... | ... | @@ -68,7 +70,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { |
68 | 70 | case TENANT_SOURCE: |
69 | 71 | return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, original); |
70 | 72 | case RELATED_SOURCE: |
71 | - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getDirection(), config.getRelationType()); | |
73 | + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getRelationsQuery()); | |
72 | 74 | default: |
73 | 75 | return Futures.immediateFailedFuture(new IllegalStateException("Unexpected originator source " + config.getOriginatorSource())); |
74 | 76 | } |
... | ... | @@ -82,9 +84,9 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { |
82 | 84 | } |
83 | 85 | |
84 | 86 | if (conf.getOriginatorSource().equals(RELATED_SOURCE)) { |
85 | - if (conf.getDirection() == null || StringUtils.isBlank(conf.getRelationType())) { | |
86 | - log.error("Related source for TbChangeOriginatorNode should have direction and relationType. Actual [{}] [{}]", | |
87 | - conf.getDirection(), conf.getRelationType()); | |
87 | + if (conf.getRelationsQuery() == null) { | |
88 | + log.error("Related source for TbChangeOriginatorNode should have relations query. Actual [{}]", | |
89 | + conf.getRelationsQuery()); | |
88 | 90 | throw new IllegalArgumentException("Wrong config for RElated Source in TbChangeOriginatorNode" + conf.getOriginatorSource()); |
89 | 91 | } |
90 | 92 | } | ... | ... |
... | ... | @@ -17,22 +17,32 @@ package org.thingsboard.rule.engine.transform; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | 19 | import org.thingsboard.rule.engine.api.NodeConfiguration; |
20 | +import org.thingsboard.rule.engine.data.RelationsQuery; | |
20 | 21 | import org.thingsboard.server.common.data.relation.EntityRelation; |
21 | 22 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
23 | +import org.thingsboard.server.common.data.relation.EntityTypeFilter; | |
24 | + | |
25 | +import java.util.Collections; | |
22 | 26 | |
23 | 27 | @Data |
24 | 28 | public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { |
25 | 29 | |
26 | 30 | private String originatorSource; |
27 | - private EntitySearchDirection direction; | |
28 | - private String relationType; | |
31 | + | |
32 | + private RelationsQuery relationsQuery; | |
29 | 33 | |
30 | 34 | @Override |
31 | 35 | public TbChangeOriginatorNodeConfiguration defaultConfiguration() { |
32 | 36 | TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration(); |
33 | 37 | configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); |
34 | - configuration.setDirection(EntitySearchDirection.FROM); | |
35 | - configuration.setRelationType(EntityRelation.CONTAINS_TYPE); | |
38 | + | |
39 | + RelationsQuery relationsQuery = new RelationsQuery(); | |
40 | + relationsQuery.setDirection(EntitySearchDirection.FROM); | |
41 | + relationsQuery.setMaxLevel(1); | |
42 | + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); | |
43 | + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); | |
44 | + configuration.setRelationsQuery(relationsQuery); | |
45 | + | |
36 | 46 | configuration.setStartNewChain(false); |
37 | 47 | return configuration; |
38 | 48 | } | ... | ... |
... | ... | @@ -31,7 +31,9 @@ import javax.script.Bindings; |
31 | 31 | nodeDescription = "Change Message payload and Metadata using JavaScript", |
32 | 32 | nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " + |
33 | 33 | "<code>metadata</code> - is a Message metadata.<br/>" + |
34 | - "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.") | |
34 | + "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.", | |
35 | + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, | |
36 | + configDirective = "tbTransformationNodeScriptConfig") | |
35 | 37 | public class TbTransformMsgNode extends TbAbstractTransformNode { |
36 | 38 | |
37 | 39 | private TbTransformMsgNodeConfiguration config; | ... | ... |
... | ... | @@ -20,32 +20,41 @@ import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | 21 | import org.apache.commons.collections.CollectionUtils; |
22 | 22 | import org.thingsboard.rule.engine.api.TbContext; |
23 | +import org.thingsboard.rule.engine.data.RelationsQuery; | |
23 | 24 | import org.thingsboard.server.common.data.id.EntityId; |
24 | 25 | import org.thingsboard.server.common.data.relation.EntityRelation; |
26 | +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; | |
25 | 27 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
28 | +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; | |
26 | 29 | import org.thingsboard.server.dao.relation.RelationService; |
27 | 30 | |
28 | 31 | import java.util.List; |
29 | 32 | |
30 | -import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; | |
31 | - | |
32 | 33 | public class EntitiesRelatedEntityIdAsyncLoader { |
33 | 34 | |
34 | 35 | public static ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator, |
35 | - EntitySearchDirection direction, String relationType) { | |
36 | + RelationsQuery relationsQuery) { | |
36 | 37 | RelationService relationService = ctx.getRelationService(); |
37 | - if (direction == EntitySearchDirection.FROM) { | |
38 | - ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON); | |
38 | + EntityRelationsQuery query = buildQuery(originator, relationsQuery); | |
39 | + ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByQuery(query); | |
40 | + if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { | |
39 | 41 | return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
40 | 42 | r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) |
41 | 43 | : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); |
42 | - } else if (direction == EntitySearchDirection.TO) { | |
43 | - ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON); | |
44 | + } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { | |
44 | 45 | return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
45 | 46 | r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) |
46 | 47 | : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); |
47 | 48 | } |
48 | - | |
49 | 49 | return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); |
50 | 50 | } |
51 | + | |
52 | + private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) { | |
53 | + EntityRelationsQuery query = new EntityRelationsQuery(); | |
54 | + RelationsSearchParameters parameters = new RelationsSearchParameters(originator, | |
55 | + relationsQuery.getDirection(), relationsQuery.getMaxLevel()); | |
56 | + query.setParameters(parameters); | |
57 | + query.setFilters(relationsQuery.getFilters()); | |
58 | + return query; | |
59 | + } | |
51 | 60 | } | ... | ... |
1 | -.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} | |
1 | +.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} | |
2 | 2 | /*# sourceMappingURL=rulenode-core-config.css.map*/ |
\ No newline at end of file | ... | ... |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
1 | -!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=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},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<s.messageTypes.length;t++)e.push(s.messageTypes[t].value);l.$viewValue.messageTypes=e,o()}}function o(){if(s.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var c=i.default;n.html(c),s.selectedMessageType=null,s.messageTypeSearchText=null,s.ngModelCtrl=l;var d=[];for(var p in a.messageType){var m={name:a.messageType[p].name,value:a.messageType[p].value};d.push(m)}s.transformMessageTypeChip=function(e){var a,s=t("filter")(d,{name:e},!0);return a=s&&s.length?angular.copy(s[0]):{name:e,value:e}},s.messageTypesSearch=function(e){var a=e?t("filter")(d,{name:e}):d;return a.map(function(e){return e.name})},s.createMessageType=function(e,t){var a=angular.element(t,n)[0].firstElementChild,s=angular.element(a),r=s.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),s.scope().$mdChipsCtrl.appendChip(r.trim()),s.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var n=0;n<e.messageTypes.length;n++){var r=e.messageTypes[n];a.messageType[r]?t.push(angular.copy(a.messageType[r])):t.push({name:r,value:r})}s.messageTypes=t,s.$watch("messageTypes",function(e,t){angular.equals(e,t)||u()},!0)},e(n.contents())(s)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:s}}n.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,a(1);var r=a(2),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(3),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(4),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(11),r=s(n),i=a(6),l=s(i),u=a(5),o=s(u),c=a(7),d=s(c),p=a(10),m=s(p);t.default=angular.module("thingsboard.ruleChain.config",[r.default]).directive("tbFilterNodeScriptConfig",l.default).directive("tbFilterNodeMessageTypeConfig",o.default).directive("tbFilterNodeSwitchConfig",d.default).config(m.default).name},function(e,t){"use strict";function a(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."}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t){(0,i.default)(t);for(var a in t){var s=t[a];e.translations(a,s)}}n.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(9),i=s(r)},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"}}}).name}]); | |
1 | +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(28)},function(e,t){},1,function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},3,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div flex layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </section> "},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<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);o.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!o.$viewValue.messageTypes||!o.$viewValue.messageTypes.length);o.$setValidity("messageTypes",e)}else o.$setValidity("messageTypes",!0)}var d=l.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=o;var c=[];for(var m in n.messageType){var f={name:n.messageType[m].name,value:n.messageType[m].value};c.push(f)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},o.$render=function(){var e=o.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(7),l=a(i)},[32,8],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(9),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){function i(e){e>-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)}])); | |
2 | 2 | //# sourceMappingURL=rulenode-core-config.js.map |
\ No newline at end of file | ... | ... |
... | ... | @@ -253,7 +253,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co |
253 | 253 | if (ruleChainConnections && ruleChainConnections.length) { |
254 | 254 | var tasks = []; |
255 | 255 | for (var i = 0; i < ruleChainConnections.length; i++) { |
256 | - tasks.push(getRuleChain(ruleChainConnections[i].targetRuleChainId.id)); | |
256 | + tasks.push(resolveRuleChain(ruleChainConnections[i].targetRuleChainId.id)); | |
257 | 257 | } |
258 | 258 | $q.all(tasks).then( |
259 | 259 | (ruleChains) => { |
... | ... | @@ -273,6 +273,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co |
273 | 273 | return deferred.promise; |
274 | 274 | } |
275 | 275 | |
276 | + function resolveRuleChain(ruleChainId) { | |
277 | + var deferred = $q.defer(); | |
278 | + getRuleChain(ruleChainId, {ignoreErrors: true}).then( | |
279 | + (ruleChain) => { | |
280 | + deferred.resolve(ruleChain); | |
281 | + }, | |
282 | + () => { | |
283 | + deferred.resolve({ | |
284 | + id: {id: ruleChainId, entityType: types.entityType.rulechain} | |
285 | + }); | |
286 | + } | |
287 | + ); | |
288 | + return deferred.promise; | |
289 | + } | |
290 | + | |
276 | 291 | function loadRuleNodeComponents() { |
277 | 292 | return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes); |
278 | 293 | } | ... | ... |
... | ... | @@ -46,6 +46,7 @@ export default function RelationFilters($compile, $templateCache) { |
46 | 46 | ngModelCtrl.$render = function () { |
47 | 47 | if (ngModelCtrl.$viewValue) { |
48 | 48 | var value = ngModelCtrl.$viewValue; |
49 | + scope.relationFilters.length = 0; | |
49 | 50 | value.forEach(function (filter) { |
50 | 51 | scope.relationFilters.push(filter); |
51 | 52 | }); | ... | ... |
... | ... | @@ -15,13 +15,13 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div hide-xs hide-sm translate class="tb-cell" flex="30">event.event-time</div> | |
18 | +<div hide-xs hide-sm translate class="tb-cell" flex="25">event.event-time</div> | |
19 | 19 | <div translate class="tb-cell" flex="20">event.server</div> |
20 | -<div translate class="tb-cell" flex="20">event.type</div> | |
21 | -<div translate class="tb-cell" flex="20">event.entity</div> | |
20 | +<div translate class="tb-cell" flex="10">event.type</div> | |
21 | +<div translate class="tb-cell" flex="15">event.entity</div> | |
22 | 22 | <div translate class="tb-cell" flex="20">event.message-id</div> |
23 | 23 | <div translate class="tb-cell" flex="20">event.message-type</div> |
24 | -<div translate class="tb-cell" flex="20">event.data-type</div> | |
25 | -<div translate class="tb-cell" flex="20">event.data</div> | |
26 | -<div translate class="tb-cell" flex="20">event.metadata</div> | |
27 | -<div translate class="tb-cell" flex="20">event.error</div> | |
24 | +<div translate class="tb-cell" flex="15">event.data-type</div> | |
25 | +<div translate class="tb-cell" flex="10">event.data</div> | |
26 | +<div translate class="tb-cell" flex="10">event.metadata</div> | |
27 | +<div translate class="tb-cell" flex="10">event.error</div> | ... | ... |
... | ... | @@ -15,14 +15,14 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div hide-xs hide-sm class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div> | |
18 | +<div hide-xs hide-sm class="tb-cell" flex="25">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div> | |
19 | 19 | <div class="tb-cell" flex="20">{{event.body.server}}</div> |
20 | -<div class="tb-cell" flex="20">{{event.body.type}}</div> | |
21 | -<div class="tb-cell" flex="20">{{event.body.entityName}}</div> | |
22 | -<div class="tb-cell" flex="20">{{event.body.msgId}}</div> | |
23 | -<div class="tb-cell" flex="20">{{event.body.msgType}}</div> | |
24 | -<div class="tb-cell" flex="20">{{event.body.dataType}}</div> | |
25 | -<div class="tb-cell" flex="20"> | |
20 | +<div class="tb-cell" flex="10">{{event.body.type}}</div> | |
21 | +<div class="tb-cell" flex="15">{{event.body.entityName}}</div> | |
22 | +<div class="tb-cell tb-nowrap" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgId}}</div> | |
23 | +<div class="tb-cell" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgType}}</div> | |
24 | +<div class="tb-cell" flex="15">{{event.body.dataType}}</div> | |
25 | +<div class="tb-cell" flex="10"> | |
26 | 26 | <md-button ng-if="event.body.data" class="md-icon-button md-primary" |
27 | 27 | ng-click="showContent($event, event.body.data, 'event.data', event.body.dataType)" |
28 | 28 | aria-label="{{ 'action.view' | translate }}"> |
... | ... | @@ -35,7 +35,7 @@ |
35 | 35 | </md-icon> |
36 | 36 | </md-button> |
37 | 37 | </div> |
38 | -<div class="tb-cell" flex="20"> | |
38 | +<div class="tb-cell" flex="10"> | |
39 | 39 | <md-button ng-if="event.body.metadata" class="md-icon-button md-primary" |
40 | 40 | ng-click="showContent($event, event.body.metadata, 'event.metadata', 'JSON')" |
41 | 41 | aria-label="{{ 'action.view' | translate }}"> |
... | ... | @@ -48,7 +48,7 @@ |
48 | 48 | </md-icon> |
49 | 49 | </md-button> |
50 | 50 | </div> |
51 | -<div class="tb-cell" flex="20"> | |
51 | +<div class="tb-cell" flex="10"> | |
52 | 52 | <md-button ng-if="event.body.error" class="md-icon-button md-primary" |
53 | 53 | ng-click="showContent($event, event.body.error, 'event.error')" |
54 | 54 | aria-label="{{ 'action.view' | translate }}"> | ... | ... |
... | ... | @@ -86,6 +86,14 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ |
86 | 86 | }); |
87 | 87 | } |
88 | 88 | |
89 | + scope.checkTooltip = function($event) { | |
90 | + var el = $event.target; | |
91 | + var $el = angular.element(el); | |
92 | + if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ | |
93 | + $el.attr('title', $el.text()); | |
94 | + } | |
95 | + } | |
96 | + | |
89 | 97 | $compile(element.contents())(scope); |
90 | 98 | } |
91 | 99 | ... | ... |
... | ... | @@ -24,6 +24,17 @@ md-list.tb-event-table { |
24 | 24 | height: 48px; |
25 | 25 | padding: 0px; |
26 | 26 | overflow: hidden; |
27 | + .tb-cell { | |
28 | + text-overflow: ellipsis; | |
29 | + &.tb-scroll { | |
30 | + white-space: nowrap; | |
31 | + overflow-y: hidden; | |
32 | + overflow-x: auto; | |
33 | + } | |
34 | + &.tb-nowrap { | |
35 | + white-space: nowrap; | |
36 | + } | |
37 | + } | |
27 | 38 | } |
28 | 39 | |
29 | 40 | .tb-row:hover { |
... | ... | @@ -39,13 +50,19 @@ md-list.tb-event-table { |
39 | 50 | color: rgba(0,0,0,.54); |
40 | 51 | font-size: 12px; |
41 | 52 | font-weight: 700; |
42 | - white-space: nowrap; | |
43 | 53 | background: none; |
54 | + white-space: nowrap; | |
44 | 55 | } |
45 | 56 | } |
46 | 57 | |
47 | 58 | .tb-cell { |
48 | - padding: 0 24px; | |
59 | + &:first-child { | |
60 | + padding-left: 14px; | |
61 | + } | |
62 | + &:last-child { | |
63 | + padding-right: 14px; | |
64 | + } | |
65 | + padding: 0 6px; | |
49 | 66 | margin: auto 0; |
50 | 67 | color: rgba(0,0,0,.87); |
51 | 68 | font-size: 13px; |
... | ... | @@ -53,8 +70,8 @@ md-list.tb-event-table { |
53 | 70 | text-align: left; |
54 | 71 | overflow: hidden; |
55 | 72 | .md-button { |
56 | - padding: 0; | |
57 | - margin: 0; | |
73 | + padding: 0; | |
74 | + margin: 0; | |
58 | 75 | } |
59 | 76 | } |
60 | 77 | ... | ... |
... | ... | @@ -281,39 +281,63 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
281 | 281 | |
282 | 282 | function exportRuleChain(ruleChainId) { |
283 | 283 | ruleChainService.getRuleChain(ruleChainId).then( |
284 | - function success(ruleChain) { | |
285 | - var name = ruleChain.name; | |
286 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
287 | - exportToPc(prepareExport(ruleChain), name + '.json'); | |
288 | - //TODO: metadata | |
284 | + (ruleChain) => { | |
285 | + ruleChainService.getRuleChainMetaData(ruleChainId).then( | |
286 | + (ruleChainMetaData) => { | |
287 | + var ruleChainExport = { | |
288 | + ruleChain: prepareRuleChain(ruleChain), | |
289 | + metadata: prepareRuleChainMetaData(ruleChainMetaData) | |
290 | + }; | |
291 | + var name = ruleChain.name; | |
292 | + name = name.toLowerCase().replace(/\W/g,"_"); | |
293 | + exportToPc(ruleChainExport, name + '.json'); | |
294 | + }, | |
295 | + (rejection) => { | |
296 | + processExportRuleChainRejection(rejection); | |
297 | + } | |
298 | + ); | |
289 | 299 | }, |
290 | - function fail(rejection) { | |
291 | - var message = rejection; | |
292 | - if (!message) { | |
293 | - message = $translate.instant('error.unknown-error'); | |
294 | - } | |
295 | - toast.showError($translate.instant('rulechain.export-failed-error', {error: message})); | |
300 | + (rejection) => { | |
301 | + processExportRuleChainRejection(rejection); | |
296 | 302 | } |
297 | 303 | ); |
298 | 304 | } |
299 | 305 | |
306 | + function prepareRuleChain(ruleChain) { | |
307 | + ruleChain = prepareExport(ruleChain); | |
308 | + if (ruleChain.firstRuleNodeId) { | |
309 | + ruleChain.firstRuleNodeId = null; | |
310 | + } | |
311 | + ruleChain.root = false; | |
312 | + return ruleChain; | |
313 | + } | |
314 | + | |
315 | + function prepareRuleChainMetaData(ruleChainMetaData) { | |
316 | + delete ruleChainMetaData.ruleChainId; | |
317 | + for (var i=0;i<ruleChainMetaData.nodes.length;i++) { | |
318 | + var node = ruleChainMetaData.nodes[i]; | |
319 | + ruleChainMetaData.nodes[i] = prepareExport(node); | |
320 | + } | |
321 | + return ruleChainMetaData; | |
322 | + } | |
323 | + | |
324 | + function processExportRuleChainRejection(rejection) { | |
325 | + var message = rejection; | |
326 | + if (!message) { | |
327 | + message = $translate.instant('error.unknown-error'); | |
328 | + } | |
329 | + toast.showError($translate.instant('rulechain.export-failed-error', {error: message})); | |
330 | + } | |
331 | + | |
300 | 332 | function importRuleChain($event) { |
301 | 333 | var deferred = $q.defer(); |
302 | 334 | openImportDialog($event, 'rulechain.import', 'rulechain.rulechain-file').then( |
303 | - function success(ruleChain) { | |
304 | - if (!validateImportedRuleChain(ruleChain)) { | |
335 | + function success(ruleChainImport) { | |
336 | + if (!validateImportedRuleChain(ruleChainImport)) { | |
305 | 337 | toast.showError($translate.instant('rulechain.invalid-rulechain-file-error')); |
306 | 338 | deferred.reject(); |
307 | 339 | } else { |
308 | - //TODO: rulechain metadata | |
309 | - ruleChainService.saveRuleChain(ruleChain).then( | |
310 | - function success() { | |
311 | - deferred.resolve(); | |
312 | - }, | |
313 | - function fail() { | |
314 | - deferred.reject(); | |
315 | - } | |
316 | - ); | |
340 | + deferred.resolve(ruleChainImport); | |
317 | 341 | } |
318 | 342 | }, |
319 | 343 | function fail() { |
... | ... | @@ -323,10 +347,14 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
323 | 347 | return deferred.promise; |
324 | 348 | } |
325 | 349 | |
326 | - function validateImportedRuleChain(ruleChain) { | |
327 | - //TODO: rulechain metadata | |
328 | - if (angular.isUndefined(ruleChain.name)) | |
329 | - { | |
350 | + function validateImportedRuleChain(ruleChainImport) { | |
351 | + if (angular.isUndefined(ruleChainImport.ruleChain)) { | |
352 | + return false; | |
353 | + } | |
354 | + if (angular.isUndefined(ruleChainImport.metadata)) { | |
355 | + return false; | |
356 | + } | |
357 | + if (angular.isUndefined(ruleChainImport.ruleChain.name)) { | |
330 | 358 | return false; |
331 | 359 | } |
332 | 360 | return true; | ... | ... |
... | ... | @@ -43,6 +43,7 @@ export default angular.module('thingsboard.locale', []) |
43 | 43 | "update": "Update", |
44 | 44 | "remove": "Remove", |
45 | 45 | "search": "Search", |
46 | + "clear-search": "Clear search", | |
46 | 47 | "assign": "Assign", |
47 | 48 | "unassign": "Unassign", |
48 | 49 | "share": "Share", |
... | ... | @@ -1174,7 +1175,7 @@ export default angular.module('thingsboard.locale', []) |
1174 | 1175 | "export": "Export rule chain", |
1175 | 1176 | "export-failed-error": "Unable to export rule chain: {{error}}", |
1176 | 1177 | "create-new-rulechain": "Create new rule chain", |
1177 | - "rule-file": "Rule chain file", | |
1178 | + "rulechain-file": "Rule chain file", | |
1178 | 1179 | "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", |
1179 | 1180 | "copyId": "Copy rule chain Id", |
1180 | 1181 | "idCopiedMessage": "Rule chain Id has been copied to clipboard", |
... | ... | @@ -1188,6 +1189,7 @@ export default angular.module('thingsboard.locale', []) |
1188 | 1189 | "details": "Details", |
1189 | 1190 | "events": "Events", |
1190 | 1191 | "search": "Search nodes", |
1192 | + "open-node-library": "Open node library", | |
1191 | 1193 | "add": "Add rule node", |
1192 | 1194 | "name": "Name", |
1193 | 1195 | "name-required": "Name is required.", |
... | ... | @@ -1217,7 +1219,8 @@ export default angular.module('thingsboard.locale', []) |
1217 | 1219 | "type-rule-chain": "Rule Chain", |
1218 | 1220 | "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", |
1219 | 1221 | "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", |
1220 | - "ui-resources-load-error": "Failed to load configuration ui resources." | |
1222 | + "ui-resources-load-error": "Failed to load configuration ui resources.", | |
1223 | + "invalid-target-rulechain": "Unable to resolve target rule chain!" | |
1221 | 1224 | }, |
1222 | 1225 | "rule-plugin": { |
1223 | 1226 | "management": "Rules and plugins management" | ... | ... |
... | ... | @@ -28,7 +28,7 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html'; |
28 | 28 | /* eslint-enable import/no-unresolved, import/default */ |
29 | 29 | |
30 | 30 | /*@ngInject*/ |
31 | -export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog, | |
31 | +export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog, | |
32 | 32 | $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants, |
33 | 33 | ruleChain, ruleChainMetaData, ruleNodeComponents) { |
34 | 34 | |
... | ... | @@ -37,6 +37,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
37 | 37 | vm.$mdExpansionPanel = $mdExpansionPanel; |
38 | 38 | vm.types = types; |
39 | 39 | |
40 | + if ($state.current.data.import && !ruleChain) { | |
41 | + $state.go('home.ruleChains'); | |
42 | + return; | |
43 | + } | |
44 | + | |
45 | + vm.isImport = $state.current.data.import; | |
46 | + vm.isConfirmOnExit = false; | |
47 | + | |
48 | + $scope.$watch(function() { | |
49 | + return vm.isDirty || vm.isImport; | |
50 | + }, (val) => { | |
51 | + vm.isConfirmOnExit = val; | |
52 | + }); | |
53 | + | |
54 | + vm.errorTooltips = {}; | |
55 | + | |
56 | + vm.isFullscreen = false; | |
57 | + | |
40 | 58 | vm.editingRuleNode = null; |
41 | 59 | vm.isEditingRuleNode = false; |
42 | 60 | |
... | ... | @@ -57,6 +75,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
57 | 75 | }; |
58 | 76 | |
59 | 77 | vm.ruleNodeTypesModel = {}; |
78 | + vm.ruleNodeTypesCanvasControl = {}; | |
60 | 79 | vm.ruleChainLibraryLoaded = false; |
61 | 80 | for (var type in types.ruleNodeType) { |
62 | 81 | if (!types.ruleNodeType[type].special) { |
... | ... | @@ -67,9 +86,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
67 | 86 | }, |
68 | 87 | selectedObjects: [] |
69 | 88 | }; |
89 | + vm.ruleNodeTypesCanvasControl[type] = {}; | |
70 | 90 | } |
71 | 91 | } |
72 | 92 | |
93 | + | |
94 | + | |
73 | 95 | vm.selectedObjects = []; |
74 | 96 | |
75 | 97 | vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); |
... | ... | @@ -145,8 +167,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
145 | 167 | $scope.$broadcast('form-submit'); |
146 | 168 | if (theForm.$valid) { |
147 | 169 | theForm.$setPristine(); |
170 | + if (vm.editingRuleNode.error) { | |
171 | + delete vm.editingRuleNode.error; | |
172 | + } | |
148 | 173 | vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; |
149 | 174 | vm.editingRuleNode = angular.copy(vm.editingRuleNode); |
175 | + updateRuleNodesHighlight(); | |
150 | 176 | } |
151 | 177 | }; |
152 | 178 | |
... | ... | @@ -203,7 +229,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
203 | 229 | } |
204 | 230 | var instances = angular.element.tooltipster.instances(); |
205 | 231 | instances.forEach((instance) => { |
206 | - instance.destroy(); | |
232 | + if (!instance.isErrorTooltip) { | |
233 | + instance.destroy(); | |
234 | + } | |
207 | 235 | }); |
208 | 236 | } |
209 | 237 | |
... | ... | @@ -249,6 +277,71 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
249 | 277 | }, 500); |
250 | 278 | } |
251 | 279 | |
280 | + function updateNodeErrorTooltip(node) { | |
281 | + if (node.error) { | |
282 | + var element = angular.element('#' + node.id); | |
283 | + var tooltip = vm.errorTooltips[node.id]; | |
284 | + if (!tooltip || !element.hasClass("tooltipstered")) { | |
285 | + element.tooltipster( | |
286 | + { | |
287 | + theme: 'tooltipster-shadow', | |
288 | + delay: 0, | |
289 | + animationDuration: 0, | |
290 | + trigger: 'custom', | |
291 | + triggerOpen: { | |
292 | + click: false, | |
293 | + tap: false | |
294 | + }, | |
295 | + triggerClose: { | |
296 | + click: false, | |
297 | + tap: false, | |
298 | + scroll: false | |
299 | + }, | |
300 | + side: 'top', | |
301 | + trackOrigin: true | |
302 | + } | |
303 | + ); | |
304 | + var content = '<div class="tb-rule-node-error-tooltip">' + | |
305 | + '<div id="tooltip-content" layout="column">' + | |
306 | + '<div class="tb-node-details">' + node.error + '</div>' + | |
307 | + '</div>' + | |
308 | + '</div>'; | |
309 | + var contentElement = angular.element(content); | |
310 | + $compile(contentElement)($scope); | |
311 | + tooltip = element.tooltipster('instance'); | |
312 | + tooltip.isErrorTooltip = true; | |
313 | + tooltip.content(contentElement); | |
314 | + vm.errorTooltips[node.id] = tooltip; | |
315 | + } | |
316 | + $mdUtil.nextTick(() => { | |
317 | + tooltip.open(); | |
318 | + }); | |
319 | + } else { | |
320 | + if (vm.errorTooltips[node.id]) { | |
321 | + tooltip = vm.errorTooltips[node.id]; | |
322 | + tooltip.destroy(); | |
323 | + delete vm.errorTooltips[node.id]; | |
324 | + } | |
325 | + } | |
326 | + } | |
327 | + | |
328 | + function updateErrorTooltips(hide) { | |
329 | + for (var nodeId in vm.errorTooltips) { | |
330 | + var tooltip = vm.errorTooltips[nodeId]; | |
331 | + if (hide) { | |
332 | + tooltip.close(); | |
333 | + } else { | |
334 | + tooltip.open(); | |
335 | + } | |
336 | + } | |
337 | + } | |
338 | + | |
339 | + $scope.$watch(function() { | |
340 | + return vm.isEditingRuleNode || vm.isEditingRuleNodeLink; | |
341 | + }, (val) => { | |
342 | + updateErrorTooltips(val); | |
343 | + }); | |
344 | + | |
252 | 345 | vm.editCallbacks = { |
253 | 346 | edgeDoubleClick: function (event, edge) { |
254 | 347 | var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); |
... | ... | @@ -313,12 +406,28 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
313 | 406 | } |
314 | 407 | }; |
315 | 408 | |
316 | - loadRuleChainLibrary(); | |
409 | + loadRuleChainLibrary(ruleNodeComponents, true); | |
410 | + | |
411 | + $scope.$watch('vm.ruleNodeSearch', | |
412 | + function (newVal, oldVal) { | |
413 | + if (!angular.equals(newVal, oldVal)) { | |
414 | + var res = $filter('filter')(ruleNodeComponents, {name: vm.ruleNodeSearch}); | |
415 | + loadRuleChainLibrary(res); | |
416 | + } | |
417 | + } | |
418 | + ); | |
419 | + | |
420 | + $scope.$on('searchTextUpdated', function () { | |
421 | + updateRuleNodesHighlight(); | |
422 | + }); | |
317 | 423 | |
318 | - function loadRuleChainLibrary() { | |
424 | + function loadRuleChainLibrary(ruleNodeComponents, loadRuleChain) { | |
425 | + for (var componentType in vm.ruleNodeTypesModel) { | |
426 | + vm.ruleNodeTypesModel[componentType].model.nodes.length = 0; | |
427 | + } | |
319 | 428 | for (var i=0;i<ruleNodeComponents.length;i++) { |
320 | 429 | var ruleNodeComponent = ruleNodeComponents[i]; |
321 | - var componentType = ruleNodeComponent.type; | |
430 | + componentType = ruleNodeComponent.type; | |
322 | 431 | var model = vm.ruleNodeTypesModel[componentType].model; |
323 | 432 | var node = { |
324 | 433 | id: 'node-lib-' + componentType + '-' + model.nodes.length, |
... | ... | @@ -349,7 +458,26 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
349 | 458 | model.nodes.push(node); |
350 | 459 | } |
351 | 460 | vm.ruleChainLibraryLoaded = true; |
352 | - prepareRuleChain(); | |
461 | + if (loadRuleChain) { | |
462 | + prepareRuleChain(); | |
463 | + } | |
464 | + $mdUtil.nextTick(() => { | |
465 | + for (componentType in vm.ruleNodeTypesCanvasControl) { | |
466 | + if (vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize) { | |
467 | + vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize(true); | |
468 | + } | |
469 | + } | |
470 | + for (componentType in vm.ruleNodeTypesModel) { | |
471 | + var panel = vm.$mdExpansionPanel(componentType); | |
472 | + if (panel) { | |
473 | + if (!vm.ruleNodeTypesModel[componentType].model.nodes.length) { | |
474 | + panel.collapse(); | |
475 | + } else { | |
476 | + panel.expand(); | |
477 | + } | |
478 | + } | |
479 | + } | |
480 | + }); | |
353 | 481 | } |
354 | 482 | |
355 | 483 | function prepareRuleChain() { |
... | ... | @@ -480,11 +608,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
480 | 608 | ruleChainNode = { |
481 | 609 | id: 'rule-chain-node-' + vm.nextNodeID++, |
482 | 610 | additionalInfo: ruleChainConnection.additionalInfo, |
483 | - targetRuleChainId: ruleChainConnection.targetRuleChainId.id, | |
484 | 611 | x: ruleChainConnection.additionalInfo.layoutX, |
485 | 612 | y: ruleChainConnection.additionalInfo.layoutY, |
486 | 613 | component: types.ruleChainNodeComponent, |
487 | - name: ruleChain.name, | |
488 | 614 | nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass, |
489 | 615 | icon: vm.types.ruleNodeType.RULE_CHAIN.icon, |
490 | 616 | connectors: [ |
... | ... | @@ -494,6 +620,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
494 | 620 | } |
495 | 621 | ] |
496 | 622 | }; |
623 | + if (ruleChain.name) { | |
624 | + ruleChainNode.name = ruleChain.name; | |
625 | + ruleChainNode.targetRuleChainId = ruleChainConnection.targetRuleChainId.id; | |
626 | + } else { | |
627 | + ruleChainNode.name = "Unresolved"; | |
628 | + ruleChainNode.targetRuleChainId = null; | |
629 | + ruleChainNode.error = $translate.instant('rulenode.invalid-target-rulechain'); | |
630 | + } | |
497 | 631 | ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; |
498 | 632 | vm.ruleChainModel.nodes.push(ruleChainNode); |
499 | 633 | } |
... | ... | @@ -519,89 +653,141 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
519 | 653 | |
520 | 654 | vm.isDirty = false; |
521 | 655 | |
656 | + updateRuleNodesHighlight(); | |
657 | + | |
658 | + validate(); | |
659 | + | |
522 | 660 | $mdUtil.nextTick(() => { |
523 | 661 | vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', |
524 | 662 | function (newVal, oldVal) { |
525 | - if (!vm.isDirty && !angular.equals(newVal, oldVal)) { | |
526 | - vm.isDirty = true; | |
663 | + if (!angular.equals(newVal, oldVal)) { | |
664 | + validate(); | |
665 | + if (!vm.isDirty) { | |
666 | + vm.isDirty = true; | |
667 | + } | |
527 | 668 | } |
528 | 669 | }, true |
529 | 670 | ); |
530 | 671 | }); |
531 | 672 | } |
532 | 673 | |
674 | + function updateRuleNodesHighlight() { | |
675 | + for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) { | |
676 | + vm.ruleChainModel.nodes[i].highlighted = false; | |
677 | + } | |
678 | + if ($scope.searchConfig.searchText) { | |
679 | + var res = $filter('filter')(vm.ruleChainModel.nodes, {name: $scope.searchConfig.searchText}); | |
680 | + if (res) { | |
681 | + for (i = 0; i < res.length; i++) { | |
682 | + res[i].highlighted = true; | |
683 | + } | |
684 | + } | |
685 | + } | |
686 | + } | |
687 | + | |
688 | + function validate() { | |
689 | + $mdUtil.nextTick(() => { | |
690 | + vm.isInvalid = false; | |
691 | + for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) { | |
692 | + if (vm.ruleChainModel.nodes[i].error) { | |
693 | + vm.isInvalid = true; | |
694 | + } | |
695 | + updateNodeErrorTooltip(vm.ruleChainModel.nodes[i]); | |
696 | + } | |
697 | + }); | |
698 | + } | |
699 | + | |
533 | 700 | function saveRuleChain() { |
534 | - var ruleChainMetaData = { | |
535 | - ruleChainId: vm.ruleChain.id, | |
536 | - nodes: [], | |
537 | - connections: [], | |
538 | - ruleChainConnections: [] | |
539 | - }; | |
701 | + var saveRuleChainPromise; | |
702 | + if (vm.isImport) { | |
703 | + saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain); | |
704 | + } else { | |
705 | + saveRuleChainPromise = $q.when(vm.ruleChain); | |
706 | + } | |
707 | + saveRuleChainPromise.then( | |
708 | + (ruleChain) => { | |
709 | + vm.ruleChain = ruleChain; | |
710 | + var ruleChainMetaData = { | |
711 | + ruleChainId: vm.ruleChain.id, | |
712 | + nodes: [], | |
713 | + connections: [], | |
714 | + ruleChainConnections: [] | |
715 | + }; | |
540 | 716 | |
541 | - var nodes = []; | |
717 | + var nodes = []; | |
542 | 718 | |
543 | - for (var i=0;i<vm.ruleChainModel.nodes.length;i++) { | |
544 | - var node = vm.ruleChainModel.nodes[i]; | |
545 | - if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) { | |
546 | - var ruleNode = {}; | |
547 | - if (node.ruleNodeId) { | |
548 | - ruleNode.id = node.ruleNodeId; | |
719 | + for (var i=0;i<vm.ruleChainModel.nodes.length;i++) { | |
720 | + var node = vm.ruleChainModel.nodes[i]; | |
721 | + if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) { | |
722 | + var ruleNode = {}; | |
723 | + if (node.ruleNodeId) { | |
724 | + ruleNode.id = node.ruleNodeId; | |
725 | + } | |
726 | + ruleNode.type = node.component.clazz; | |
727 | + ruleNode.name = node.name; | |
728 | + ruleNode.configuration = node.configuration; | |
729 | + ruleNode.additionalInfo = node.additionalInfo; | |
730 | + ruleNode.debugMode = node.debugMode; | |
731 | + if (!ruleNode.additionalInfo) { | |
732 | + ruleNode.additionalInfo = {}; | |
733 | + } | |
734 | + ruleNode.additionalInfo.layoutX = node.x; | |
735 | + ruleNode.additionalInfo.layoutY = node.y; | |
736 | + ruleChainMetaData.nodes.push(ruleNode); | |
737 | + nodes.push(node); | |
738 | + } | |
549 | 739 | } |
550 | - ruleNode.type = node.component.clazz; | |
551 | - ruleNode.name = node.name; | |
552 | - ruleNode.configuration = node.configuration; | |
553 | - ruleNode.additionalInfo = node.additionalInfo; | |
554 | - ruleNode.debugMode = node.debugMode; | |
555 | - if (!ruleNode.additionalInfo) { | |
556 | - ruleNode.additionalInfo = {}; | |
740 | + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); | |
741 | + if (res && res.length) { | |
742 | + var firstNodeEdge = res[0]; | |
743 | + var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination); | |
744 | + ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); | |
557 | 745 | } |
558 | - ruleNode.additionalInfo.layoutX = node.x; | |
559 | - ruleNode.additionalInfo.layoutY = node.y; | |
560 | - ruleChainMetaData.nodes.push(ruleNode); | |
561 | - nodes.push(node); | |
562 | - } | |
563 | - } | |
564 | - var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); | |
565 | - if (res && res.length) { | |
566 | - var firstNodeEdge = res[0]; | |
567 | - var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination); | |
568 | - ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); | |
569 | - } | |
570 | - for (i=0;i<vm.ruleChainModel.edges.length;i++) { | |
571 | - var edge = vm.ruleChainModel.edges[i]; | |
572 | - var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); | |
573 | - var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); | |
574 | - if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { | |
575 | - var fromIndex = nodes.indexOf(sourceNode); | |
576 | - if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) { | |
577 | - var ruleChainConnection = { | |
578 | - fromIndex: fromIndex, | |
579 | - targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId}, | |
580 | - additionalInfo: destNode.additionalInfo, | |
581 | - type: edge.label | |
582 | - }; | |
583 | - if (!ruleChainConnection.additionalInfo) { | |
584 | - ruleChainConnection.additionalInfo = {}; | |
746 | + for (i=0;i<vm.ruleChainModel.edges.length;i++) { | |
747 | + var edge = vm.ruleChainModel.edges[i]; | |
748 | + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); | |
749 | + var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); | |
750 | + if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { | |
751 | + var fromIndex = nodes.indexOf(sourceNode); | |
752 | + if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) { | |
753 | + var ruleChainConnection = { | |
754 | + fromIndex: fromIndex, | |
755 | + targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId}, | |
756 | + additionalInfo: destNode.additionalInfo, | |
757 | + type: edge.label | |
758 | + }; | |
759 | + if (!ruleChainConnection.additionalInfo) { | |
760 | + ruleChainConnection.additionalInfo = {}; | |
761 | + } | |
762 | + ruleChainConnection.additionalInfo.layoutX = destNode.x; | |
763 | + ruleChainConnection.additionalInfo.layoutY = destNode.y; | |
764 | + ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; | |
765 | + ruleChainMetaData.ruleChainConnections.push(ruleChainConnection); | |
766 | + } else { | |
767 | + var toIndex = nodes.indexOf(destNode); | |
768 | + var nodeConnection = { | |
769 | + fromIndex: fromIndex, | |
770 | + toIndex: toIndex, | |
771 | + type: edge.label | |
772 | + }; | |
773 | + ruleChainMetaData.connections.push(nodeConnection); | |
774 | + } | |
585 | 775 | } |
586 | - ruleChainConnection.additionalInfo.layoutX = destNode.x; | |
587 | - ruleChainConnection.additionalInfo.layoutY = destNode.y; | |
588 | - ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; | |
589 | - ruleChainMetaData.ruleChainConnections.push(ruleChainConnection); | |
590 | - } else { | |
591 | - var toIndex = nodes.indexOf(destNode); | |
592 | - var nodeConnection = { | |
593 | - fromIndex: fromIndex, | |
594 | - toIndex: toIndex, | |
595 | - type: edge.label | |
596 | - }; | |
597 | - ruleChainMetaData.connections.push(nodeConnection); | |
598 | 776 | } |
599 | - } | |
600 | - } | |
601 | - ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then( | |
602 | - (ruleChainMetaData) => { | |
603 | - vm.ruleChainMetaData = ruleChainMetaData; | |
604 | - prepareRuleChain(); | |
777 | + ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then( | |
778 | + (ruleChainMetaData) => { | |
779 | + vm.ruleChainMetaData = ruleChainMetaData; | |
780 | + if (vm.isImport) { | |
781 | + vm.isDirty = false; | |
782 | + vm.isImport = false; | |
783 | + $mdUtil.nextTick(() => { | |
784 | + $state.go('home.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); | |
785 | + }); | |
786 | + } else { | |
787 | + prepareRuleChain(); | |
788 | + } | |
789 | + } | |
790 | + ); | |
605 | 791 | } |
606 | 792 | ); |
607 | 793 | } |
... | ... | @@ -614,12 +800,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
614 | 800 | |
615 | 801 | ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); |
616 | 802 | |
803 | + var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null; | |
804 | + | |
617 | 805 | $mdDialog.show({ |
618 | 806 | controller: 'AddRuleNodeController', |
619 | 807 | controllerAs: 'vm', |
620 | 808 | templateUrl: addRuleNodeTemplate, |
621 | 809 | parent: angular.element($document[0].body), |
622 | - locals: {ruleNode: ruleNode, ruleChainId: vm.ruleChain.id.id}, | |
810 | + locals: {ruleNode: ruleNode, ruleChainId: ruleChainId}, | |
623 | 811 | fullscreen: true, |
624 | 812 | targetEvent: $event |
625 | 813 | }).then(function (ruleNode) { |
... | ... | @@ -642,6 +830,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
642 | 830 | ); |
643 | 831 | } |
644 | 832 | vm.ruleChainModel.nodes.push(ruleNode); |
833 | + updateRuleNodesHighlight(); | |
645 | 834 | }, function () { |
646 | 835 | }); |
647 | 836 | } | ... | ... |
... | ... | @@ -76,11 +76,52 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider |
76 | 76 | } |
77 | 77 | }, |
78 | 78 | data: { |
79 | - searchEnabled: false, | |
79 | + import: false, | |
80 | + searchEnabled: true, | |
80 | 81 | pageTitle: 'rulechain.rulechain' |
81 | 82 | }, |
82 | 83 | ncyBreadcrumb: { |
83 | 84 | label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' |
84 | 85 | } |
86 | + }).state('home.ruleChains.importRuleChain', { | |
87 | + url: '/ruleChain/import', | |
88 | + reloadOnSearch: false, | |
89 | + module: 'private', | |
90 | + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], | |
91 | + views: { | |
92 | + "content@home": { | |
93 | + templateUrl: ruleChainTemplate, | |
94 | + controller: 'RuleChainController', | |
95 | + controllerAs: 'vm' | |
96 | + } | |
97 | + }, | |
98 | + params: { | |
99 | + ruleChainImport: {} | |
100 | + }, | |
101 | + resolve: { | |
102 | + ruleChain: | |
103 | + /*@ngInject*/ | |
104 | + function($stateParams) { | |
105 | + return $stateParams.ruleChainImport.ruleChain; | |
106 | + }, | |
107 | + ruleChainMetaData: | |
108 | + /*@ngInject*/ | |
109 | + function($stateParams) { | |
110 | + return $stateParams.ruleChainImport.metadata; | |
111 | + }, | |
112 | + ruleNodeComponents: | |
113 | + /*@ngInject*/ | |
114 | + function($stateParams, ruleChainService) { | |
115 | + return ruleChainService.getRuleNodeComponents(); | |
116 | + } | |
117 | + }, | |
118 | + data: { | |
119 | + import: true, | |
120 | + searchEnabled: true, | |
121 | + pageTitle: 'rulechain.rulechain' | |
122 | + }, | |
123 | + ncyBreadcrumb: { | |
124 | + label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' | |
125 | + } | |
85 | 126 | }); |
86 | 127 | } | ... | ... |
... | ... | @@ -125,6 +125,16 @@ |
125 | 125 | color: #333; |
126 | 126 | border: solid 1px #777; |
127 | 127 | font-size: 12px; |
128 | + &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { | |
129 | + box-shadow: 0 0 10px 6px #51cbee; | |
130 | + .tb-node-title { | |
131 | + text-decoration: underline; | |
132 | + font-weight: bold; | |
133 | + } | |
134 | + } | |
135 | + &.tb-rule-node-invalid { | |
136 | + box-shadow: 0 0 10px 6px #ff5c50; | |
137 | + } | |
128 | 138 | &.tb-input-type { |
129 | 139 | background-color: #a3eaa9; |
130 | 140 | user-select: none; |
... | ... | @@ -156,7 +166,7 @@ |
156 | 166 | |
157 | 167 | } |
158 | 168 | .tb-node-title { |
159 | - font-weight: 600; | |
169 | + font-weight: 500; | |
160 | 170 | } |
161 | 171 | .tb-node-type, .tb-node-title { |
162 | 172 | overflow: hidden; |
... | ... | @@ -380,6 +390,14 @@ |
380 | 390 | font-size: 14px; |
381 | 391 | width: 300px; |
382 | 392 | color: #333; |
393 | +} | |
394 | + | |
395 | +.tb-rule-node-error-tooltip { | |
396 | + font-size: 16px; | |
397 | + color: #ea0d0d; | |
398 | +} | |
399 | + | |
400 | +.tb-rule-node-tooltip, .tb-rule-node-error-tooltip { | |
383 | 401 | #tooltip-content { |
384 | 402 | .tb-node-title { |
385 | 403 | font-weight: 600; | ... | ... |
... | ... | @@ -16,20 +16,20 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 18 | |
19 | -<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty" | |
19 | +<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isConfirmOnExit" | |
20 | 20 | expand-tooltip-direction="bottom" layout="column" class="tb-rulechain" |
21 | 21 | ng-keydown="vm.keyDown($event)" |
22 | - ng-keyup="vm.keyUp($event)"> | |
22 | + ng-keyup="vm.keyUp($event)" on-fullscreen-changed="vm.isFullscreen = expanded"> | |
23 | 23 | <section class="tb-rulechain-container" flex layout="column"> |
24 | 24 | <div class="tb-rulechain-layout" flex layout="row"> |
25 | 25 | <section layout="row" layout-wrap |
26 | 26 | class="tb-header-buttons md-fab tb-library-open"> |
27 | 27 | <md-button ng-show="!vm.isLibraryOpen" |
28 | 28 | class="tb-btn-header tb-btn-open-library md-primary md-fab md-fab-top-left" |
29 | - aria-label="{{ 'action.apply' | translate }}" | |
29 | + aria-label="{{ 'rulenode.open-node-library' | translate }}" | |
30 | 30 | ng-click="vm.isLibraryOpen = true"> |
31 | - <md-tooltip md-direction="top"> | |
32 | - {{ 'action.apply-changes' | translate }} | |
31 | + <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}"> | |
32 | + {{ 'rulenode.open-node-library' | translate }} | |
33 | 33 | </md-tooltip> |
34 | 34 | <ng-md-icon icon="menu"></ng-md-icon> |
35 | 35 | </md-button> |
... | ... | @@ -43,7 +43,7 @@ |
43 | 43 | <div class="md-toolbar-tools"> |
44 | 44 | <md-button class="md-icon-button tb-small" aria-label="{{ 'action.search' | translate }}"> |
45 | 45 | <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> |
46 | - <md-tooltip md-direction="top"> | |
46 | + <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}"> | |
47 | 47 | {{'rulenode.search' | translate}} |
48 | 48 | </md-tooltip> |
49 | 49 | </md-button> |
... | ... | @@ -53,15 +53,17 @@ |
53 | 53 | <input ng-model="vm.ruleNodeSearch" placeholder="{{'rulenode.search' | translate}}"/> |
54 | 54 | </md-input-container> |
55 | 55 | </div> |
56 | - <md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.ruleNodeSearch = ''"> | |
56 | + <md-button class="md-icon-button tb-small" aria-label="Close" | |
57 | + ng-show="vm.ruleNodeSearch" | |
58 | + ng-click="vm.ruleNodeSearch = ''"> | |
57 | 59 | <md-icon aria-label="Close" class="material-icons">close</md-icon> |
58 | - <md-tooltip md-direction="top"> | |
59 | - {{ 'action.close' | translate }} | |
60 | + <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}"> | |
61 | + {{ 'action.clear-search' | translate }} | |
60 | 62 | </md-tooltip> |
61 | 63 | </md-button> |
62 | 64 | <md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.isLibraryOpen = false"> |
63 | 65 | <md-icon aria-label="Close" class="material-icons">chevron_left</md-icon> |
64 | - <md-tooltip md-direction="top"> | |
66 | + <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}"> | |
65 | 67 | {{ 'action.close' | translate }} |
66 | 68 | </md-tooltip> |
67 | 69 | </md-button> |
... | ... | @@ -90,6 +92,7 @@ |
90 | 92 | callbacks="vm.nodeLibCallbacks" |
91 | 93 | node-width="170" |
92 | 94 | node-height="50" |
95 | + control="vm.ruleNodeTypesCanvasControl[typeId]" | |
93 | 96 | drop-target-id="'tb-rulchain-canvas'"></fc-canvas> |
94 | 97 | </md-expansion-panel-content> |
95 | 98 | </md-expansion-panel-expanded> |
... | ... | @@ -182,7 +185,7 @@ |
182 | 185 | </md-tooltip> |
183 | 186 | <ng-md-icon icon="delete"></ng-md-icon> |
184 | 187 | </md-button> |
185 | - <md-button ng-disabled="$root.loading || !vm.isDirty" | |
188 | + <md-button ng-disabled="$root.loading || vm.isInvalid || (!vm.isDirty && !vm.isImport)" | |
186 | 189 | class="tb-btn-footer md-accent md-hue-2 md-fab" |
187 | 190 | aria-label="{{ 'action.apply' | translate }}" |
188 | 191 | ng-click="vm.saveRuleChain()"> | ... | ... |
... | ... | @@ -63,8 +63,8 @@ export default function RuleChainsController(ruleChainService, userService, impo |
63 | 63 | { |
64 | 64 | onAction: function ($event) { |
65 | 65 | importExport.importRuleChain($event).then( |
66 | - function() { | |
67 | - vm.grid.refreshList(); | |
66 | + function(ruleChainImport) { | |
67 | + $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport}); | |
68 | 68 | } |
69 | 69 | ); |
70 | 70 | }, | ... | ... |
... | ... | @@ -23,7 +23,7 @@ |
23 | 23 | ng-mouseenter="callbacks.mouseEnter($event, node)" |
24 | 24 | ng-mouseleave="callbacks.mouseLeave($event, node)"> |
25 | 25 | <div class="{{flowchartConstants.nodeOverlayClass}}"></div> |
26 | - <div class="tb-rule-node {{node.nodeClass}}"> | |
26 | + <div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }"> | |
27 | 27 | <md-icon aria-label="node-type-icon" flex="15" |
28 | 28 | class="material-icons">{{node.icon}}</md-icon> |
29 | 29 | <div layout="column" flex="85" layout-align="center"> | ... | ... |