diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
index 4488f86c0..b712d3e99 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
@@ -414,18 +414,20 @@ RED.editor = (function() {
for (var cred in credDefinition) {
if (credDefinition.hasOwnProperty(cred)) {
var input = $("#" + prefix + '-' + cred);
- var value = input.val();
- if (credDefinition[cred].type == 'password') {
- node.credentials['has_' + cred] = (value !== "");
- if (value == '__PWRD__') {
- continue;
- }
- changed = true;
+ if (input.length > 0) {
+ var value = input.val();
+ if (credDefinition[cred].type == 'password') {
+ node.credentials['has_' + cred] = (value !== "");
+ if (value == '__PWRD__') {
+ continue;
+ }
+ changed = true;
- }
- node.credentials[cred] = value;
- if (value != node.credentials._[cred]) {
- changed = true;
+ }
+ node.credentials[cred] = value;
+ if (value != node.credentials._[cred]) {
+ changed = true;
+ }
}
}
}
@@ -465,6 +467,7 @@ RED.editor = (function() {
definition.oneditprepare.call(node);
} catch(err) {
console.log("oneditprepare",node.id,node.type,err.toString());
+ console.log(err.stack);
}
}
// Now invoke any change handlers added to the fields - passing true
@@ -1192,7 +1195,7 @@ RED.editor = (function() {
changed = true;
}
} catch(err) {
- console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
+ console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
@@ -1890,7 +1893,7 @@ RED.editor = (function() {
try {
configTypeDef.oneditsave.call(editing_config_node);
} catch(err) {
- console.log("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
+ console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
}
}
@@ -2457,7 +2460,7 @@ RED.editor = (function() {
changed = true;
}
} catch(err) {
- console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
+ console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
index 3f4dc9d74..49434f6a9 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
@@ -1725,22 +1725,54 @@ RED.subflow = (function() {
parentEnv[env.name] = item;
})
}
- }
-
- if (node.env) {
- for (var i = 0; i < node.env.length; i++) {
- var env = node.env[i];
- if (parentEnv.hasOwnProperty(env.name)) {
- parentEnv[env.name].type = env.type;
- parentEnv[env.name].value = env.value;
- } else {
- // envList.push({
- // name: env.name,
- // type: env.type,
- // value: env.value,
- // });
+ if (node.env) {
+ for (var i = 0; i < node.env.length; i++) {
+ var env = node.env[i];
+ if (parentEnv.hasOwnProperty(env.name)) {
+ parentEnv[env.name].type = env.type;
+ parentEnv[env.name].value = env.value;
+ } else {
+ // envList.push({
+ // name: env.name,
+ // type: env.type,
+ // value: env.value,
+ // });
+ }
}
}
+ } else if (/^sf-/.test(node.type)) {
+ var keys = Object.keys(node._def.defaults);
+ keys.forEach(function(name) {
+ if (name !== 'name') {
+ var prop = node._def.defaults[name];
+ var nodeProp = node[name];
+ var nodePropType;
+ var nodePropValue = nodeProp;
+ if (prop.ui && prop.ui.type === "cred") {
+ nodePropType = "cred";
+ } else {
+ switch(typeof nodeProp) {
+ case "string": nodePropType = "str"; break;
+ case "number": nodePropType = "num"; break;
+ case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
+ default:
+ nodePropType = nodeProp.type;
+ nodePropValue = nodeProp.value;
+ }
+ }
+ var item = {
+ name: name,
+ type: nodePropType,
+ value: nodePropValue,
+ parent: {
+ type: prop.type,
+ value: prop.value
+ },
+ ui: $.extend(true,{},prop.ui)
+ }
+ envList.push(item);
+ }
+ })
}
return envList;
}
diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js
index 426dfcb61..03f979424 100644
--- a/packages/node_modules/@node-red/registry/lib/index.js
+++ b/packages/node_modules/@node-red/registry/lib/index.js
@@ -99,6 +99,8 @@ module.exports = {
*/
get: registry.getNodeConstructor,
+ registerSubflow: registry.registerSubflow,
+
/**
* Get a node's set information.
*
diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js
index 18d202420..a2f737c30 100644
--- a/packages/node_modules/@node-red/registry/lib/loader.js
+++ b/packages/node_modules/@node-red/registry/lib/loader.js
@@ -158,13 +158,10 @@ async function loadNodeTemplate(node) {
}
return node
}).catch(err => {
- node.types = [];
- if (err.code === 'ENOENT') {
- if (!node.types) {
- node.types = [];
- }
- node.err = "Error: "+node.template+" does not exist";
- } else {
+ // ENOENT means no html file. We can live with that. But any other error
+ // should be fatal
+ // node.err = "Error: "+node.template+" does not exist";
+ if (err.code !== 'ENOENT') {
node.types = [];
node.err = err.toString();
}
diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js
index a91735071..159ec96da 100644
--- a/packages/node_modules/@node-red/registry/lib/registry.js
+++ b/packages/node_modules/@node-red/registry/lib/registry.js
@@ -20,6 +20,7 @@ var fs = require("fs");
var library = require("./library");
const {events} = require("@node-red/util")
+var subflows = require("./subflow");
var settings;
var loader;
@@ -27,6 +28,8 @@ var nodeConfigCache = {};
var moduleConfigs = {};
var nodeList = [];
var nodeConstructors = {};
+var subflowModules = {};
+
var nodeTypeToId = {};
var moduleNodes = {};
@@ -36,6 +39,7 @@ function init(_settings,_loader) {
moduleNodes = {};
nodeTypeToId = {};
nodeConstructors = {};
+ subflowModules = {};
nodeList = [];
nodeConfigCache = {};
}
@@ -225,6 +229,7 @@ function removeNode(id) {
config.types.forEach(function(t) {
var typeId = nodeTypeToId[t];
if (typeId === id) {
+ delete subflowModules[t];
delete nodeConstructors[t];
delete nodeTypeToId[t];
}
@@ -393,6 +398,27 @@ function registerNodeConstructor(nodeSet,type,constructor) {
events.emit("type-registered",type);
}
+function registerSubflow(nodeSet, subflow) {
+ var nodeSetInfo = getFullNodeInfo(nodeSet);
+
+ const result = subflows.register(nodeSet,subflow);
+
+ if (subflowModules.hasOwnProperty(result.type)) {
+ throw new Error(result.type+" already registered");
+ }
+
+ if (nodeSetInfo) {
+ if (nodeSetInfo.types.indexOf(result.type) === -1) {
+ nodeSetInfo.types.push(result.type);
+ nodeTypeToId[result.type] = nodeSetInfo.id;
+ }
+ nodeSetInfo.config = result.config;
+ }
+ subflowModules[result.type] = result;
+ events.emit("type-registered",result.type);
+ return result;
+}
+
function getAllNodeConfigs(lang) {
if (!nodeConfigCache[lang]) {
var result = "";
@@ -447,7 +473,7 @@ function getNodeConstructor(type) {
}
if (!config || (config.enabled && !config.err)) {
- return nodeConstructors[type];
+ return nodeConstructors[type] || subflowModules[type];
}
return null;
}
@@ -457,6 +483,7 @@ function clear() {
moduleConfigs = {};
nodeList = [];
nodeConstructors = {};
+ subflowModules = {};
nodeTypeToId = {};
}
@@ -613,6 +640,7 @@ var registry = module.exports = {
registerNodeConstructor: registerNodeConstructor,
getNodeConstructor: getNodeConstructor,
+ registerSubflow: registerSubflow,
addModule: addModule,
diff --git a/packages/node_modules/@node-red/registry/lib/subflow.js b/packages/node_modules/@node-red/registry/lib/subflow.js
new file mode 100644
index 000000000..a398d3607
--- /dev/null
+++ b/packages/node_modules/@node-red/registry/lib/subflow.js
@@ -0,0 +1,117 @@
+function getSubflowType(subflow) {
+ return "sf-"+subflow.id
+}
+
+function generateSubflowConfig(subflow) {
+
+ const subflowType = getSubflowType(subflow)
+ const label = subflow.name || subflowType;
+ const category = subflow.category || "function";
+ const color = subflow.color || "#C0DEED";
+ const inputCount = subflow.in?subflow.in.length:0;
+ const outputCount = subflow.out?subflow.out.length:0;
+ const icon = subflow.icon || "arrow-in.svg";
+
+ const defaults = {
+ name: {value: ""}
+ }
+
+ const credentials = {}
+
+ if (subflow.env) {
+ subflow.env.forEach(prop => {
+ var defaultValue;
+
+ switch(prop.type) {
+ case "cred": defaultValue = ""; break;
+ case "str": defaultValue = prop.value||""; break;
+ case "bool": defaultValue = (typeof prop.value === 'boolean')?prop.value:prop.value === "true" ; break;
+ case "num": defaultValue = (typeof prop.value === 'number')?prop.value:Number(prop.value); break;
+ default:
+ defaultValue = {
+ type: prop.type,
+ value: prop.value||""
+ }
+ }
+
+
+
+ defaults[prop.name] = {
+ value: defaultValue,
+ ui: prop.ui
+ }
+ if (prop.type === 'cred') {
+ defaults[prop.name].ui.type = "cred";
+ credentials[prop.name] = {type:"password"}
+ }
+ })
+ }
+ const defaultString = JSON.stringify(defaults);
+ const credentialsString = JSON.stringify(credentials);
+
+ return `
+
+`
+}
+
+
+function register(id,subflow) {
+ return {
+ subflow: subflow,
+ type: getSubflowType(subflow),
+ config: generateSubflowConfig(subflow)
+ }
+}
+
+module.exports = {
+ register: register
+}
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js
index 6e7609fd5..94690d6cb 100644
--- a/packages/node_modules/@node-red/registry/lib/util.js
+++ b/packages/node_modules/@node-red/registry/lib/util.js
@@ -83,6 +83,9 @@ function createNodeApi(node) {
red.nodes.registerType = function(type,constructor,opts) {
runtime.nodes.registerType(node.id,type,constructor,opts);
}
+ red.nodes.registerSubflow = function(subflowDef) {
+ runtime.nodes.registerSubflow(node.id,subflowDef)
+ }
copyObjectProperties(log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) {
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
index 307da6d62..dbccf3a3d 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
@@ -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",{
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js
index 87facdc23..84477b3f7 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js
@@ -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)
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js
index d5a8cba9f..4c4cdebcb 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/util.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js
@@ -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
}
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
index 7c6b6085e..2bc7fd61e 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
@@ -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,
diff --git a/test/unit/@node-red/registry/lib/loader_spec.js b/test/unit/@node-red/registry/lib/loader_spec.js
index d62a8a9b4..274a20d44 100644
--- a/test/unit/@node-red/registry/lib/loader_spec.js
+++ b/test/unit/@node-red/registry/lib/loader_spec.js
@@ -414,60 +414,61 @@ describe("red/nodes/registry/loader",function() {
});
});
- it("load core node files scanned by lfs - missing html file", function(done) {
- stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){
- var result = {};
- result["node-red"] = {
- "name": "node-red",
- "version": "1.2.3",
- "nodes": {
- "DuffNode": {
- "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
- "module": "node-red",
- "name": "DuffNode"
- }
- }
- };
- return result;
- }));
-
- stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
- stubs.push(sinon.stub(registry,"addModule", function(){ return }));
- // This module isn't already loaded
- stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
-
- stubs.push(sinon.stub(nodes,"registerType"));
- loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
- loader.load().then(function(result) {
-
- registry.addModule.called.should.be.true();
- var module = registry.addModule.lastCall.args[0];
- module.should.have.property("name","node-red");
- module.should.have.property("version","1.2.3");
- module.should.have.property("nodes");
- module.nodes.should.have.property("DuffNode");
- module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
- module.nodes.DuffNode.should.have.property("module","node-red");
- module.nodes.DuffNode.should.have.property("name","DuffNode");
- module.nodes.DuffNode.should.have.property("file");
- module.nodes.DuffNode.should.have.property("template");
- module.nodes.DuffNode.should.have.property("enabled",true);
- module.nodes.DuffNode.should.have.property("loaded",false);
- module.nodes.DuffNode.should.have.property("types");
- module.nodes.DuffNode.types.should.have.a.length(0);
- module.nodes.DuffNode.should.have.property("config","");
- module.nodes.DuffNode.should.have.property("help",{});
- module.nodes.DuffNode.should.have.property("namespace","node-red");
- module.nodes.DuffNode.should.have.property('err');
- module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
-
- nodes.registerType.called.should.be.false();
-
- done();
- }).catch(function(err) {
- done(err);
- });
- });
+ // it("load core node files scanned by lfs - missing html file", function(done) {
+ // // This is now an okay situation
+ // stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){
+ // var result = {};
+ // result["node-red"] = {
+ // "name": "node-red",
+ // "version": "1.2.3",
+ // "nodes": {
+ // "DuffNode": {
+ // "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
+ // "module": "node-red",
+ // "name": "DuffNode"
+ // }
+ // }
+ // };
+ // return result;
+ // }));
+ //
+ // stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
+ // stubs.push(sinon.stub(registry,"addModule", function(){ return }));
+ // // This module isn't already loaded
+ // stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
+ //
+ // stubs.push(sinon.stub(nodes,"registerType"));
+ // loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
+ // loader.load().then(function(result) {
+ //
+ // registry.addModule.called.should.be.true();
+ // var module = registry.addModule.lastCall.args[0];
+ // module.should.have.property("name","node-red");
+ // module.should.have.property("version","1.2.3");
+ // module.should.have.property("nodes");
+ // module.nodes.should.have.property("DuffNode");
+ // module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
+ // module.nodes.DuffNode.should.have.property("module","node-red");
+ // module.nodes.DuffNode.should.have.property("name","DuffNode");
+ // module.nodes.DuffNode.should.have.property("file");
+ // module.nodes.DuffNode.should.have.property("template");
+ // module.nodes.DuffNode.should.have.property("enabled",true);
+ // module.nodes.DuffNode.should.have.property("loaded",false);
+ // module.nodes.DuffNode.should.have.property("types");
+ // module.nodes.DuffNode.types.should.have.a.length(0);
+ // module.nodes.DuffNode.should.have.property("config","");
+ // module.nodes.DuffNode.should.have.property("help",{});
+ // module.nodes.DuffNode.should.have.property("namespace","node-red");
+ // module.nodes.DuffNode.should.have.property('err');
+ // module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
+ //
+ // nodes.registerType.called.should.be.false();
+ //
+ // done();
+ // }).catch(function(err) {
+ // done(err);
+ // });
+ // });
});
describe("#addModule",function() {
diff --git a/test/unit/@node-red/registry/lib/subflow_spec.js b/test/unit/@node-red/registry/lib/subflow_spec.js
new file mode 100644
index 000000000..2aa7db203
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/subflow_spec.js
@@ -0,0 +1,3 @@
+describe("red/nodes/registry/subflow",function() {
+ it.skip("NEEDS TESTS");
+});