mirror of
https://github.com/node-red/node-red.git
synced 2025-12-27 23:34:38 +01:00
Merge branch 'dev' into export-module-info
This commit is contained in:
@@ -73,7 +73,13 @@ RED.nodes = (function() {
|
||||
|
||||
var exports = {
|
||||
setModulePendingUpdated: function(module,version) {
|
||||
moduleList[module].pending_version = version;
|
||||
if (!!RED.plugins.getModule(module)) {
|
||||
// The module updated is a plugin
|
||||
RED.plugins.getModule(module).pending_version = version;
|
||||
} else {
|
||||
moduleList[module].pending_version = version;
|
||||
}
|
||||
|
||||
RED.events.emit("registry:module-updated",{module:module,version:version});
|
||||
},
|
||||
getModule: function(module) {
|
||||
@@ -701,12 +707,15 @@ RED.nodes = (function() {
|
||||
}
|
||||
n["_"] = RED._;
|
||||
}
|
||||
|
||||
// Both node and config node can use a config node
|
||||
updateConfigNodeUsers(newNode, { action: "add" });
|
||||
|
||||
if (n._def.category == "config") {
|
||||
configNodes[n.id] = n;
|
||||
configNodes[n.id] = newNode;
|
||||
} else {
|
||||
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
||||
n.dirty = true;
|
||||
updateConfigNodeUsers(n);
|
||||
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
||||
var nextId = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
@@ -768,9 +777,11 @@ RED.nodes = (function() {
|
||||
var removedLinks = [];
|
||||
var removedNodes = [];
|
||||
var node;
|
||||
|
||||
if (id in configNodes) {
|
||||
node = configNodes[id];
|
||||
delete configNodes[id];
|
||||
updateConfigNodeUsers(node, { action: "remove" });
|
||||
RED.events.emit('nodes:remove',node);
|
||||
RED.workspaces.refresh();
|
||||
} else if (allNodes.hasNode(id)) {
|
||||
@@ -779,6 +790,9 @@ RED.nodes = (function() {
|
||||
delete nodeLinks[id];
|
||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||
removedLinks.forEach(removeLink);
|
||||
updateConfigNodeUsers(node, { action: "remove" });
|
||||
|
||||
// TODO: Legacy code for exclusive config node
|
||||
var updatedConfigNode = false;
|
||||
for (var d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d)) {
|
||||
@@ -792,10 +806,6 @@ RED.nodes = (function() {
|
||||
if (configNode._def.exclusive) {
|
||||
removeNode(node[d]);
|
||||
removedNodes.push(configNode);
|
||||
} else {
|
||||
var users = configNode.users;
|
||||
users.splice(users.indexOf(node),1);
|
||||
RED.events.emit('nodes:change',configNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1032,23 +1042,34 @@ RED.nodes = (function() {
|
||||
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Subflow to the Workspace
|
||||
*
|
||||
* @param {object} sf The Subflow to add.
|
||||
* @param {boolean|undefined} createNewIds Whether to update the name.
|
||||
*/
|
||||
function addSubflow(sf, createNewIds) {
|
||||
if (createNewIds) {
|
||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
||||
return subflows[sfid].name;
|
||||
});
|
||||
// Update the Subflow name to highlight that this is a copy
|
||||
const subflowNames = Object.keys(subflows).map(function (sfid) {
|
||||
return subflows[sfid].name || "";
|
||||
})
|
||||
subflowNames.sort()
|
||||
|
||||
subflowNames.sort();
|
||||
var copyNumber = 1;
|
||||
var subflowName = sf.name;
|
||||
let copyNumber = 1;
|
||||
let subflowName = sf.name;
|
||||
subflowNames.forEach(function(name) {
|
||||
if (subflowName == name) {
|
||||
subflowName = sf.name + " (" + copyNumber + ")";
|
||||
copyNumber++;
|
||||
subflowName = sf.name+" ("+copyNumber+")";
|
||||
}
|
||||
});
|
||||
|
||||
sf.name = subflowName;
|
||||
}
|
||||
|
||||
sf.instances = [];
|
||||
|
||||
subflows[sf.id] = sf;
|
||||
allNodes.addTab(sf.id);
|
||||
linkTabMap[sf.id] = [];
|
||||
@@ -1101,7 +1122,7 @@ RED.nodes = (function() {
|
||||
module: "node-red"
|
||||
}
|
||||
});
|
||||
sf.instances = [];
|
||||
|
||||
sf._def = RED.nodes.getType("subflow:"+sf.id);
|
||||
RED.events.emit("subflows:add",sf);
|
||||
}
|
||||
@@ -1755,7 +1776,8 @@ RED.nodes = (function() {
|
||||
// Remove the old subflow definition - but leave the instances in place
|
||||
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
||||
// Create the list of nodes for the new subflow def
|
||||
var subflowNodes = [n].concat(zMap[n.id]);
|
||||
// Need to sort the list in order to remove missing nodes
|
||||
var subflowNodes = [n].concat(zMap[n.id]).filter((s) => !!s);
|
||||
// Import the new subflow - no clashes should occur as we've removed
|
||||
// the old version
|
||||
var result = importNodes(subflowNodes);
|
||||
@@ -1792,9 +1814,20 @@ RED.nodes = (function() {
|
||||
// Replace config nodes
|
||||
//
|
||||
configNodeIds.forEach(function(id) {
|
||||
removedNodes = removedNodes.concat(convertNode(getNode(id)));
|
||||
const configNode = getNode(id);
|
||||
const currentUserCount = configNode.users;
|
||||
|
||||
// Add a snapshot of the Config Node
|
||||
removedNodes = removedNodes.concat(convertNode(configNode));
|
||||
|
||||
// Remove the Config Node instance
|
||||
removeNode(id);
|
||||
importNodes([newConfigNodes[id]])
|
||||
|
||||
// Import the new one
|
||||
importNodes([newConfigNodes[id]]);
|
||||
|
||||
// Re-attributes the user count
|
||||
getNode(id).users = currentUserCount;
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -2078,6 +2111,8 @@ RED.nodes = (function() {
|
||||
if (matchingSubflow) {
|
||||
subflow_denylist[n.id] = matchingSubflow;
|
||||
} else {
|
||||
const oldId = n.id;
|
||||
|
||||
subflow_map[n.id] = n;
|
||||
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||
nid = getID();
|
||||
@@ -2105,7 +2140,7 @@ RED.nodes = (function() {
|
||||
n.status.id = getID();
|
||||
}
|
||||
new_subflows.push(n);
|
||||
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
|
||||
addSubflow(n,createNewIds || options.importMap[oldId] === "copy");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2119,6 +2154,8 @@ RED.nodes = (function() {
|
||||
activeWorkspace = RED.workspaces.active();
|
||||
}
|
||||
|
||||
const pendingConfigNodes = []
|
||||
const pendingConfigNodeIds = new Set()
|
||||
// Find all config nodes and add them
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
@@ -2178,7 +2215,8 @@ RED.nodes = (function() {
|
||||
type:n.type,
|
||||
info: n.info,
|
||||
users:[],
|
||||
_config:{}
|
||||
_config:{},
|
||||
_configNodeReferences: new Set()
|
||||
};
|
||||
if (!n.z) {
|
||||
delete configNode.z;
|
||||
@@ -2193,6 +2231,9 @@ RED.nodes = (function() {
|
||||
if (def.defaults.hasOwnProperty(d)) {
|
||||
configNode[d] = n[d];
|
||||
configNode._config[d] = JSON.stringify(n[d]);
|
||||
if (def.defaults[d].type) {
|
||||
configNode._configNodeReferences.add(n[d])
|
||||
}
|
||||
}
|
||||
}
|
||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||
@@ -2209,11 +2250,55 @@ RED.nodes = (function() {
|
||||
configNode.id = getID();
|
||||
}
|
||||
node_map[n.id] = configNode;
|
||||
new_nodes.push(configNode);
|
||||
pendingConfigNodes.push(configNode);
|
||||
pendingConfigNodeIds.add(configNode.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to sort new_nodes (which only contains config nodes at this point)
|
||||
// to ensure they get added in the right order. If NodeA depends on NodeB, then
|
||||
// NodeB must be added first.
|
||||
|
||||
// Limit us to 5 full iterations of the list - this should be more than
|
||||
// enough to process the list as config->config node relationships are
|
||||
// not very common
|
||||
let iterationLimit = pendingConfigNodes.length * 5
|
||||
const handledConfigNodes = new Set()
|
||||
while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
|
||||
const node = pendingConfigNodes.shift()
|
||||
let hasPending = false
|
||||
// Loop through the nodes referenced by this node to see if anything
|
||||
// is pending
|
||||
node._configNodeReferences.forEach(id => {
|
||||
if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
|
||||
// This reference is for a node we know is in this import, but
|
||||
// it isn't added yet - flag as pending
|
||||
hasPending = true
|
||||
}
|
||||
})
|
||||
if (!hasPending) {
|
||||
// This node has no pending config node references - safe to add
|
||||
delete node._configNodeReferences
|
||||
new_nodes.push(node)
|
||||
handledConfigNodes.add(node.id)
|
||||
} else {
|
||||
// This node has pending config node references
|
||||
// Put to the back of the queue
|
||||
pendingConfigNodes.push(node)
|
||||
}
|
||||
iterationLimit--
|
||||
}
|
||||
if (pendingConfigNodes.length > 0) {
|
||||
// We exceeded the iteration count. Could be due to reference loops
|
||||
// between the config nodes. At this point, just add the remaining
|
||||
// nodes as-is
|
||||
pendingConfigNodes.forEach(node => {
|
||||
delete node._configNodeReferences
|
||||
new_nodes.push(node)
|
||||
})
|
||||
}
|
||||
|
||||
// Find regular flow nodes and subflow instances
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
@@ -2225,7 +2310,7 @@ RED.nodes = (function() {
|
||||
x:parseFloat(n.x || 0),
|
||||
y:parseFloat(n.y || 0),
|
||||
z:n.z,
|
||||
type:0,
|
||||
type: n.type,
|
||||
info: n.info,
|
||||
changed:false,
|
||||
_config:{}
|
||||
@@ -2286,7 +2371,6 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
node.type = n.type;
|
||||
node._def = def;
|
||||
if (node.type === "group") {
|
||||
node._def = RED.group.def;
|
||||
@@ -2316,6 +2400,15 @@ RED.nodes = (function() {
|
||||
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
|
||||
set: registry.getNodeSet("node-red/unknown")
|
||||
}
|
||||
var orig = {};
|
||||
for (var p in n) {
|
||||
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||
orig[p] = n[p];
|
||||
}
|
||||
}
|
||||
node._orig = orig;
|
||||
node.name = n.type;
|
||||
node.type = "unknown";
|
||||
} else {
|
||||
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
|
||||
parentId = subflow.id;
|
||||
@@ -2383,29 +2476,31 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
if (node._def.category != "config") {
|
||||
if (n.hasOwnProperty('inputs')) {
|
||||
node.inputs = n.inputs;
|
||||
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
|
||||
node.inputs = parseInt(n.inputs, 10);
|
||||
node._config.inputs = JSON.stringify(n.inputs);
|
||||
} else {
|
||||
node.inputs = node._def.inputs;
|
||||
}
|
||||
if (n.hasOwnProperty('outputs')) {
|
||||
node.outputs = n.outputs;
|
||||
if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
|
||||
node.outputs = parseInt(n.outputs, 10);
|
||||
node._config.outputs = JSON.stringify(n.outputs);
|
||||
} else {
|
||||
node.outputs = node._def.outputs;
|
||||
}
|
||||
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
|
||||
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
|
||||
// If 'wires' is longer than outputs, clip wires
|
||||
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
|
||||
node.wires = node.wires.slice(0,node.outputs);
|
||||
} else {
|
||||
// The node declares outputs in its defaults, but has not got a valid value
|
||||
// Defer to the length of the wires array
|
||||
|
||||
// The node declares outputs in its defaults, but has not got a valid value
|
||||
// Defer to the length of the wires array
|
||||
if (node.hasOwnProperty('wires')) {
|
||||
if (isNaN(node.outputs)) {
|
||||
node.outputs = node.wires.length;
|
||||
} else if (node.wires.length > node.outputs) {
|
||||
// If 'wires' is longer than outputs, clip wires
|
||||
console.log("Warning: node.wires longer than node.outputs - trimming wires:", node.id, " wires:", node.wires.length, " outputs:", node.outputs);
|
||||
node.wires = node.wires.slice(0, node.outputs);
|
||||
}
|
||||
}
|
||||
|
||||
for (d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
||||
node[d] = n[d];
|
||||
@@ -2468,11 +2563,28 @@ RED.nodes = (function() {
|
||||
} else {
|
||||
delete n.g
|
||||
}
|
||||
// If importing into a subflow, ensure an outbound-link doesn't get added
|
||||
if (activeSubflow && /^link /.test(n.type) && n.links) {
|
||||
// If importing a link node, ensure both ends of each link are either:
|
||||
// - not in a subflow
|
||||
// - both in the same subflow (not for link call node)
|
||||
if (/^link /.test(n.type) && n.links) {
|
||||
n.links = n.links.filter(function(id) {
|
||||
const otherNode = node_map[id] || RED.nodes.node(id);
|
||||
return (otherNode && otherNode.z === activeWorkspace);
|
||||
if (!otherNode) {
|
||||
// Cannot find other end - remove the link
|
||||
return false
|
||||
}
|
||||
if (otherNode.z === n.z) {
|
||||
// Both ends in the same flow/subflow
|
||||
return true
|
||||
} else if (n.type === "link call" && !getSubflow(otherNode.z)) {
|
||||
// Link call node can call out of a subflow as long as otherNode is
|
||||
// not in a subflow
|
||||
return true
|
||||
} else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
|
||||
// One end is in a subflow - remove the link
|
||||
return false
|
||||
}
|
||||
return true
|
||||
});
|
||||
}
|
||||
for (var d3 in n._def.defaults) {
|
||||
@@ -2485,11 +2597,6 @@ RED.nodes = (function() {
|
||||
nodeList = nodeList.map(function(id) {
|
||||
var node = node_map[id];
|
||||
if (node) {
|
||||
if (node._def.category === 'config') {
|
||||
if (node.users.indexOf(n) === -1) {
|
||||
node.users.push(n);
|
||||
}
|
||||
}
|
||||
return node.id;
|
||||
}
|
||||
return id;
|
||||
@@ -2503,9 +2610,11 @@ RED.nodes = (function() {
|
||||
n = new_subflows[i];
|
||||
n.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
if (node_map.hasOwnProperty(wire.id)) {
|
||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
}
|
||||
});
|
||||
delete input.wires;
|
||||
});
|
||||
@@ -2514,11 +2623,13 @@ RED.nodes = (function() {
|
||||
var link;
|
||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||
} else {
|
||||
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
||||
}
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
if (link) {
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
}
|
||||
});
|
||||
delete output.wires;
|
||||
});
|
||||
@@ -2527,11 +2638,13 @@ RED.nodes = (function() {
|
||||
var link;
|
||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
|
||||
} else {
|
||||
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
|
||||
}
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
if (link) {
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
}
|
||||
});
|
||||
delete n.status.wires;
|
||||
}
|
||||
@@ -2710,25 +2823,79 @@ RED.nodes = (function() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
|
||||
function updateConfigNodeUsers(n) {
|
||||
for (var d in n._def.defaults) {
|
||||
if (n._def.defaults.hasOwnProperty(d)) {
|
||||
var property = n._def.defaults[d];
|
||||
/**
|
||||
* Update any config nodes referenced by the provided node to ensure
|
||||
* their 'users' list is correct.
|
||||
*
|
||||
* @param {object} node The node in which to check if it contains references
|
||||
* @param {object} options Options to apply.
|
||||
* @param {"add" | "remove"} [options.action] Add or remove the node from
|
||||
* the Config Node users list. Default `add`.
|
||||
* @param {boolean} [options.emitEvent] Emit the `nodes:changes` event.
|
||||
* Default true.
|
||||
*/
|
||||
function updateConfigNodeUsers(node, options) {
|
||||
const defaultOptions = { action: "add", emitEvent: true };
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
for (var d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d)) {
|
||||
var property = node._def.defaults[d];
|
||||
if (property.type) {
|
||||
var type = registry.getNodeType(property.type);
|
||||
// Need to ensure the type is a config node to not treat links nodes
|
||||
if (type && type.category == "config") {
|
||||
var configNode = configNodes[n[d]];
|
||||
var configNode = configNodes[node[d]];
|
||||
if (configNode) {
|
||||
if (configNode.users.indexOf(n) === -1) {
|
||||
configNode.users.push(n);
|
||||
RED.events.emit('nodes:change',configNode)
|
||||
if (options.action === "add") {
|
||||
if (configNode.users.indexOf(node) === -1) {
|
||||
configNode.users.push(node);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
}
|
||||
} else if (options.action === "remove") {
|
||||
if (configNode.users.indexOf(node) !== -1) {
|
||||
const users = configNode.users;
|
||||
users.splice(users.indexOf(node), 1);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subflows can have config node env
|
||||
if (node.type.indexOf("subflow:") === 0) {
|
||||
node.env?.forEach((prop) => {
|
||||
if (prop.type === "conf-type" && prop.value) {
|
||||
// Add the node to the config node users
|
||||
const configNode = getNode(prop.value);
|
||||
if (configNode) {
|
||||
if (options.action === "add") {
|
||||
if (configNode.users.indexOf(node) === -1) {
|
||||
configNode.users.push(node);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
}
|
||||
} else if (options.action === "remove") {
|
||||
if (configNode.users.indexOf(node) !== -1) {
|
||||
const users = configNode.users;
|
||||
users.splice(users.indexOf(node), 1);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function flowVersion(version) {
|
||||
@@ -3101,7 +3268,12 @@ RED.nodes = (function() {
|
||||
});
|
||||
RED.events.on('deploy', function () {
|
||||
allNodes.clearState()
|
||||
})
|
||||
});
|
||||
RED.actions.add("core:trigger-selected-nodes-action", function () {
|
||||
const selectedNodes = RED.view.selection().nodes || [];
|
||||
// Triggers the button action of the selected nodes
|
||||
selectedNodes.forEach((node) => RED.view.clickNodeButton(node));
|
||||
});
|
||||
},
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
|
||||
Reference in New Issue
Block a user