Support npm subflow modules

This commit is contained in:
Nick O'Leary 2020-08-28 16:36:11 +01:00 committed by Nick O'Leary
parent 6e1466e411
commit 9a660f3fe9
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
13 changed files with 499 additions and 254 deletions

View File

@ -414,18 +414,20 @@ RED.editor = (function() {
for (var cred in credDefinition) { for (var cred in credDefinition) {
if (credDefinition.hasOwnProperty(cred)) { if (credDefinition.hasOwnProperty(cred)) {
var input = $("#" + prefix + '-' + cred); var input = $("#" + prefix + '-' + cred);
var value = input.val(); if (input.length > 0) {
if (credDefinition[cred].type == 'password') { var value = input.val();
node.credentials['has_' + cred] = (value !== ""); if (credDefinition[cred].type == 'password') {
if (value == '__PWRD__') { node.credentials['has_' + cred] = (value !== "");
continue; if (value == '__PWRD__') {
} continue;
changed = true; }
changed = true;
} }
node.credentials[cred] = value; node.credentials[cred] = value;
if (value != node.credentials._[cred]) { if (value != node.credentials._[cred]) {
changed = true; changed = true;
}
} }
} }
} }
@ -465,6 +467,7 @@ RED.editor = (function() {
definition.oneditprepare.call(node); definition.oneditprepare.call(node);
} catch(err) { } catch(err) {
console.log("oneditprepare",node.id,node.type,err.toString()); console.log("oneditprepare",node.id,node.type,err.toString());
console.log(err.stack);
} }
} }
// Now invoke any change handlers added to the fields - passing true // Now invoke any change handlers added to the fields - passing true
@ -1192,7 +1195,7 @@ RED.editor = (function() {
changed = true; changed = true;
} }
} catch(err) { } 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) { for (d in editing_node._def.defaults) {
@ -1890,7 +1893,7 @@ RED.editor = (function() {
try { try {
configTypeDef.oneditsave.call(editing_config_node); configTypeDef.oneditsave.call(editing_config_node);
} catch(err) { } 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; changed = true;
} }
} catch(err) { } 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) { for (d in editing_node._def.defaults) {

View File

@ -1725,22 +1725,54 @@ RED.subflow = (function() {
parentEnv[env.name] = item; parentEnv[env.name] = item;
}) })
} }
} if (node.env) {
for (var i = 0; i < node.env.length; i++) {
if (node.env) { var env = node.env[i];
for (var i = 0; i < node.env.length; i++) { if (parentEnv.hasOwnProperty(env.name)) {
var env = node.env[i]; parentEnv[env.name].type = env.type;
if (parentEnv.hasOwnProperty(env.name)) { parentEnv[env.name].value = env.value;
parentEnv[env.name].type = env.type; } else {
parentEnv[env.name].value = env.value; // envList.push({
} else { // name: env.name,
// envList.push({ // type: env.type,
// name: env.name, // value: env.value,
// 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; return envList;
} }

View File

@ -99,6 +99,8 @@ module.exports = {
*/ */
get: registry.getNodeConstructor, get: registry.getNodeConstructor,
registerSubflow: registry.registerSubflow,
/** /**
* Get a node's set information. * Get a node's set information.
* *

View File

@ -158,13 +158,10 @@ async function loadNodeTemplate(node) {
} }
return node return node
}).catch(err => { }).catch(err => {
node.types = []; // ENOENT means no html file. We can live with that. But any other error
if (err.code === 'ENOENT') { // should be fatal
if (!node.types) { // node.err = "Error: "+node.template+" does not exist";
node.types = []; if (err.code !== 'ENOENT') {
}
node.err = "Error: "+node.template+" does not exist";
} else {
node.types = []; node.types = [];
node.err = err.toString(); node.err = err.toString();
} }

View File

@ -20,6 +20,7 @@ var fs = require("fs");
var library = require("./library"); var library = require("./library");
const {events} = require("@node-red/util") const {events} = require("@node-red/util")
var subflows = require("./subflow");
var settings; var settings;
var loader; var loader;
@ -27,6 +28,8 @@ var nodeConfigCache = {};
var moduleConfigs = {}; var moduleConfigs = {};
var nodeList = []; var nodeList = [];
var nodeConstructors = {}; var nodeConstructors = {};
var subflowModules = {};
var nodeTypeToId = {}; var nodeTypeToId = {};
var moduleNodes = {}; var moduleNodes = {};
@ -36,6 +39,7 @@ function init(_settings,_loader) {
moduleNodes = {}; moduleNodes = {};
nodeTypeToId = {}; nodeTypeToId = {};
nodeConstructors = {}; nodeConstructors = {};
subflowModules = {};
nodeList = []; nodeList = [];
nodeConfigCache = {}; nodeConfigCache = {};
} }
@ -225,6 +229,7 @@ function removeNode(id) {
config.types.forEach(function(t) { config.types.forEach(function(t) {
var typeId = nodeTypeToId[t]; var typeId = nodeTypeToId[t];
if (typeId === id) { if (typeId === id) {
delete subflowModules[t];
delete nodeConstructors[t]; delete nodeConstructors[t];
delete nodeTypeToId[t]; delete nodeTypeToId[t];
} }
@ -393,6 +398,27 @@ function registerNodeConstructor(nodeSet,type,constructor) {
events.emit("type-registered",type); 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) { function getAllNodeConfigs(lang) {
if (!nodeConfigCache[lang]) { if (!nodeConfigCache[lang]) {
var result = ""; var result = "";
@ -447,7 +473,7 @@ function getNodeConstructor(type) {
} }
if (!config || (config.enabled && !config.err)) { if (!config || (config.enabled && !config.err)) {
return nodeConstructors[type]; return nodeConstructors[type] || subflowModules[type];
} }
return null; return null;
} }
@ -457,6 +483,7 @@ function clear() {
moduleConfigs = {}; moduleConfigs = {};
nodeList = []; nodeList = [];
nodeConstructors = {}; nodeConstructors = {};
subflowModules = {};
nodeTypeToId = {}; nodeTypeToId = {};
} }
@ -613,6 +640,7 @@ var registry = module.exports = {
registerNodeConstructor: registerNodeConstructor, registerNodeConstructor: registerNodeConstructor,
getNodeConstructor: getNodeConstructor, getNodeConstructor: getNodeConstructor,
registerSubflow: registerSubflow,
addModule: addModule, addModule: addModule,

View File

@ -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 `<script type="text/javascript">
RED.nodes.registerType("${subflowType}",{
category: "${category}",
color: "${color}",
defaults: ${defaultString},
credentials: ${credentialsString},
inputs:${inputCount},
outputs:${outputCount},
icon: "${icon}",
paletteLabel: "${label}",
label: function() {
return this.name||"${label}";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
RED.subflow.buildEditForm('subflow', this);
},
oneditsave: function() {
var props = RED.subflow.exportSubflowInstanceEnv(this);
var i=0,l=props.length;
for (;i<l;i++) {
var prop = props[i];
if (this._def.defaults[prop.name].ui && this._def.defaults[prop.name].ui.type === "cred") {
this[prop.name] = "";
this.credentials[prop.name] = prop.value || "";
this.credentials['has_' + prop.name] = (this.credentials[prop.name] !== "");
} else {
switch(prop.type) {
case "str": this[prop.name] = prop.value||""; break;
case "bool": this[prop.name] = (typeof prop.value === 'boolean')?prop.value:prop.value === "true" ; break;
case "num": this[prop.name] = (typeof prop.value === 'number')?prop.value:Number(prop.value); break;
default:
this[prop.name] = {
type: prop.type,
value: prop.value||""
}
}
}
}
}
});
</script>
<script type="text/x-red" data-template-name="${subflowType}">
<div class="form-row">
<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>
<input type="text" id="node-input-name" data-i18n="[placeholder]editor:common.label.name">
</div>
<div id="subflow-input-ui"></div>
</script>
`
}
function register(id,subflow) {
return {
subflow: subflow,
type: getSubflowType(subflow),
config: generateSubflowConfig(subflow)
}
}
module.exports = {
register: register
}

View File

@ -83,6 +83,9 @@ function createNodeApi(node) {
red.nodes.registerType = function(type,constructor,opts) { red.nodes.registerType = function(type,constructor,opts) {
runtime.nodes.registerType(node.id,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(log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]); copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) { if (runtime.adminApi) {

View File

@ -303,21 +303,13 @@ class Flow {
if (node) { if (node) {
delete this.activeNodes[stopList[i]]; delete this.activeNodes[stopList[i]];
if (this.subflowInstanceNodes[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]]; delete this.subflowInstanceNodes[stopList[i]];
} else { }
try { try {
var removed = removedMap[stopList[i]]; var removed = removedMap[stopList[i]];
promises.push(stopNode(node,removed).catch(()=>{})); promises.push(stopNode(node,removed).catch(()=>{}));
} catch(err) { } catch(err) {
node.error(err); node.error(err);
}
} }
if (removedMap[stopList[i]]) { if (removedMap[stopList[i]]) {
events.emit("node-status",{ events.emit("node-status",{

View File

@ -208,7 +208,12 @@ class Subflow extends Flow {
this.node = new Node(subflowInstanceConfig); this.node = new Node(subflowInstanceConfig);
this.node.on("input", function(msg) { this.send(msg);}); 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); this.node.status = status => this.parent.handleStatus(this.node,status);
// Create a context instance // Create a context instance
// console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)

View File

@ -19,6 +19,7 @@ var Log = require("@node-red/util").log;
var subflowInstanceRE = /^subflow:(.+)$/; var subflowInstanceRE = /^subflow:(.+)$/;
var typeRegistry = require("@node-red/registry"); var typeRegistry = require("@node-red/registry");
var envVarExcludes = {}; var envVarExcludes = {};
function diffNodes(oldNode,newNode) { 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 = { module.exports = {
init: function(runtime) { init: function(runtime) {
envVarExcludes = {}; envVarExcludes = {};
@ -79,123 +268,7 @@ module.exports = {
diffNodes: diffNodes, diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties, mapEnvVarProperties: mapEnvVarProperties,
parseConfig: function(config) { parseConfig: parseConfig,
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;
},
diffConfigs: function(oldConfig, newConfig) { diffConfigs: function(oldConfig, newConfig) {
var id; var id;
@ -475,36 +548,5 @@ module.exports = {
* @param {object} config The node configuration object * @param {object} config The node configuration object
* @return {Node} The instance of the node * @return {Node} The instance of the node
*/ */
createNode: function(flow,config) { createNode: createNode
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;
}
} }

View File

@ -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) { function init(runtime) {
settings = runtime.settings; settings = runtime.settings;
log = runtime.log; log = runtime.log;
@ -196,6 +215,7 @@ module.exports = {
// Node type registry // Node type registry
registerType: registerType, registerType: registerType,
registerSubflow: registerSubflow,
getType: registry.get, getType: registry.get,
getNodeInfo: registry.getNodeInfo, getNodeInfo: registry.getNodeInfo,

View File

@ -414,60 +414,61 @@ describe("red/nodes/registry/loader",function() {
}); });
}); });
it("load core node files scanned by lfs - missing html file", function(done) { // it("load core node files scanned by lfs - missing html file", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){ // // This is now an okay situation
var result = {}; // stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){
result["node-red"] = { // var result = {};
"name": "node-red", // result["node-red"] = {
"version": "1.2.3", // "name": "node-red",
"nodes": { // "version": "1.2.3",
"DuffNode": { // "nodes": {
"file": path.join(resourcesDir,"DuffNode","DuffNode.js"), // "DuffNode": {
"module": "node-red", // "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
"name": "DuffNode" // "module": "node-red",
} // "name": "DuffNode"
} // }
}; // }
return result; // };
})); // return result;
// }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); //
stubs.push(sinon.stub(registry,"addModule", function(){ return })); // stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
// This module isn't already loaded // stubs.push(sinon.stub(registry,"addModule", function(){ return }));
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); // // 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;}}}); // stubs.push(sinon.stub(nodes,"registerType"));
loader.load().then(function(result) { // 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]; // registry.addModule.called.should.be.true();
module.should.have.property("name","node-red"); // var module = registry.addModule.lastCall.args[0];
module.should.have.property("version","1.2.3"); // module.should.have.property("name","node-red");
module.should.have.property("nodes"); // module.should.have.property("version","1.2.3");
module.nodes.should.have.property("DuffNode"); // module.should.have.property("nodes");
module.nodes.DuffNode.should.have.property("id","node-red/DuffNode"); // module.nodes.should.have.property("DuffNode");
module.nodes.DuffNode.should.have.property("module","node-red"); // module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
module.nodes.DuffNode.should.have.property("name","DuffNode"); // module.nodes.DuffNode.should.have.property("module","node-red");
module.nodes.DuffNode.should.have.property("file"); // module.nodes.DuffNode.should.have.property("name","DuffNode");
module.nodes.DuffNode.should.have.property("template"); // module.nodes.DuffNode.should.have.property("file");
module.nodes.DuffNode.should.have.property("enabled",true); // module.nodes.DuffNode.should.have.property("template");
module.nodes.DuffNode.should.have.property("loaded",false); // module.nodes.DuffNode.should.have.property("enabled",true);
module.nodes.DuffNode.should.have.property("types"); // module.nodes.DuffNode.should.have.property("loaded",false);
module.nodes.DuffNode.types.should.have.a.length(0); // module.nodes.DuffNode.should.have.property("types");
module.nodes.DuffNode.should.have.property("config",""); // module.nodes.DuffNode.types.should.have.a.length(0);
module.nodes.DuffNode.should.have.property("help",{}); // module.nodes.DuffNode.should.have.property("config","");
module.nodes.DuffNode.should.have.property("namespace","node-red"); // module.nodes.DuffNode.should.have.property("help",{});
module.nodes.DuffNode.should.have.property('err'); // module.nodes.DuffNode.should.have.property("namespace","node-red");
module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist"); // module.nodes.DuffNode.should.have.property('err');
// module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
nodes.registerType.called.should.be.false(); //
// nodes.registerType.called.should.be.false();
done(); //
}).catch(function(err) { // done();
done(err); // }).catch(function(err) {
}); // done(err);
}); // });
// });
}); });
describe("#addModule",function() { describe("#addModule",function() {

View File

@ -0,0 +1,3 @@
describe("red/nodes/registry/subflow",function() {
it.skip("NEEDS TESTS");
});