Add External Rule Node types. Rule Chain UI improvements.
This commit is contained in:
		
							parent
							
								
									5e88b02f1e
								
							
						
					
					
						commit
						e7ae82b104
					
				@ -118,6 +118,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
 | 
			
		||||
                case FILTER:
 | 
			
		||||
                case TRANSFORMATION:
 | 
			
		||||
                case ACTION:
 | 
			
		||||
                case EXTERNAL:
 | 
			
		||||
                    RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
 | 
			
		||||
                    scannedComponent.setName(ruleNodeAnnotation.name());
 | 
			
		||||
                    scannedComponent.setScope(ruleNodeAnnotation.scope());
 | 
			
		||||
@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
 | 
			
		||||
        nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
 | 
			
		||||
        nodeDefinition.setUiResources(nodeAnnotation.uiResources());
 | 
			
		||||
        nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
 | 
			
		||||
        nodeDefinition.setIcon(nodeAnnotation.icon());
 | 
			
		||||
        nodeDefinition.setIconUrl(nodeAnnotation.iconUrl());
 | 
			
		||||
        return nodeDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
 | 
			
		||||
 */
 | 
			
		||||
public enum ComponentType {
 | 
			
		||||
 | 
			
		||||
    ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN
 | 
			
		||||
    ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, OLD_ACTION, PLUGIN
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,5 +31,7 @@ public class NodeDefinition {
 | 
			
		||||
    JsonNode defaultConfiguration;
 | 
			
		||||
    String[] uiResources;
 | 
			
		||||
    String configDirective;
 | 
			
		||||
    String icon;
 | 
			
		||||
    String iconUrl;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,10 @@ public @interface RuleNode {
 | 
			
		||||
 | 
			
		||||
    String configDirective() default "";
 | 
			
		||||
 | 
			
		||||
    String icon() default "";
 | 
			
		||||
 | 
			
		||||
    String iconUrl() default "";
 | 
			
		||||
 | 
			
		||||
    boolean customRelations() default false;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
                "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
 | 
			
		||||
                "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeAlarmConfig")
 | 
			
		||||
        configDirective = "tbActionNodeAlarmConfig",
 | 
			
		||||
        icon = "notifications_active"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
public class TbAlarmNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
                "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
 | 
			
		||||
                "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeLogConfig")
 | 
			
		||||
        configDirective = "tbActionNodeLogConfig",
 | 
			
		||||
        icon = "menu"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
public class TbLogNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "aws sns",
 | 
			
		||||
        configClazz = TbSnsNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Publish messages to AWS SNS",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeSnsConfig"
 | 
			
		||||
        configDirective = "tbActionNodeSnsConfig",
 | 
			
		||||
        iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
 | 
			
		||||
)
 | 
			
		||||
public class TbSnsNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "aws sqs",
 | 
			
		||||
        configClazz = TbSqsNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Publish messages to AWS SQS",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeSqsConfig"
 | 
			
		||||
        configDirective = "tbActionNodeSqsConfig",
 | 
			
		||||
        iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
 | 
			
		||||
)
 | 
			
		||||
public class TbSqsNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
        nodeDetails = "Generates messages with configurable period. ",
 | 
			
		||||
        inEnabled = false,
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbActionNodeGeneratorConfig"
 | 
			
		||||
        configDirective = "tbActionNodeGeneratorConfig",
 | 
			
		||||
        icon = "repeat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
public class TbMsgGeneratorNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "kafka",
 | 
			
		||||
        configClazz = TbKafkaNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Publish messages to Kafka server",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeKafkaConfig"
 | 
			
		||||
        configDirective = "tbActionNodeKafkaConfig",
 | 
			
		||||
        iconUrl = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzOCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDQxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIwMS44MTYgMjMwLjIxNmMtMTYuMTg2IDAtMzAuNjk3IDcuMTcxLTQwLjYzNCAxOC40NjFsLTI1LjQ2My0xOC4wMjZjMi43MDMtNy40NDIgNC4yNTUtMTUuNDMzIDQuMjU1LTIzLjc5NyAwLTguMjE5LTEuNDk4LTE2LjA3Ni00LjExMi0yMy40MDhsMjUuNDA2LTE3LjgzNWM5LjkzNiAxMS4yMzMgMjQuNDA5IDE4LjM2NSA0MC41NDggMTguMzY1IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI5Ljg3OS0yNC4zMDktNTQuMTg0LTU0LjE4NC01NC4xODQtMjkuODc1IDAtNTQuMTg0IDI0LjMwNS01NC4xODQgNTQuMTg0IDAgNS4zNDguODA4IDEwLjUwNSAyLjI1OCAxNS4zODlsLTI1LjQyMyAxNy44NDRjLTEwLjYyLTEzLjE3NS0yNS45MTEtMjIuMzc0LTQzLjMzMy0yNS4xODJ2LTMwLjY0YzI0LjU0NC01LjE1NSA0My4wMzctMjYuOTYyIDQzLjAzNy01My4wMTlDMTI0LjE3MSAyNC4zMDUgOTkuODYyIDAgNjkuOTg3IDAgNDAuMTEyIDAgMTUuODAzIDI0LjMwNSAxNS44MDMgNTQuMTg0YzAgMjUuNzA4IDE4LjAxNCA0Ny4yNDYgNDIuMDY3IDUyLjc2OXYzMS4wMzhDMjUuMDQ0IDE0My43NTMgMCAxNzIuNDAxIDAgMjA2Ljg1NGMwIDM0LjYyMSAyNS4yOTIgNjMuMzc0IDU4LjM1NSA2OC45NHYzMi43NzRjLTI0LjI5OSA1LjM0MS00Mi41NTIgMjcuMDExLTQyLjU1MiA1Mi44OTQgMCAyOS44NzkgMjQuMzA5IDU0LjE4NCA1NC4xODQgNTQuMTg0IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI1Ljg4My0xOC4yNTMtNDcuNTUzLTQyLjU1Mi01Mi44OTR2LTMyLjc3NWE2OS45NjUgNjkuOTY1IDAgMCAwIDQyLjYtMjQuNzc2bDI1LjYzMyAxOC4xNDNjLTEuNDIzIDQuODQtMi4yMiA5Ljk0Ni0yLjIyIDE1LjI0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0em0wLTEyNi42OTVjMTQuNDg3IDAgMjYuMjcgMTEuNzg4IDI2LjI3IDI2LjI3MXMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3LTI2LjI3LTExLjc4Ny0yNi4yNy0yNi4yN2MwLTE0LjQ4MyAxMS43ODMtMjYuMjcxIDI2LjI3LTI2LjI3MXptLTE1OC4xLTQ5LjMzN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3em01Mi41NDEgMzA3LjI3OGMwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3YzAtMTQuNDgzIDExLjc4NC0yNi4yNyAyNi4yNzEtMjYuMjdzMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3em0tMjYuMjcyLTExNy45N2MtMjAuMjA1IDAtMzYuNjQyLTE2LjQzNC0zNi42NDItMzYuNjM4IDAtMjAuMjA1IDE2LjQzNy0zNi42NDIgMzYuNjQyLTM2LjY0MiAyMC4yMDQgMCAzNi42NDEgMTYuNDM3IDM2LjY0MSAzNi42NDIgMCAyMC4yMDQtMTYuNDM3IDM2LjYzOC0zNi42NDEgMzYuNjM4em0xMzEuODMxIDY3LjE3OWMtMTQuNDg3IDAtMjYuMjctMTEuNzg4LTI2LjI3LTI2LjI3MXMxMS43ODMtMjYuMjcgMjYuMjctMjYuMjcgMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3YzAgMTQuNDgzLTExLjc4MyAyNi4yNzEtMjYuMjcgMjYuMjcxeiIvPjwvc3ZnPg=="
 | 
			
		||||
)
 | 
			
		||||
public class TbKafkaNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,9 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
 | 
			
		||||
        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. ",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbTransformationNodeToEmailConfig")
 | 
			
		||||
        configDirective = "tbTransformationNodeToEmailConfig",
 | 
			
		||||
        icon = "email"
 | 
			
		||||
)
 | 
			
		||||
public class TbMsgToEmailNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
    private static final ObjectMapper MAPPER = new ObjectMapper();
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "send email",
 | 
			
		||||
        configClazz = TbSendEmailNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Log incoming messages using JS script for transformation Message into String",
 | 
			
		||||
@ -43,7 +43,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
                "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
 | 
			
		||||
                "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeSendEmailConfig"
 | 
			
		||||
        configDirective = "tbActionNodeSendEmailConfig",
 | 
			
		||||
        icon = "send"
 | 
			
		||||
)
 | 
			
		||||
public class TbSendEmailNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "mqtt",
 | 
			
		||||
        configClazz = TbMqttNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Publish messages to MQTT broker",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbActionNodeMqttConfig"
 | 
			
		||||
        configDirective = "tbActionNodeMqttConfig",
 | 
			
		||||
        icon = "call_split"
 | 
			
		||||
)
 | 
			
		||||
public class TbMqttNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "rabbitmq",
 | 
			
		||||
        configClazz = TbRabbitMqNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Publish messages to RabbitMQ",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeRabbitMqConfig"
 | 
			
		||||
        configDirective = "tbActionNodeRabbitMqConfig",
 | 
			
		||||
        iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgeT0iMHB4IiB4PSIwcHgiIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIHN0cm9rZS13aWR0aD0iLjg0OTU2IiBkPSJtODYwLjQ3IDQxNi4zMmgtMjYyLjAxYy0xMi45MTMgMC0yMy42MTgtMTAuNzA0LTIzLjYxOC0yMy42MTh2LTI3Mi43MWMwLTIwLjMwNS0xNi4yMjctMzYuMjc2LTM2LjI3Ni0zNi4yNzZoLTkzLjc5MmMtMjAuMzA1IDAtMzYuMjc2IDE2LjIyNy0zNi4yNzYgMzYuMjc2djI3MC44NGMtMC4yNTQ4NyAxNC4xMDMtMTEuNDY5IDI1LjU3Mi0yNS43NDIgMjUuNTcybC04NS42MzYgMC42Nzk2NWMtMTQuMTAzIDAtMjUuNTcyLTExLjQ2OS0yNS41NzItMjUuNTcybDAuNjc5NjUtMjcxLjUyYzAtMjAuMzA1LTE2LjIyNy0zNi4yNzYtMzYuMjc2LTM2LjI3NmgtOTMuNTM3Yy0yMC4zMDUgMC0zNi4yNzYgMTYuMjI3LTM2LjI3NiAzNi4yNzZ2NzYzLjg0YzAgMTguMDk2IDE0Ljc4MiAzMi40NTMgMzIuNDUzIDMyLjQ1M2g3MjIuODFjMTguMDk2IDAgMzIuNDUzLTE0Ljc4MiAzMi40NTMtMzIuNDUzdi00MzUuMzFjLTEuMTg5NC0xOC4xODEtMTUuMjkyLTMyLjE5OC0zMy4zODgtMzIuMTk4em0tMTIyLjY4IDI4Ny4wN2MwIDIzLjYxOC0xOC44NiA0Mi40NzgtNDIuNDc4IDQyLjQ3OGgtNzMuOTk3Yy0yMy42MTggMC00Mi40NzgtMTguODYtNDIuNDc4LTQyLjQ3OHYtNzQuMjUyYzAtMjMuNjE4IDE4Ljg2LTQyLjQ3OCA0Mi40NzgtNDIuNDc4aDczLjk5N2MyMy42MTggMCA0Mi40NzggMTguODYgNDIuNDc4IDQyLjQ3OHoiLz48L3N2Zz4="
 | 
			
		||||
)
 | 
			
		||||
public class TbRabbitMqNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
 | 
			
		||||
import org.springframework.util.concurrent.ListenableFuture;
 | 
			
		||||
import org.springframework.util.concurrent.ListenableFutureCallback;
 | 
			
		||||
import org.springframework.web.client.*;
 | 
			
		||||
import org.springframework.web.client.AsyncRestTemplate;
 | 
			
		||||
import org.springframework.web.client.HttpClientErrorException;
 | 
			
		||||
import org.thingsboard.rule.engine.TbNodeUtils;
 | 
			
		||||
import org.thingsboard.rule.engine.api.*;
 | 
			
		||||
import org.thingsboard.server.common.data.plugin.ComponentType;
 | 
			
		||||
@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
 | 
			
		||||
 | 
			
		||||
import javax.net.ssl.SSLException;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
        type = ComponentType.ACTION,
 | 
			
		||||
        type = ComponentType.EXTERNAL,
 | 
			
		||||
        name = "rest api call",
 | 
			
		||||
        configClazz = TbRestApiCallNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Invoke REST API calls to external REST server",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeRestApiCallConfig"
 | 
			
		||||
        configDirective = "tbActionNodeRestApiCallConfig",
 | 
			
		||||
        iconUrl = "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB2ZXJzaW9uPSIxLjEiIHk9IjBweCIgeD0iMHB4Ij48ZyB0cmFuc2Zvcm09Im1hdHJpeCguOTQ5NzUgMCAwIC45NDk3NSAxNy4xMiAyNi40OTIpIj48cGF0aCBkPSJtMTY5LjExIDEwOC41NGMtOS45MDY2IDAuMDczNC0xOS4wMTQgNi41NzI0LTIyLjAxNCAxNi40NjlsLTY5Ljk5MyAyMzEuMDhjLTMuNjkwNCAxMi4xODEgMy4yODkyIDI1LjIyIDE1LjQ2OSAyOC45MSAyLjIyNTkgMC42NzQ4MSA0LjQ5NjkgMSA2LjcyODUgMSA5Ljk3MjEgMCAxOS4xNjUtNi41MTUzIDIyLjE4Mi0xNi40NjdhNi41MjI0IDYuNTIyNCAwIDAgMCAwLjAwMiAtMC4wMDJsNjkuOTktMjMxLjA3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMCAtMC4wMDJjMy42ODU1LTEyLjE4MS0zLjI4Ny0yNS4yMjUtMTUuNDcxLTI4LjkxMi0yLjI4MjUtMC42OTE0NS00LjYxMTYtMS4wMTY5LTYuODk4NC0xem04NC45ODggMGMtOS45MDQ4IDAuMDczNC0xOS4wMTggNi41Njc1LTIyLjAxOCAxNi40NjlsLTY5Ljk4NiAyMzEuMDhjLTMuNjg5OCAxMi4xNzkgMy4yODUzIDI1LjIxNyAxNS40NjUgMjguOTA4IDIuMjI5NyAwLjY3NjQ3IDQuNTAwOCAxLjAwMiA2LjczMjQgMS4wMDIgOS45NzIxIDAgMTkuMTY1LTYuNTE1MyAyMi4xODItMTYuNDY3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMC4wMDIgLTAuMDAybDY5Ljk4OC0yMzEuMDdjMy42OTA4LTEyLjE4MS0zLjI4NTItMjUuMjIzLTE1LjQ2Ny0yOC45MTItMi4yODE0LTAuNjkyMzEtNC42MTA4LTEuMDE4OS02Ljg5ODQtMS4wMDJ6bS0yMTcuMjkgNDIuMjNjLTEyLjcyOS0wLjAwMDg3LTIzLjE4OCAxMC40NTYtMjMuMTg4IDIzLjE4NiAwLjAwMSAxMi43MjggMTAuNDU5IDIzLjE4NiAyMy4xODggMjMuMTg2IDEyLjcyNy0wLjAwMSAyMy4xODMtMTAuNDU5IDIzLjE4NC0yMy4xODYgMC4wMDA4NzYtMTIuNzI4LTEwLjQ1Ni0yMy4xODUtMjMuMTg0LTIzLjE4NnptMCAxNDYuNjRjLTEyLjcyNy0wLjAwMDg3LTIzLjE4NiAxMC40NTUtMjMuMTg4IDIzLjE4NC0wLjAwMDg3MyAxMi43MjkgMTAuNDU4IDIzLjE4OCAyMy4xODggMjMuMTg4IDEyLjcyOC0wLjAwMSAyMy4xODQtMTAuNDYgMjMuMTg0LTIzLjE4OC0wLjAwMS0xMi43MjYtMTAuNDU3LTIzLjE4My0yMy4xODQtMjMuMTg0em0yNzAuNzkgNDIuMjExYy0xMi43MjcgMC0yMy4xODQgMTAuNDU3LTIzLjE4NCAyMy4xODRzMTAuNDU1IDIzLjE4OCAyMy4xODQgMjMuMTg4aDE1NC45OGMxMi43MjkgMCAyMy4xODYtMTAuNDYgMjMuMTg2LTIzLjE4OCAwLjAwMS0xMi43MjgtMTAuNDU4LTIzLjE4NC0yMy4xODYtMjMuMTg0eiIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMzc2IDAgMCAxLjAzNzYgLTcuNTY3NiAtMTQuOTI1KSIgc3Ryb2tlLXdpZHRoPSIxLjI2OTMiLz48L2c+PC9zdmc+"
 | 
			
		||||
)
 | 
			
		||||
public class TbRestApiCallNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
        nodeDescription = "Sends reply to the RPC call from device",
 | 
			
		||||
        nodeDetails = "Expects messages with any message type. Will forward message body to the device.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeRpcReplyConfig"
 | 
			
		||||
        configDirective = "tbActionNodeRpcReplyConfig",
 | 
			
		||||
        icon = "call_merge"
 | 
			
		||||
)
 | 
			
		||||
public class TbSendRPCReplyNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit;
 | 
			
		||||
        nodeDescription = "Sends one-way RPC call to device",
 | 
			
		||||
        nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbActionNodeRpcRequestConfig"
 | 
			
		||||
        configDirective = "tbActionNodeRpcRequestConfig",
 | 
			
		||||
        icon = "call_made"
 | 
			
		||||
)
 | 
			
		||||
public class TbSendRPCRequestNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,8 @@ import java.util.Set;
 | 
			
		||||
        nodeDescription = "Saves attributes data",
 | 
			
		||||
        nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbActionNodeAttributesConfig"
 | 
			
		||||
        configDirective = "tbActionNodeAttributesConfig",
 | 
			
		||||
        icon = "file_upload"
 | 
			
		||||
)
 | 
			
		||||
public class TbMsgAttributesNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,8 @@ import java.util.Map;
 | 
			
		||||
        nodeDescription = "Saves timeseries data",
 | 
			
		||||
        nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbActionNodeTimeseriesConfig"
 | 
			
		||||
        configDirective = "tbActionNodeTimeseriesConfig",
 | 
			
		||||
        icon = "file_upload"
 | 
			
		||||
)
 | 
			
		||||
public class TbMsgTimeseriesNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,9 @@ import java.util.HashSet;
 | 
			
		||||
        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. ",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbTransformationNodeChangeOriginatorConfig")
 | 
			
		||||
        configDirective = "tbTransformationNodeChangeOriginatorConfig",
 | 
			
		||||
        icon = "find_replace"
 | 
			
		||||
)
 | 
			
		||||
public class TbChangeOriginatorNode extends TbAbstractTransformNode {
 | 
			
		||||
 | 
			
		||||
    protected static final String CUSTOMER_SOURCE = "CUSTOMER";
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', [])
 | 
			
		||||
                    clientSide: false
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"],
 | 
			
		||||
            ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
 | 
			
		||||
            ruleChainNodeComponent: {
 | 
			
		||||
                type: 'RULE_CHAIN',
 | 
			
		||||
                name: 'rule chain',
 | 
			
		||||
@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', [])
 | 
			
		||||
                    nodeClass: "tb-action-type",
 | 
			
		||||
                    icon: "flash_on"
 | 
			
		||||
                },
 | 
			
		||||
                EXTERNAL: {
 | 
			
		||||
                    value: "EXTERNAL",
 | 
			
		||||
                    name: "rulenode.type-external",
 | 
			
		||||
                    details: "rulenode.type-external-details",
 | 
			
		||||
                    nodeClass: "tb-external-type",
 | 
			
		||||
                    icon: "cloud_upload"
 | 
			
		||||
                },
 | 
			
		||||
                RULE_CHAIN: {
 | 
			
		||||
                    value: "RULE_CHAIN",
 | 
			
		||||
                    name: "rulenode.type-rule-chain",
 | 
			
		||||
 | 
			
		||||
@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', [])
 | 
			
		||||
                    "type-transformation-details": "Change Message payload and Metadata",
 | 
			
		||||
                    "type-action": "Action",
 | 
			
		||||
                    "type-action-details": "Perform special action",
 | 
			
		||||
                    "type-external": "External",
 | 
			
		||||
                    "type-external-details": "Interacts with external system",
 | 
			
		||||
                    "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.",
 | 
			
		||||
 | 
			
		||||
@ -246,6 +246,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 | 
			
		||||
        var contextInfo = {
 | 
			
		||||
            headerClass: node.nodeClass,
 | 
			
		||||
            icon: node.icon,
 | 
			
		||||
            iconUrl: node.iconUrl,
 | 
			
		||||
            title: node.name,
 | 
			
		||||
            subtitle: node.component.name
 | 
			
		||||
        };
 | 
			
		||||
@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 | 
			
		||||
            var ruleNodeComponent = ruleNodeComponents[i];
 | 
			
		||||
            componentType = ruleNodeComponent.type;
 | 
			
		||||
            var model = vm.ruleNodeTypesModel[componentType].model;
 | 
			
		||||
            var icon = vm.types.ruleNodeType[componentType].icon;
 | 
			
		||||
            var iconUrl = null;
 | 
			
		||||
            if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) {
 | 
			
		||||
                icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon;
 | 
			
		||||
            }
 | 
			
		||||
            if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) {
 | 
			
		||||
                iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl;
 | 
			
		||||
            }
 | 
			
		||||
            var node = {
 | 
			
		||||
                id: 'node-lib-' + componentType + '-' + model.nodes.length,
 | 
			
		||||
                component: ruleNodeComponent,
 | 
			
		||||
                name: '',
 | 
			
		||||
                nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
 | 
			
		||||
                icon: vm.types.ruleNodeType[componentType].icon,
 | 
			
		||||
                icon: icon,
 | 
			
		||||
                iconUrl: iconUrl,
 | 
			
		||||
                x: 30,
 | 
			
		||||
                y: 10+50*model.nodes.length,
 | 
			
		||||
                connectors: []
 | 
			
		||||
@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 | 
			
		||||
            var ruleNode = vm.ruleChainMetaData.nodes[i];
 | 
			
		||||
            var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
 | 
			
		||||
            if (component) {
 | 
			
		||||
                var icon = vm.types.ruleNodeType[component.type].icon;
 | 
			
		||||
                var iconUrl = null;
 | 
			
		||||
                if (component.configurationDescriptor.nodeDefinition.icon) {
 | 
			
		||||
                    icon = component.configurationDescriptor.nodeDefinition.icon;
 | 
			
		||||
                }
 | 
			
		||||
                if (component.configurationDescriptor.nodeDefinition.iconUrl) {
 | 
			
		||||
                    iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
 | 
			
		||||
                }
 | 
			
		||||
                var node = {
 | 
			
		||||
                    id: 'rule-chain-node-' + vm.nextNodeID++,
 | 
			
		||||
                    ruleNodeId: ruleNode.id,
 | 
			
		||||
@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 | 
			
		||||
                    component: component,
 | 
			
		||||
                    name: ruleNode.name,
 | 
			
		||||
                    nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
 | 
			
		||||
                    icon: vm.types.ruleNodeType[component.type].icon,
 | 
			
		||||
                    icon: icon,
 | 
			
		||||
                    iconUrl: iconUrl,
 | 
			
		||||
                    connectors: []
 | 
			
		||||
                };
 | 
			
		||||
                if (component.configurationDescriptor.nodeDefinition.inEnabled) {
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,7 @@
 | 
			
		||||
        -moz-user-select: none;
 | 
			
		||||
        -ms-user-select: none;
 | 
			
		||||
        user-select: none;
 | 
			
		||||
        min-width: 180px;
 | 
			
		||||
        min-width: 150px;
 | 
			
		||||
      }
 | 
			
		||||
      .fc-canvas {
 | 
			
		||||
        background: #f9f9f9;
 | 
			
		||||
@ -161,6 +161,9 @@
 | 
			
		||||
  &.tb-action-type {
 | 
			
		||||
    background-color: #f1928f;
 | 
			
		||||
  }
 | 
			
		||||
  &.tb-external-type {
 | 
			
		||||
    background-color: #fbc766;
 | 
			
		||||
  }
 | 
			
		||||
  &.tb-rule-chain-type {
 | 
			
		||||
    background-color: #d6c4f1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,8 @@
 | 
			
		||||
                    <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
 | 
			
		||||
                        <md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
 | 
			
		||||
                                                      ng-mouseleave="vm.destroyTooltips()">
 | 
			
		||||
                            <md-icon aria-label="node-type-icon"
 | 
			
		||||
                                     class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
 | 
			
		||||
                            <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
 | 
			
		||||
                            <md-expansion-panel-icon></md-expansion-panel-icon>
 | 
			
		||||
                        </md-expansion-panel-collapsed>
 | 
			
		||||
@ -82,6 +84,8 @@
 | 
			
		||||
                            <md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
 | 
			
		||||
                                                       ng-mouseleave="vm.destroyTooltips()"
 | 
			
		||||
                                                       ng-click="vm.$mdExpansionPanel(typeId).collapse()">
 | 
			
		||||
                                <md-icon aria-label="node-type-icon"
 | 
			
		||||
                                         class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
 | 
			
		||||
                                <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
 | 
			
		||||
                                <md-expansion-panel-icon></md-expansion-panel-icon>
 | 
			
		||||
                            </md-expansion-panel-header>
 | 
			
		||||
@ -114,8 +118,10 @@
 | 
			
		||||
                </div>
 | 
			
		||||
                <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
 | 
			
		||||
                    <div class="tb-context-menu-header {{vm.contextInfo.headerClass}}">
 | 
			
		||||
                        <md-icon aria-label="node-type-icon"
 | 
			
		||||
                        <md-icon ng-if="!vm.contextInfo.iconUrl" aria-label="node-type-icon"
 | 
			
		||||
                                 class="material-icons">{{vm.contextInfo.icon}}</md-icon>
 | 
			
		||||
                        <md-icon ng-if="vm.contextInfo.iconUrl" aria-label="node-type-icon"
 | 
			
		||||
                                 md-svg-icon="{{vm.contextInfo.iconUrl}}"></md-icon>
 | 
			
		||||
                        <div flex>
 | 
			
		||||
                            <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div>
 | 
			
		||||
                            <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div>
 | 
			
		||||
 | 
			
		||||
@ -24,8 +24,10 @@
 | 
			
		||||
        ng-mouseleave="callbacks.mouseLeave($event, node)">
 | 
			
		||||
    <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
 | 
			
		||||
    <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"
 | 
			
		||||
        <md-icon ng-if="!node.iconUrl" aria-label="node-type-icon" flex="15"
 | 
			
		||||
                 class="material-icons">{{node.icon}}</md-icon>
 | 
			
		||||
        <md-icon ng-if="node.iconUrl" aria-label="node-type-icon" flex="15"
 | 
			
		||||
                 md-svg-icon="{{node.iconUrl}}"></md-icon>
 | 
			
		||||
        <div layout="column" flex="85" layout-align="center">
 | 
			
		||||
            <span class="tb-node-type">{{ node.component.name }}</span>
 | 
			
		||||
            <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user