mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Support npm subflow modules
This commit is contained in:
parent
6e1466e411
commit
9a660f3fe9
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -99,6 +99,8 @@ module.exports = {
|
||||
*/
|
||||
get: registry.getNodeConstructor,
|
||||
|
||||
registerSubflow: registry.registerSubflow,
|
||||
|
||||
/**
|
||||
* Get a node's set information.
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
||||
|
117
packages/node_modules/@node-red/registry/lib/subflow.js
vendored
Normal file
117
packages/node_modules/@node-red/registry/lib/subflow.js
vendored
Normal 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
|
||||
}
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
3
test/unit/@node-red/registry/lib/subflow_spec.js
Normal file
3
test/unit/@node-red/registry/lib/subflow_spec.js
Normal file
@ -0,0 +1,3 @@
|
||||
describe("red/nodes/registry/subflow",function() {
|
||||
it.skip("NEEDS TESTS");
|
||||
});
|
Loading…
Reference in New Issue
Block a user