Browse Source

Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5

pull/725/head
Andrew Shvayka 8 years ago
parent
commit
b38a7d7bda
  1. 8
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  2. 31
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/data/RelationsQuery.java
  3. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
  4. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  5. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java
  6. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  7. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  8. 12
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  9. 18
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java
  10. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  11. 25
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java
  12. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
  13. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  14. 17
      ui/src/app/api/rule-chain.service.js
  15. 1
      ui/src/app/entity/relation/relation-filters.directive.js
  16. 14
      ui/src/app/event/event-header-debug-rulenode.tpl.html
  17. 18
      ui/src/app/event/event-row-debug-rulenode.tpl.html
  18. 8
      ui/src/app/event/event-row.directive.js
  19. 25
      ui/src/app/event/event.scss
  20. 80
      ui/src/app/import-export/import-export.service.js
  21. 7
      ui/src/app/locale/locale.constant.js
  22. 341
      ui/src/app/rulechain/rulechain.controller.js
  23. 43
      ui/src/app/rulechain/rulechain.routes.js
  24. 20
      ui/src/app/rulechain/rulechain.scss
  25. 25
      ui/src/app/rulechain/rulechain.tpl.html
  26. 4
      ui/src/app/rulechain/rulechains.controller.js
  27. 2
      ui/src/app/rulechain/rulenode.tpl.html

8
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -69,14 +69,18 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
if (restartRequired) {
tbNode.destroy();
if (tbNode != null) {
tbNode.destroy();
}
start(context);
}
}
@Override
public void stop(ActorContext context) throws Exception {
tbNode.destroy();
if (tbNode != null) {
tbNode.destroy();
}
context.stop(self);
}

31
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/data/RelationsQuery.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.data;
import lombok.Data;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import java.util.List;
@Data
public class RelationsQuery {
private EntitySearchDirection direction;
private int maxLevel = 1;
private List<EntityTypeFilter> filters;
}

4
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, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
"with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +
"<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " +
"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;

4
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 " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
"<code>metadata.temperature</code>. 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<CustomerId> {
@Override

17
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;
}
}

7
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 " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
"<code>metadata.temperature</code>. 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<EntityId> {
private TbGetRelatedAttrNodeConfiguration config;
@ -45,6 +48,6 @@ public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
@Override
protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) {
return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType());
return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getRelationsQuery());
}
}

4
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 " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
"<code>metadata.temperature</code>. 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<TenantId> {
@Override

12
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());
}
}

18
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;
}

4
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.<br/> " +
"<code>metadata</code> - is a Message metadata.<br/>" +
"<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.")
"<code>msg</code> - is a Message payload.<br/>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;

25
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<EntityId> findEntityAsync(TbContext ctx, EntityId originator,
EntitySearchDirection direction, String relationType) {
RelationsQuery relationsQuery) {
RelationService relationService = ctx.getRelationService();
if (direction == EntitySearchDirection.FROM) {
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON);
EntityRelationsQuery query = buildQuery(originator, relationsQuery);
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByQuery(query);
if (relationsQuery.getDirection() == EntitySearchDirection.FROM) {
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo())
: Futures.immediateFailedFuture(new IllegalStateException("Relation not found")));
} else if (direction == EntitySearchDirection.TO) {
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON);
} else if (relationsQuery.getDirection() == EntitySearchDirection.TO) {
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, 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;
}
}

2
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*/

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

17
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);
}

1
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);
});

14
ui/src/app/event/event-header-debug-rulenode.tpl.html

@ -15,13 +15,13 @@
limitations under the License.
-->
<div hide-xs hide-sm translate class="tb-cell" flex="30">event.event-time</div>
<div hide-xs hide-sm translate class="tb-cell" flex="25">event.event-time</div>
<div translate class="tb-cell" flex="20">event.server</div>
<div translate class="tb-cell" flex="20">event.type</div>
<div translate class="tb-cell" flex="20">event.entity</div>
<div translate class="tb-cell" flex="10">event.type</div>
<div translate class="tb-cell" flex="15">event.entity</div>
<div translate class="tb-cell" flex="20">event.message-id</div>
<div translate class="tb-cell" flex="20">event.message-type</div>
<div translate class="tb-cell" flex="20">event.data-type</div>
<div translate class="tb-cell" flex="20">event.data</div>
<div translate class="tb-cell" flex="20">event.metadata</div>
<div translate class="tb-cell" flex="20">event.error</div>
<div translate class="tb-cell" flex="15">event.data-type</div>
<div translate class="tb-cell" flex="10">event.data</div>
<div translate class="tb-cell" flex="10">event.metadata</div>
<div translate class="tb-cell" flex="10">event.error</div>

18
ui/src/app/event/event-row-debug-rulenode.tpl.html

@ -15,14 +15,14 @@
limitations under the License.
-->
<div hide-xs hide-sm class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
<div hide-xs hide-sm class="tb-cell" flex="25">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
<div class="tb-cell" flex="20">{{event.body.server}}</div>
<div class="tb-cell" flex="20">{{event.body.type}}</div>
<div class="tb-cell" flex="20">{{event.body.entityName}}</div>
<div class="tb-cell" flex="20">{{event.body.msgId}}</div>
<div class="tb-cell" flex="20">{{event.body.msgType}}</div>
<div class="tb-cell" flex="20">{{event.body.dataType}}</div>
<div class="tb-cell" flex="20">
<div class="tb-cell" flex="10">{{event.body.type}}</div>
<div class="tb-cell" flex="15">{{event.body.entityName}}</div>
<div class="tb-cell tb-nowrap" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgId}}</div>
<div class="tb-cell" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgType}}</div>
<div class="tb-cell" flex="15">{{event.body.dataType}}</div>
<div class="tb-cell" flex="10">
<md-button ng-if="event.body.data" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.data, 'event.data', event.body.dataType)"
aria-label="{{ 'action.view' | translate }}">
@ -35,7 +35,7 @@
</md-icon>
</md-button>
</div>
<div class="tb-cell" flex="20">
<div class="tb-cell" flex="10">
<md-button ng-if="event.body.metadata" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.metadata, 'event.metadata', 'JSON')"
aria-label="{{ 'action.view' | translate }}">
@ -48,7 +48,7 @@
</md-icon>
</md-button>
</div>
<div class="tb-cell" flex="20">
<div class="tb-cell" flex="10">
<md-button ng-if="event.body.error" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.error, 'event.error')"
aria-label="{{ 'action.view' | translate }}">

8
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);
}

25
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;
}
}

80
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<ruleChainMetaData.nodes.length;i++) {
var node = ruleChainMetaData.nodes[i];
ruleChainMetaData.nodes[i] = prepareExport(node);
}
return ruleChainMetaData;
}
function processExportRuleChainRejection(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('rulechain.export-failed-error', {error: message}));
}
function importRuleChain($event) {
var deferred = $q.defer();
openImportDialog($event, 'rulechain.import', 'rulechain.rulechain-file').then(
function success(ruleChain) {
if (!validateImportedRuleChain(ruleChain)) {
function success(ruleChainImport) {
if (!validateImportedRuleChain(ruleChainImport)) {
toast.showError($translate.instant('rulechain.invalid-rulechain-file-error'));
deferred.reject();
} else {
//TODO: rulechain metadata
ruleChainService.saveRuleChain(ruleChain).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
deferred.resolve(ruleChainImport);
}
},
function fail() {
@ -323,10 +347,14 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return deferred.promise;
}
function validateImportedRuleChain(ruleChain) {
//TODO: rulechain metadata
if (angular.isUndefined(ruleChain.name))
{
function validateImportedRuleChain(ruleChainImport) {
if (angular.isUndefined(ruleChainImport.ruleChain)) {
return false;
}
if (angular.isUndefined(ruleChainImport.metadata)) {
return false;
}
if (angular.isUndefined(ruleChainImport.ruleChain.name)) {
return false;
}
return true;

7
ui/src/app/locale/locale.constant.js

@ -43,6 +43,7 @@ export default angular.module('thingsboard.locale', [])
"update": "Update",
"remove": "Remove",
"search": "Search",
"clear-search": "Clear search",
"assign": "Assign",
"unassign": "Unassign",
"share": "Share",
@ -1174,7 +1175,7 @@ export default angular.module('thingsboard.locale', [])
"export": "Export rule chain",
"export-failed-error": "Unable to export rule chain: {{error}}",
"create-new-rulechain": "Create new rule chain",
"rule-file": "Rule chain file",
"rulechain-file": "Rule chain file",
"invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
"copyId": "Copy rule chain Id",
"idCopiedMessage": "Rule chain Id has been copied to clipboard",
@ -1188,6 +1189,7 @@ export default angular.module('thingsboard.locale', [])
"details": "Details",
"events": "Events",
"search": "Search nodes",
"open-node-library": "Open node library",
"add": "Add rule node",
"name": "Name",
"name-required": "Name is required.",
@ -1217,7 +1219,8 @@ export default angular.module('thingsboard.locale', [])
"type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
"ui-resources-load-error": "Failed to load configuration ui resources."
"ui-resources-load-error": "Failed to load configuration ui resources.",
"invalid-target-rulechain": "Unable to resolve target rule chain!"
},
"rule-plugin": {
"management": "Rules and plugins management"

341
ui/src/app/rulechain/rulechain.controller.js

@ -28,7 +28,7 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
$filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants,
ruleChain, ruleChainMetaData, ruleNodeComponents) {
@ -37,6 +37,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.$mdExpansionPanel = $mdExpansionPanel;
vm.types = types;
if ($state.current.data.import && !ruleChain) {
$state.go('home.ruleChains');
return;
}
vm.isImport = $state.current.data.import;
vm.isConfirmOnExit = false;
$scope.$watch(function() {
return vm.isDirty || vm.isImport;
}, (val) => {
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 = '<div class="tb-rule-node-error-tooltip">' +
'<div id="tooltip-content" layout="column">' +
'<div class="tb-node-details">' + node.error + '</div>' +
'</div>' +
'</div>';
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);
$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() {
function loadRuleChainLibrary(ruleNodeComponents, loadRuleChain) {
for (var componentType in vm.ruleNodeTypesModel) {
vm.ruleNodeTypesModel[componentType].model.nodes.length = 0;
}
for (var i=0;i<ruleNodeComponents.length;i++) {
var ruleNodeComponent = ruleNodeComponents[i];
var componentType = ruleNodeComponent.type;
componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model;
var node = {
id: 'node-lib-' + componentType + '-' + model.nodes.length,
@ -349,7 +458,26 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
model.nodes.push(node);
}
vm.ruleChainLibraryLoaded = true;
prepareRuleChain();
if (loadRuleChain) {
prepareRuleChain();
}
$mdUtil.nextTick(() => {
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 saveRuleChainPromise;
if (vm.isImport) {
saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain);
} else {
saveRuleChainPromise = $q.when(vm.ruleChain);
}
saveRuleChainPromise.then(
(ruleChain) => {
vm.ruleChain = ruleChain;
var ruleChainMetaData = {
ruleChainId: vm.ruleChain.id,
nodes: [],
connections: [],
ruleChainConnections: []
};
var nodes = [];
var nodes = [];
for (var i=0;i<vm.ruleChainModel.nodes.length;i++) {
var node = vm.ruleChainModel.nodes[i];
if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
var ruleNode = {};
if (node.ruleNodeId) {
ruleNode.id = node.ruleNodeId;
for (var i=0;i<vm.ruleChainModel.nodes.length;i++) {
var node = vm.ruleChainModel.nodes[i];
if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
var ruleNode = {};
if (node.ruleNodeId) {
ruleNode.id = node.ruleNodeId;
}
ruleNode.type = node.component.clazz;
ruleNode.name = node.name;
ruleNode.configuration = node.configuration;
ruleNode.additionalInfo = node.additionalInfo;
ruleNode.debugMode = node.debugMode;
if (!ruleNode.additionalInfo) {
ruleNode.additionalInfo = {};
}
ruleNode.additionalInfo.layoutX = node.x;
ruleNode.additionalInfo.layoutY = node.y;
ruleChainMetaData.nodes.push(ruleNode);
nodes.push(node);
}
}
ruleNode.type = node.component.clazz;
ruleNode.name = node.name;
ruleNode.configuration = node.configuration;
ruleNode.additionalInfo = node.additionalInfo;
ruleNode.debugMode = node.debugMode;
if (!ruleNode.additionalInfo) {
ruleNode.additionalInfo = {};
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);
}
ruleNode.additionalInfo.layoutX = node.x;
ruleNode.additionalInfo.layoutY = node.y;
ruleChainMetaData.nodes.push(ruleNode);
nodes.push(node);
}
}
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.ruleChainModel.edges.length;i++) {
var edge = vm.ruleChainModel.edges[i];
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
var fromIndex = nodes.indexOf(sourceNode);
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
var ruleChainConnection = {
fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo,
type: edge.label
};
if (!ruleChainConnection.additionalInfo) {
ruleChainConnection.additionalInfo = {};
for (i=0;i<vm.ruleChainModel.edges.length;i++) {
var edge = vm.ruleChainModel.edges[i];
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
var fromIndex = nodes.indexOf(sourceNode);
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
var ruleChainConnection = {
fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo,
type: edge.label
};
if (!ruleChainConnection.additionalInfo) {
ruleChainConnection.additionalInfo = {};
}
ruleChainConnection.additionalInfo.layoutX = destNode.x;
ruleChainConnection.additionalInfo.layoutY = destNode.y;
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
} else {
var toIndex = nodes.indexOf(destNode);
var nodeConnection = {
fromIndex: fromIndex,
toIndex: toIndex,
type: edge.label
};
ruleChainMetaData.connections.push(nodeConnection);
}
}
ruleChainConnection.additionalInfo.layoutX = destNode.x;
ruleChainConnection.additionalInfo.layoutY = destNode.y;
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
} else {
var toIndex = nodes.indexOf(destNode);
var nodeConnection = {
fromIndex: fromIndex,
toIndex: toIndex,
type: edge.label
};
ruleChainMetaData.connections.push(nodeConnection);
}
}
}
ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then(
(ruleChainMetaData) => {
vm.ruleChainMetaData = ruleChainMetaData;
prepareRuleChain();
ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then(
(ruleChainMetaData) => {
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 () {
});
}

43
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"}'
}
});
}

20
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;

25
ui/src/app/rulechain/rulechain.tpl.html

@ -16,20 +16,20 @@
-->
<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty"
<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isConfirmOnExit"
expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"
ng-keydown="vm.keyDown($event)"
ng-keyup="vm.keyUp($event)">
ng-keyup="vm.keyUp($event)" on-fullscreen-changed="vm.isFullscreen = expanded">
<section class="tb-rulechain-container" flex layout="column">
<div class="tb-rulechain-layout" flex layout="row">
<section layout="row" layout-wrap
class="tb-header-buttons md-fab tb-library-open">
<md-button ng-show="!vm.isLibraryOpen"
class="tb-btn-header tb-btn-open-library md-primary md-fab md-fab-top-left"
aria-label="{{ 'action.apply' | translate }}"
aria-label="{{ 'rulenode.open-node-library' | translate }}"
ng-click="vm.isLibraryOpen = true">
<md-tooltip md-direction="top">
{{ 'action.apply-changes' | translate }}
<md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'rulenode.open-node-library' | translate }}
</md-tooltip>
<ng-md-icon icon="menu"></ng-md-icon>
</md-button>
@ -43,7 +43,7 @@
<div class="md-toolbar-tools">
<md-button class="md-icon-button tb-small" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="top">
<md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{'rulenode.search' | translate}}
</md-tooltip>
</md-button>
@ -53,15 +53,17 @@
<input ng-model="vm.ruleNodeSearch" placeholder="{{'rulenode.search' | translate}}"/>
</md-input-container>
</div>
<md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.ruleNodeSearch = ''">
<md-button class="md-icon-button tb-small" aria-label="Close"
ng-show="vm.ruleNodeSearch"
ng-click="vm.ruleNodeSearch = ''">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'action.close' | translate }}
<md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'action.clear-search' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.isLibraryOpen = false">
<md-icon aria-label="Close" class="material-icons">chevron_left</md-icon>
<md-tooltip md-direction="top">
<md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'action.close' | translate }}
</md-tooltip>
</md-button>
@ -90,6 +92,7 @@
callbacks="vm.nodeLibCallbacks"
node-width="170"
node-height="50"
control="vm.ruleNodeTypesCanvasControl[typeId]"
drop-target-id="'tb-rulchain-canvas'"></fc-canvas>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
@ -182,7 +185,7 @@
</md-tooltip>
<ng-md-icon icon="delete"></ng-md-icon>
</md-button>
<md-button ng-disabled="$root.loading || !vm.isDirty"
<md-button ng-disabled="$root.loading || vm.isInvalid || (!vm.isDirty && !vm.isImport)"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
ng-click="vm.saveRuleChain()">

4
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});
}
);
},

2
ui/src/app/rulechain/rulenode.tpl.html

@ -23,7 +23,7 @@
ng-mouseenter="callbacks.mouseEnter($event, node)"
ng-mouseleave="callbacks.mouseLeave($event, node)">
<div class="{{flowchartConstants.nodeOverlayClass}}"></div>
<div class="tb-rule-node {{node.nodeClass}}">
<div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }">
<md-icon aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon>
<div layout="column" flex="85" layout-align="center">

Loading…
Cancel
Save