mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Support npm subflow modules
This commit is contained in:
committed by
Nick O'Leary
parent
6e1466e411
commit
9a660f3fe9
@@ -303,21 +303,13 @@ class Flow {
|
||||
if (node) {
|
||||
delete this.activeNodes[stopList[i]];
|
||||
if (this.subflowInstanceNodes[stopList[i]]) {
|
||||
try {
|
||||
(function(subflow) {
|
||||
promises.push(stopNode(node,false).then(() => subflow.stop()));
|
||||
})(this.subflowInstanceNodes[stopList[i]]);
|
||||
} catch(err) {
|
||||
node.error(err);
|
||||
}
|
||||
delete this.subflowInstanceNodes[stopList[i]];
|
||||
} else {
|
||||
try {
|
||||
var removed = removedMap[stopList[i]];
|
||||
promises.push(stopNode(node,removed).catch(()=>{}));
|
||||
} catch(err) {
|
||||
node.error(err);
|
||||
}
|
||||
}
|
||||
try {
|
||||
var removed = removedMap[stopList[i]];
|
||||
promises.push(stopNode(node,removed).catch(()=>{}));
|
||||
} catch(err) {
|
||||
node.error(err);
|
||||
}
|
||||
if (removedMap[stopList[i]]) {
|
||||
events.emit("node-status",{
|
||||
|
@@ -208,7 +208,12 @@ class Subflow extends Flow {
|
||||
|
||||
this.node = new Node(subflowInstanceConfig);
|
||||
this.node.on("input", function(msg) { this.send(msg);});
|
||||
this.node.on("close", function() { this.status({}); })
|
||||
// Called when the subflow instance node is being stopped
|
||||
this.node.on("close", function(done) {
|
||||
this.status({});
|
||||
// Stop the complete subflow
|
||||
self.stop().finally(done)
|
||||
})
|
||||
this.node.status = status => this.parent.handleStatus(this.node,status);
|
||||
// Create a context instance
|
||||
// console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)
|
||||
|
@@ -19,6 +19,7 @@ var Log = require("@node-red/util").log;
|
||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
|
||||
|
||||
var envVarExcludes = {};
|
||||
|
||||
function diffNodes(oldNode,newNode) {
|
||||
@@ -66,6 +67,194 @@ function mapEnvVarProperties(obj,prop,flow) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createNode(flow,config) {
|
||||
var newNode = null;
|
||||
var type = config.type;
|
||||
try {
|
||||
var nodeTypeConstructor = typeRegistry.get(type);
|
||||
if (typeof nodeTypeConstructor === "function") {
|
||||
var conf = clone(config);
|
||||
delete conf.credentials;
|
||||
for (var p in conf) {
|
||||
if (conf.hasOwnProperty(p)) {
|
||||
mapEnvVarProperties(conf,p,flow);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
|
||||
newNode = new nodeTypeConstructor(conf);
|
||||
} catch (err) {
|
||||
Log.log({
|
||||
level: Log.ERROR,
|
||||
id:conf.id,
|
||||
type: type,
|
||||
msg: err
|
||||
});
|
||||
}
|
||||
} else if (nodeTypeConstructor) {
|
||||
// console.log(nodeTypeConstructor)
|
||||
var subflowConfig = parseConfig([nodeTypeConstructor.subflow].concat(nodeTypeConstructor.subflow.flow));
|
||||
var instanceConfig = clone(config);
|
||||
instanceConfig.env = clone(nodeTypeConstructor.subflow.env);
|
||||
|
||||
instanceConfig.env = nodeTypeConstructor.subflow.env.map(nodeProp => {
|
||||
var nodePropType;
|
||||
var nodePropValue = config[nodeProp.name];
|
||||
if (nodeProp.type === "cred") {
|
||||
nodePropType = "cred";
|
||||
} else {
|
||||
switch(typeof config[nodeProp.name]) {
|
||||
case "string": nodePropType = "str"; break;
|
||||
case "number": nodePropType = "num"; break;
|
||||
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
|
||||
default:
|
||||
nodePropType = config[nodeProp.name].type;
|
||||
nodePropValue = config[nodeProp.name].value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: nodeProp.name,
|
||||
type: nodePropType,
|
||||
value: nodePropValue
|
||||
}
|
||||
})
|
||||
|
||||
var subflow = require("./Subflow").create(
|
||||
flow,
|
||||
flow.global,
|
||||
subflowConfig.subflows[nodeTypeConstructor.subflow.id],
|
||||
instanceConfig
|
||||
);
|
||||
subflow.start();
|
||||
return subflow.node;
|
||||
|
||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||
}
|
||||
} catch(err) {
|
||||
Log.error(err);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
function parseConfig(config) {
|
||||
var flow = {};
|
||||
flow.allNodes = {};
|
||||
flow.subflows = {};
|
||||
flow.configs = {};
|
||||
flow.flows = {};
|
||||
flow.groups = {};
|
||||
flow.missingTypes = [];
|
||||
|
||||
config.forEach(function(n) {
|
||||
flow.allNodes[n.id] = clone(n);
|
||||
if (n.type === 'tab') {
|
||||
flow.flows[n.id] = n;
|
||||
flow.flows[n.id].subflows = {};
|
||||
flow.flows[n.id].configs = {};
|
||||
flow.flows[n.id].nodes = {};
|
||||
}
|
||||
if (n.type === 'group') {
|
||||
flow.groups[n.id] = n;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: why a separate forEach? this can be merged with above
|
||||
config.forEach(function(n) {
|
||||
if (n.type === 'subflow') {
|
||||
flow.subflows[n.id] = n;
|
||||
flow.subflows[n.id].configs = {};
|
||||
flow.subflows[n.id].nodes = {};
|
||||
flow.subflows[n.id].instances = [];
|
||||
}
|
||||
});
|
||||
var linkWires = {};
|
||||
var linkOutNodes = [];
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
|
||||
var subflowDetails = subflowInstanceRE.exec(n.type);
|
||||
|
||||
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
|
||||
if (flow.missingTypes.indexOf(n.type) === -1) {
|
||||
flow.missingTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
var container = null;
|
||||
if (flow.flows[n.z]) {
|
||||
container = flow.flows[n.z];
|
||||
} else if (flow.subflows[n.z]) {
|
||||
container = flow.subflows[n.z];
|
||||
}
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
if (subflowDetails) {
|
||||
var subflowType = subflowDetails[1]
|
||||
n.subflow = subflowType;
|
||||
flow.subflows[subflowType].instances.push(n)
|
||||
}
|
||||
if (container) {
|
||||
container.nodes[n.id] = n;
|
||||
}
|
||||
} else {
|
||||
if (container) {
|
||||
container.configs[n.id] = n;
|
||||
} else {
|
||||
flow.configs[n.id] = n;
|
||||
flow.configs[n.id]._users = [];
|
||||
}
|
||||
}
|
||||
if (n.type === 'link in' && n.links) {
|
||||
// Ensure wires are present in corresponding link out nodes
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[id] = linkWires[id]||{};
|
||||
linkWires[id][n.id] = true;
|
||||
})
|
||||
} else if (n.type === 'link out' && n.links) {
|
||||
linkWires[n.id] = linkWires[n.id]||{};
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[n.id][id] = true;
|
||||
})
|
||||
linkOutNodes.push(n);
|
||||
}
|
||||
}
|
||||
});
|
||||
linkOutNodes.forEach(function(n) {
|
||||
var links = linkWires[n.id];
|
||||
var targets = Object.keys(links);
|
||||
n.wires = [targets];
|
||||
});
|
||||
|
||||
|
||||
var addedTabs = {};
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
|
||||
for (var prop in n) {
|
||||
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
|
||||
// This property references a global config node
|
||||
flow.configs[n[prop]]._users.push(n.id)
|
||||
}
|
||||
}
|
||||
if (n.z && !flow.subflows[n.z]) {
|
||||
|
||||
if (!flow.flows[n.z]) {
|
||||
flow.flows[n.z] = {type:'tab',id:n.z};
|
||||
flow.flows[n.z].subflows = {};
|
||||
flow.flows[n.z].configs = {};
|
||||
flow.flows[n.z].nodes = {};
|
||||
addedTabs[n.z] = flow.flows[n.z];
|
||||
}
|
||||
if (addedTabs[n.z]) {
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
addedTabs[n.z].nodes[n.id] = n;
|
||||
} else {
|
||||
addedTabs[n.z].configs[n.id] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return flow;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
envVarExcludes = {};
|
||||
@@ -79,123 +268,7 @@ module.exports = {
|
||||
diffNodes: diffNodes,
|
||||
mapEnvVarProperties: mapEnvVarProperties,
|
||||
|
||||
parseConfig: function(config) {
|
||||
var flow = {};
|
||||
flow.allNodes = {};
|
||||
flow.subflows = {};
|
||||
flow.configs = {};
|
||||
flow.flows = {};
|
||||
flow.groups = {};
|
||||
flow.missingTypes = [];
|
||||
|
||||
config.forEach(function(n) {
|
||||
flow.allNodes[n.id] = clone(n);
|
||||
if (n.type === 'tab') {
|
||||
flow.flows[n.id] = n;
|
||||
flow.flows[n.id].subflows = {};
|
||||
flow.flows[n.id].configs = {};
|
||||
flow.flows[n.id].nodes = {};
|
||||
}
|
||||
if (n.type === 'group') {
|
||||
flow.groups[n.id] = n;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: why a separate forEach? this can be merged with above
|
||||
config.forEach(function(n) {
|
||||
if (n.type === 'subflow') {
|
||||
flow.subflows[n.id] = n;
|
||||
flow.subflows[n.id].configs = {};
|
||||
flow.subflows[n.id].nodes = {};
|
||||
flow.subflows[n.id].instances = [];
|
||||
}
|
||||
});
|
||||
var linkWires = {};
|
||||
var linkOutNodes = [];
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
|
||||
var subflowDetails = subflowInstanceRE.exec(n.type);
|
||||
|
||||
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
|
||||
if (flow.missingTypes.indexOf(n.type) === -1) {
|
||||
flow.missingTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
var container = null;
|
||||
if (flow.flows[n.z]) {
|
||||
container = flow.flows[n.z];
|
||||
} else if (flow.subflows[n.z]) {
|
||||
container = flow.subflows[n.z];
|
||||
}
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
if (subflowDetails) {
|
||||
var subflowType = subflowDetails[1]
|
||||
n.subflow = subflowType;
|
||||
flow.subflows[subflowType].instances.push(n)
|
||||
}
|
||||
if (container) {
|
||||
container.nodes[n.id] = n;
|
||||
}
|
||||
} else {
|
||||
if (container) {
|
||||
container.configs[n.id] = n;
|
||||
} else {
|
||||
flow.configs[n.id] = n;
|
||||
flow.configs[n.id]._users = [];
|
||||
}
|
||||
}
|
||||
if (n.type === 'link in' && n.links) {
|
||||
// Ensure wires are present in corresponding link out nodes
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[id] = linkWires[id]||{};
|
||||
linkWires[id][n.id] = true;
|
||||
})
|
||||
} else if (n.type === 'link out' && n.links) {
|
||||
linkWires[n.id] = linkWires[n.id]||{};
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[n.id][id] = true;
|
||||
})
|
||||
linkOutNodes.push(n);
|
||||
}
|
||||
}
|
||||
});
|
||||
linkOutNodes.forEach(function(n) {
|
||||
var links = linkWires[n.id];
|
||||
var targets = Object.keys(links);
|
||||
n.wires = [targets];
|
||||
});
|
||||
|
||||
|
||||
var addedTabs = {};
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
|
||||
for (var prop in n) {
|
||||
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
|
||||
// This property references a global config node
|
||||
flow.configs[n[prop]]._users.push(n.id)
|
||||
}
|
||||
}
|
||||
if (n.z && !flow.subflows[n.z]) {
|
||||
|
||||
if (!flow.flows[n.z]) {
|
||||
flow.flows[n.z] = {type:'tab',id:n.z};
|
||||
flow.flows[n.z].subflows = {};
|
||||
flow.flows[n.z].configs = {};
|
||||
flow.flows[n.z].nodes = {};
|
||||
addedTabs[n.z] = flow.flows[n.z];
|
||||
}
|
||||
if (addedTabs[n.z]) {
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
addedTabs[n.z].nodes[n.id] = n;
|
||||
} else {
|
||||
addedTabs[n.z].configs[n.id] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return flow;
|
||||
},
|
||||
parseConfig: parseConfig,
|
||||
|
||||
diffConfigs: function(oldConfig, newConfig) {
|
||||
var id;
|
||||
@@ -475,36 +548,5 @@ module.exports = {
|
||||
* @param {object} config The node configuration object
|
||||
* @return {Node} The instance of the node
|
||||
*/
|
||||
createNode: function(flow,config) {
|
||||
var newNode = null;
|
||||
var type = config.type;
|
||||
try {
|
||||
var nodeTypeConstructor = typeRegistry.get(type);
|
||||
if (nodeTypeConstructor) {
|
||||
var conf = clone(config);
|
||||
delete conf.credentials;
|
||||
for (var p in conf) {
|
||||
if (conf.hasOwnProperty(p)) {
|
||||
mapEnvVarProperties(conf,p,flow);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
|
||||
newNode = new nodeTypeConstructor(conf);
|
||||
} catch (err) {
|
||||
Log.log({
|
||||
level: Log.ERROR,
|
||||
id:conf.id,
|
||||
type: type,
|
||||
msg: err
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||
}
|
||||
} catch(err) {
|
||||
Log.error(err);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
createNode: createNode
|
||||
}
|
||||
|
@@ -112,6 +112,25 @@ function createNode(node,def) {
|
||||
}
|
||||
}
|
||||
|
||||
function registerSubflow(nodeSet, subflow) {
|
||||
// TODO: extract credentials definition from subflow properties
|
||||
var registeredType = registry.registerSubflow(nodeSet,subflow);
|
||||
|
||||
if (subflow.env) {
|
||||
var creds = {};
|
||||
var hasCreds = false;
|
||||
subflow.env.forEach(e => {
|
||||
if (e.type === "cred") {
|
||||
creds[e.name] = {type: "password"};
|
||||
hasCreds = true;
|
||||
}
|
||||
})
|
||||
if (hasCreds) {
|
||||
credentials.register(registeredType.type,creds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
@@ -196,6 +215,7 @@ module.exports = {
|
||||
|
||||
// Node type registry
|
||||
registerType: registerType,
|
||||
registerSubflow: registerSubflow,
|
||||
getType: registry.get,
|
||||
|
||||
getNodeInfo: registry.getNodeInfo,
|
||||
|
Reference in New Issue
Block a user