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) {
|
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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
|
||||||
|
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) {
|
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) {
|
||||||
|
@ -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",{
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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() {
|
||||||
|
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…
x
Reference in New Issue
Block a user