From eab512ef22191f3dc6418c4c2da0313bf9419170 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:55:23 +0200 Subject: [PATCH 01/56] Fix save and history of credentials for panes --- .../src/js/ui/editors/panes/properties.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js index cfa72be10..9b18ec35e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js @@ -131,9 +131,13 @@ } } if (node._def.credentials) { - var credDefinition = node._def.credentials; - var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass); - editState.changed = editState.changed || credsChanged; + const credsDefinition = node._def.credentials; + const credsChanged = updateNodeCredentials(node, credsDefinition, this.inputClass); + + if (credsChanged) { + editState.changed = true; + editState.changes.credentials = node.credentials._; + } } } } @@ -178,11 +182,14 @@ var value = input.val(); if (credDefinition[cred].type == 'password') { node.credentials['has_' + cred] = (value !== ""); - if (value == '__PWRD__') { + + // Skip if the credential has not changed + if ((value === '__PWRD__' && node.credentials._['has_' + cred] === true) || + (value === "" && node.credentials._['has_' + cred] === false)) { continue; } - changed = true; + changed = true; } node.credentials[cred] = value; if (value != node.credentials._[cred]) { @@ -193,6 +200,4 @@ } return changed; } - - })(); From 53e092e4840ae03ef53ea0cc461096d3dc553cb5 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:06:58 +0200 Subject: [PATCH 02/56] Add config node to history + handling `changed` prop --- .../editor-client/src/js/ui/editor.js | 231 ++++++++++-------- 1 file changed, 134 insertions(+), 97 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 46dc60fe2..c6a448dd3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1475,134 +1475,171 @@ RED.editor = (function() { }, { id: "node-config-dialog-ok", - text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"), + text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"), class: "primary", click: function() { - var editState = { + // TODO: Already defined + const configProperty = name; + const configType = type; + const configTypeDef = RED.nodes.getType(configType); + + const wasChanged = editing_config_node.changed; + const editState = { changes: {}, changed: false, outputMap: null }; - var configProperty = name; - var configId = editing_config_node.id; - var configType = type; - var configAdding = adding; - var configTypeDef = RED.nodes.getType(configType); - var d; - var input; + + // Call `oneditsave` and search for changes + handleEditSave(editing_config_node, editState); - if (configTypeDef.oneditsave) { - try { - configTypeDef.oneditsave.call(editing_config_node); - } catch(err) { - console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString()); - } - } - - for (d in configTypeDef.defaults) { - if (configTypeDef.defaults.hasOwnProperty(d)) { - var newValue; - input = $("#node-config-input-"+d); - if (input.attr('type') === "checkbox") { - newValue = input.prop('checked'); - } else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") { - newValue = input.text(); - } else { - newValue = input.val(); - } - if (newValue != null && newValue !== editing_config_node[d]) { - if (editing_config_node._def.defaults[d].type) { - if (newValue == "_ADD_") { - newValue = ""; - } - // Change to a related config node - var configNode = RED.nodes.node(editing_config_node[d]); - if (configNode) { - var users = configNode.users; - users.splice(users.indexOf(editing_config_node),1); - RED.events.emit("nodes:change",configNode); - } - configNode = RED.nodes.node(newValue); - if (configNode) { - configNode.users.push(editing_config_node); - RED.events.emit("nodes:change",configNode); - } - } - editing_config_node[d] = newValue; - } - } - } - - activeEditPanes.forEach(function(pane) { + // Search for changes in the edit box (panes) + activeEditPanes.forEach(function (pane) { if (pane.apply) { pane.apply.call(pane, editState); } - }) + }); - editing_config_node.label = configTypeDef.label; - - var scope = $("#red-ui-editor-config-scope").val(); - editing_config_node.z = scope; + // TODO: Why? + editing_config_node.label = configTypeDef.label + // Check if disabled has changed if ($("#node-config-input-node-disabled").prop('checked')) { if (editing_config_node.d !== true) { + editState.changes.d = editing_config_node.d; + editState.changed = true; editing_config_node.d = true; } } else { if (editing_config_node.d === true) { + editState.changes.d = editing_config_node.d; + editState.changed = true; delete editing_config_node.d; } } + // NOTE: must be undefined if no scope used + const scope = $("#red-ui-editor-config-scope").val() || undefined; + + // Check if the scope has changed + if (editing_config_node.z !== scope) { + editState.changes.z = editing_config_node.z; + editState.changed = true; + editing_config_node.z = scope; + } + + // Search for nodes that use this config node that are no longer + // in scope, so must be removed + const historyEvents = []; if (scope) { - // Search for nodes that use this one that are no longer - // in scope, so must be removed - editing_config_node.users = editing_config_node.users.filter(function(n) { - var keep = true; - for (var d in n._def.defaults) { - if (n._def.defaults.hasOwnProperty(d)) { - if (n._def.defaults[d].type === editing_config_node.type && - n[d] === editing_config_node.id && - n.z !== scope) { - keep = false; - // Remove the reference to this node - // and revalidate - n[d] = null; - n.dirty = true; - n.changed = true; - validateNode(n); + const newUsers = editing_config_node.users.filter(function (node) { + let keepNode = false; + + for (const d in node._def.defaults) { + if (node._def.defaults.hasOwnProperty(d)) { + if (node._def.defaults[d].type === editing_config_node.type) { + if (node[d] === editing_config_node.id) { + if (node.z === editing_config_node.z) { + // The node is kept only if at least one property uses + // this config node in the correct scope. + keepNode = true; + } else { + historyEvents.push({ + t: "edit", + node: node, + changes: { [d]: node[d] }, + changed: node.changed, + dirty: node.dirty + }); + + // Remove the reference to the config + node[d] = ""; + } + } } } } - return keep; - }); - } - if (configAdding) { - RED.nodes.add(editing_config_node); - } - - validateNode(editing_config_node); - var validatedNodes = {}; - validatedNodes[editing_config_node.id] = true; - - var userStack = editing_config_node.users.slice(); - while(userStack.length > 0) { - var user = userStack.pop(); - if (!validatedNodes[user.id]) { - validatedNodes[user.id] = true; - if (user.users) { - userStack = userStack.concat(user.users); + // Mark as changed and revalidate this node + if (!keepNode) { + node.changed = true; + node.dirty = true; + validateNode(node); + RED.events.emit("nodes:change", node); } - validateNode(user); + + return keepNode; + }); + + // Check if users are changed + if (editing_config_node.users.length !== newUsers.length) { + editState.changes.users = editing_config_node.users; + editState.changed = true; + editing_config_node.users = newUsers; } } - RED.nodes.dirty(true); - RED.view.redraw(true); - if (!configAdding) { - RED.events.emit("editor:save",editing_config_node); - RED.events.emit("nodes:change",editing_config_node); + + if (editState.changed) { + // Set the congig node as changed + editing_config_node.changed = true; } + + // Now, validate the config node + validateNode(editing_config_node); + + // And validate nodes using this config node too + const validatedNodes = new Set(); + const userStack = editing_config_node.users.slice(); + + validatedNodes.add(editing_config_node.id); + while (userStack.length) { + const node = userStack.pop(); + if (!validatedNodes.has(node.id)) { + validatedNodes.add(node.id); + if (node.users) { + userStack.push(...node.users); + } + validateNode(node); + } + } + + let historyEvent = { + t: "edit", + node: editing_config_node, + changes: editState.changes, + changed: wasChanged, + dirty: RED.nodes.dirty() + }; + + if (historyEvents.length) { + // Need a multi events + historyEvent = { + t: "multi", + events: [historyEvent].concat(historyEvents), + dirty: historyEvent.dirty + }; + } + + if (!adding) { + // This event is triggered when the edit box is saved, + // regardless of whether there are any modifications. + RED.events.emit("editor:save", editing_config_node); + } + + if (editState.changed) { + if (adding) { + RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() }); + // Add the new config node and trigger the `nodes:add` event + RED.nodes.add(editing_config_node); + } else { + RED.history.push(historyEvent); + RED.events.emit("nodes:change", editing_config_node); + } + + RED.nodes.dirty(true); + RED.view.redraw(true); + } + RED.tray.close(function() { var filter = null; // when editing a config via subflow edit panel, the `configProperty` will not From ed4b98b5982720ea005bc1fcf433d01ccb2bddc6 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:22:01 +0200 Subject: [PATCH 03/56] Fix adding users to history if multiple props modified --- .../editor-client/src/js/ui/editor.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index c6a448dd3..1bdd134fb 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1534,6 +1534,7 @@ RED.editor = (function() { if (scope) { const newUsers = editing_config_node.users.filter(function (node) { let keepNode = false; + let nodeModified = null; for (const d in node._def.defaults) { if (node._def.defaults.hasOwnProperty(d)) { @@ -1544,15 +1545,19 @@ RED.editor = (function() { // this config node in the correct scope. keepNode = true; } else { - historyEvents.push({ - t: "edit", - node: node, - changes: { [d]: node[d] }, - changed: node.changed, - dirty: node.dirty - }); + if (!nodeModified) { + nodeModified = { + t: "edit", + node: node, + changes: { [d]: node[d] }, + changed: node.changed, + dirty: node.dirty + }; + } else { + nodeModified.changes[d] = node[d]; + } - // Remove the reference to the config + // Remove the reference to the config node node[d] = ""; } } @@ -1560,6 +1565,11 @@ RED.editor = (function() { } } + // Add the node modified to the history + if (nodeModified) { + historyEvents.push(nodeModified); + } + // Mark as changed and revalidate this node if (!keepNode) { node.changed = true; From cc1c87387b8cd6792b7e8e53ff10f2500b6a462c Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:10:55 +0200 Subject: [PATCH 04/56] Fix the config node users count --- .../@node-red/editor-client/src/js/nodes.js | 70 +++++++++++++------ .../editor-client/src/js/ui/subflow.js | 6 +- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2a7b440f2..14e1598a5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -706,7 +706,7 @@ RED.nodes = (function() { } else { if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; } n.dirty = true; - updateConfigNodeUsers(n); + updateConfigNodeUsers(newNode, { action: "add" }); if (n._def.category == "subflows" && typeof n.i === "undefined") { var nextId = 0; RED.nodes.eachNode(function(node) { @@ -779,6 +779,7 @@ 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" }); var updatedConfigNode = false; for (var d in node._def.defaults) { if (node._def.defaults.hasOwnProperty(d)) { @@ -792,10 +793,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) } } } @@ -1780,9 +1777,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 { @@ -2423,11 +2431,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; @@ -2648,19 +2651,44 @@ 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. + * + * Options: + * - `action` - Add or remove the node from the Config Node users list. Default `add`. + * - `emitEvent` - Emit the `nodes:changes` event. Default true. + * + * @param {object} node The node in which to check if it contains references + * @param {{ action?: "add" | "remove"; emitEvent?: boolean; }} options Options to apply. + */ + 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); 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); + } + } } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 751bf9cfb..fe5e81319 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -508,10 +508,8 @@ RED.subflow = (function() { var activeSubflow = RED.nodes.subflow(id); RED.nodes.eachNode(function(n) { - if (!keepInstanceNodes && n.type == "subflow:"+id) { - removedNodes.push(n); - } - if (n.z == id) { + if (n.z === id || (!keepInstanceNodes && n.type === "subflow:" + id)) { + RED.nodes.updateConfigNodeUsers(n, { action: "remove" }); removedNodes.push(n); } }); From a743764345095ac02300625406af9621cda79754 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:42:26 +0200 Subject: [PATCH 05/56] Fix a node with an invalid number of outputs --- .../@node-red/editor-client/src/js/nodes.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2a7b440f2..f7b61f3a6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2321,29 +2321,29 @@ RED.nodes = (function() { node.type = "unknown"; } if (node._def.category != "config") { - if (n.hasOwnProperty('inputs')) { - node.inputs = n.inputs; + if (n.hasOwnProperty('inputs') && 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') && 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 - node.outputs = node.wires.length; - } + + // The node declares outputs in its defaults, but has not got a valid value + // Defer to the length of the wires array + 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]; From 7950ee124150652f58a643dd675007738456a379 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:16:59 +0200 Subject: [PATCH 06/56] Fix updating the subflow name during a copy --- .../@node-red/editor-client/src/js/nodes.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2a7b440f2..2874a024e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1032,23 +1032,31 @@ 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 || ""; + }).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; } + subflows[sf.id] = sf; allNodes.addTab(sf.id); linkTabMap[sf.id] = []; @@ -2023,6 +2031,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(); @@ -2050,7 +2060,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"); } } } From bea08706cc10558012874d45120fc44bb0fe13c6 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 26 Jun 2024 22:51:08 +0200 Subject: [PATCH 07/56] Handle the import of an incomplete Subflow --- .../@node-red/editor-client/src/js/nodes.js | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2a7b440f2..6b839714f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1049,6 +1049,9 @@ RED.nodes = (function() { }); sf.name = subflowName; } + + sf.instances = []; + subflows[sf.id] = sf; allNodes.addTab(sf.id); linkTabMap[sf.id] = []; @@ -1101,7 +1104,7 @@ RED.nodes = (function() { module: "node-red" } }); - sf.instances = []; + sf._def = RED.nodes.getType("subflow:"+sf.id); RED.events.emit("subflows:add",sf); } @@ -1743,7 +1746,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); @@ -2170,7 +2174,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:{} @@ -2261,6 +2265,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; @@ -2441,9 +2454,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; }); @@ -2452,11 +2467,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; }); @@ -2465,11 +2482,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; } From 10ac7fc369f700e28ba2cf3e6823c2d8069af956 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:08:04 +0200 Subject: [PATCH 08/56] Validate user nodes into history when editing a config node --- .../@node-red/editor-client/src/js/history.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 2fa4e4427..646ec830f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -536,6 +536,24 @@ RED.history = (function() { RED.editor.updateNodeProperties(ev.node,outputMap); RED.editor.validateNode(ev.node); } + // If it's a Config Node, validate user nodes too. + // NOTE: The Config Node must be validated before validating users. + if (ev.node.users) { + const validatedNodes = new Set(); + const userStack = ev.node.users.slice(); + + validatedNodes.add(ev.node.id); + while (userStack.length) { + const node = userStack.pop(); + if (!validatedNodes.has(node.id)) { + validatedNodes.add(node.id); + if (node.users) { + userStack.push(...node.users); + } + RED.editor.validateNode(node); + } + } + } if (ev.links) { inverseEv.createdLinks = []; for (i=0;i Date: Mon, 1 Jul 2024 17:43:39 +0200 Subject: [PATCH 09/56] Update packages/node_modules/@node-red/editor-client/src/js/nodes.js Co-authored-by: Nick O'Leary --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2874a024e..b940bfc32 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1043,7 +1043,8 @@ RED.nodes = (function() { // Update the Subflow name to highlight that this is a copy const subflowNames = Object.keys(subflows).map(function (sfid) { return subflows[sfid].name || ""; - }).sort(); + }) + subflowNames.sort() let copyNumber = 1; let subflowName = sf.name; From 8aec038c2453411fb730debc4a8c9c8adade59a3 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:59:28 +0200 Subject: [PATCH 10/56] Remove duplicate type definition --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 6b839714f..c24da629e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2235,7 +2235,6 @@ RED.nodes = (function() { } } } - node.type = n.type; node._def = def; if (node.type === "group") { node._def = RED.group.def; From 83696abf9db7f433c38dec5f9deb5092f696bb71 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:34:51 +0200 Subject: [PATCH 11/56] Fixes and improvements with comments --- .../src/js/ui/editors/panes/properties.js | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js index 9b18ec35e..8f78b0a9a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js @@ -131,12 +131,15 @@ } } if (node._def.credentials) { - const credsDefinition = node._def.credentials; - const credsChanged = updateNodeCredentials(node, credsDefinition, this.inputClass); + const credDefinition = node._def.credentials; + const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass); - if (credsChanged) { + if (Object.keys(credChanges).length) { editState.changed = true; - editState.changes.credentials = node.credentials._; + editState.changes.credentials = { + ...(editState.changes.credentials || {}), + ...credChanges + }; } } } @@ -165,10 +168,11 @@ * @param node - the node containing the credentials * @param credDefinition - definition of the credentials * @param prefix - prefix of the input fields - * @return {boolean} whether anything has changed + * @return {object} an object containing the modified properties */ function updateNodeCredentials(node, credDefinition, prefix) { - var changed = false; + const changes = {}; + if (!node.credentials) { node.credentials = {_:{}}; } else if (!node.credentials._) { @@ -181,23 +185,32 @@ if (input.length > 0) { var value = input.val(); if (credDefinition[cred].type == 'password') { - node.credentials['has_' + cred] = (value !== ""); - - // Skip if the credential has not changed - if ((value === '__PWRD__' && node.credentials._['has_' + cred] === true) || - (value === "" && node.credentials._['has_' + cred] === false)) { - continue; + if (value === '__PWRD__') { + // A cred value exists - no changes + } else if (value === '' && node.credentials['has_' + cred] === false) { + // Empty cred value exists - no changes + } else if (value === node.credentials[cred]) { + // A cred value exists locally in the editor - no changes + // Like the user sets a value, saves the config, + // reopens the config and save the config again + } else { + changes[cred] = node.credentials[cred]; + node.credentials[cred] = value; } - changed = true; - } - node.credentials[cred] = value; - if (value != node.credentials._[cred]) { - changed = true; + node.credentials['has_' + cred] = (value !== ''); + } else { + // Since these creds are loaded by the editor, + // values can be directly compared + if (value !== node.credentials[cred]) { + changes[cred] = node.credentials[cred]; + node.credentials[cred] = value; + } } } } } - return changed; + + return changes; } })(); From 966064328f6fd92145729f7710da82cd98c3997d Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:33:19 +0200 Subject: [PATCH 12/56] Add `oneditsave` credentials changes to history --- .../editor-client/src/js/ui/editor.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 1bdd134fb..b656af0b0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -804,6 +804,17 @@ RED.editor = (function() { } } + const oldCreds = {}; + if (editing_node._def.credentials) { + for (const prop in editing_node._def.credentials) { + if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { + if (prop in editing_node.credentials) { + oldCreds[prop] = editing_node.credentials[prop]; + } + } + } + } + try { const rc = editing_node._def.oneditsave.call(editing_node); if (rc === true) { @@ -835,6 +846,21 @@ RED.editor = (function() { } } } + + if (editing_node._def.credentials) { + for (const prop in editing_node._def.credentials) { + if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { + if (oldCreds[prop] !== editing_node.credentials[prop]) { + if (editing_node.credentials[prop] === '__PWRD__') { + continue; + } + editState.changes.credentials = editState.changes.credentials || {}; + editState.changes.credentials[prop] = oldCreds[prop]; + editState.changed = true; + } + } + } + } } } From 443492d6eb27be98e5a01176384cf3eea7b99645 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:12:13 +0100 Subject: [PATCH 13/56] Fix `setModulePendingUpdated` with plugins --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 12e0a6ef1..bcd1f8c5a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -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) { From 33a5b2527c929a6b06ed8d6a7da17bed297b5117 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 31 Oct 2024 17:06:13 +0000 Subject: [PATCH 14/56] Make delay node rate limit reset consistent - not send on reset. to fix #4830 --- .../@node-red/nodes/core/function/89-delay.js | 11 +++++---- test/nodes/core/function/89-delay_spec.js | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index b0fc2ed86..caf0bd892 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -253,9 +253,11 @@ module.exports = function(RED) { if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) { node.rate = m.rate; } - send(m); - node.reportDepth(); - node.intervalID = setInterval(sendMsgFromBuffer, node.rate); + if (!msg.hasOwnProperty("reset")) { + send(m); + node.reportDepth(); + node.intervalID = setInterval(sendMsgFromBuffer, node.rate); + } done(); } } @@ -303,7 +305,8 @@ module.exports = function(RED) { node.droppedMsgs++; } } - } else { + } + else { if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) { node.rate = msg.rate; } diff --git a/test/nodes/core/function/89-delay_spec.js b/test/nodes/core/function/89-delay_spec.js index 46b0037bc..4fbf3df54 100644 --- a/test/nodes/core/function/89-delay_spec.js +++ b/test/nodes/core/function/89-delay_spec.js @@ -1009,6 +1009,29 @@ describe('delay Node', function() { }); }); + it('sending a msg with reset to empty queue doesnt send anything', function(done) { + this.timeout(2000); + var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(delayNode, flow, function() { + var delayNode1 = helper.getNode("delayNode1"); + var helperNode1 = helper.getNode("helperNode1"); + var t = Date.now(); + var c = 0; + helperNode1.on("input", function(msg) { + console.log("Shold not get here") + done(e); + }); + + setTimeout( function() { + if (c === 0) { done(); } + }, 250); + + // send test messages + delayNode1.receive({payload:1,topic:"foo",reset:true}); // send something with blank topic + }); + }); + /* Messaging API support */ function mapiDoneTestHelper(done, pauseType, drop, msgAndTimings) { const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); From d3219f0600e11ae17577f2579514d0c212e47208 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 31 Oct 2024 17:21:53 +0000 Subject: [PATCH 15/56] do add to queue in case it needs to also be flushed --- .../@node-red/nodes/core/function/89-delay.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index caf0bd892..8f9964115 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -253,11 +253,15 @@ module.exports = function(RED) { if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) { node.rate = m.rate; } - if (!msg.hasOwnProperty("reset")) { - send(m); - node.reportDepth(); - node.intervalID = setInterval(sendMsgFromBuffer, node.rate); + if (msg.hasOwnProperty("reset")) { + if (msg.hasOwnProperty("flush")) { + node.buffer.push({msg: m, send: send, done: done}); + } } + else { send(m); } + + node.reportDepth(); + node.intervalID = setInterval(sendMsgFromBuffer, node.rate); done(); } } From abe0b60bf7b9f765ef172fe1ecf3b695f12d32a4 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:28:51 +0100 Subject: [PATCH 16/56] Remove disabled node types from QuickAddDialog list --- .../@node-red/editor-client/src/js/ui/typeSearch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 216cc3e40..9a83409da 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -382,7 +382,7 @@ RED.typeSearch = (function() { var items = []; RED.nodes.registry.getNodeTypes().forEach(function(t) { var def = RED.nodes.getType(t); - if (def.category !== 'config' && t !== 'unknown' && t !== 'tab') { + if (def.set.enabled && def.category !== 'config' && t !== 'unknown' && t !== 'tab') { items.push({type:t,def: def, label:getTypeLabel(t,def)}); } }); From 3d9bc265dde7a21f0e6aad83b89af967b10302cc Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:58:11 +0100 Subject: [PATCH 17/56] Handle users of env config nodes when adding/removing subflow node --- .../@node-red/editor-client/src/js/nodes.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 12e0a6ef1..ace560f4a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -809,6 +809,20 @@ RED.nodes = (function() { if (sf) { sf.instances.splice(sf.instances.indexOf(node),1); } + + node.env?.forEach((prop) => { + if (prop.type === "conf-type" && prop.value) { + // Remove the node from the config node users + const configNode = getNode(prop.value); + if (configNode) { + if (configNode.users.indexOf(node) !== -1) { + configNode.users.splice(configNode.users.indexOf(node), 1); + RED.events.emit('nodes:change', configNode); + updatedConfigNode = true; + } + } + } + }); } if (updatedConfigNode) { @@ -2684,6 +2698,22 @@ RED.nodes = (function() { } } } + + // Subflows can have config node env + if (n.type.indexOf("subflow:") === 0) { + n.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 (configNode.users.indexOf(n) === -1) { + configNode.users.push(n); + RED.events.emit('nodes:change', configNode); + } + } + } + }); + } } function flowVersion(version) { From f2d72b1050874f0d049d1bafef015bf13fd82d94 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:59:14 +0100 Subject: [PATCH 18/56] Handle users of env config nodes when saving subflow node config --- .../js/ui/editors/panes/envVarProperties.js | 31 ++++++++++++++++++- .../src/js/ui/editors/panes/properties.js | 1 + .../editor-client/src/js/ui/subflow.js | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js index b004662be..0c7694126 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js @@ -20,10 +20,31 @@ apply: function(editState) { var old_env = node.env; var new_env = []; + if (/^subflow:/.test(node.type)) { + // Get the list of environment variables from the node properties new_env = RED.subflow.exportSubflowInstanceEnv(node); } + if (old_env && old_env.length) { + old_env.forEach(function (prop) { + if (prop.type === "conf-type" && prop.value) { + const stillInUse = new_env?.some((p) => p.type === "conf-type" && p.name === prop.name && p.value === prop.value); + if (!stillInUse) { + // Remove the node from the config node users + // Only for empty value or modified + const configNode = RED.nodes.node(prop.value); + if (configNode) { + if (configNode.users.indexOf(node) !== -1) { + configNode.users.splice(configNode.users.indexOf(node), 1); + RED.events.emit('nodes:change', configNode) + } + } + } + } + }); + } + // Get the values from the Properties table tab var items = this.list.editableList('items'); items.each(function (i,el) { @@ -41,7 +62,6 @@ } }); - if (new_env && new_env.length > 0) { new_env.forEach(function(prop) { if (prop.type === "cred") { @@ -52,6 +72,15 @@ editState.changed = true; } delete prop.value; + } else if (prop.type === "conf-type" && prop.value) { + const configNode = RED.nodes.node(prop.value); + if (configNode) { + if (configNode.users.indexOf(node) === -1) { + // Add the node to the config node users + configNode.users.push(node); + RED.events.emit('nodes:change', configNode); + } + } } }); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js index cfa72be10..3f35d98a8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js @@ -44,6 +44,7 @@ apply: function(editState) { var newValue; var d; + // If the node is a subflow, the node's properties (exepts name) are saved by `envProperties` if (node._def.defaults) { for (d in node._def.defaults) { if (node._def.defaults.hasOwnProperty(d)) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 751bf9cfb..3e1b9a410 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -1362,7 +1362,7 @@ RED.subflow = (function() { item.value = ""+input.prop("checked"); break; case "conf-types": - item.value = input.val() + item.value = input.val() === "_ADD_" ? "" : input.val(); item.type = "conf-type" } if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { From deccfdf654982fcb2127cb061978d2d36939a2fd Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:00:36 +0100 Subject: [PATCH 19/56] Handle users of env config nodes when undo subflow node changes --- .../@node-red/editor-client/src/js/history.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 2fa4e4427..60e71c391 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -453,10 +453,48 @@ RED.history = (function() { RED.events.emit("nodes:change",newConfigNode); } }); + } else if (i === "env" && ev.node.type.indexOf("subflow:") === 0) { + // Subflow can have config node in node.env + let nodeList = ev.node.env || []; + nodeList = nodeList.reduce((list, prop) => { + if (prop.type === "conf-type" && prop.value) { + list.push(prop.value); + } + return list; + }, []); + + nodeList.forEach(function(id) { + const configNode = RED.nodes.node(id); + if (configNode) { + if (configNode.users.indexOf(ev.node) !== -1) { + configNode.users.splice(configNode.users.indexOf(ev.node), 1); + RED.events.emit("nodes:change", configNode); + } + } + }); + + nodeList = ev.changes.env || []; + nodeList = nodeList.reduce((list, prop) => { + if (prop.type === "conf-type" && prop.value) { + list.push(prop.value); + } + return list; + }, []); + + nodeList.forEach(function(id) { + const configNode = RED.nodes.node(id); + if (configNode) { + if (configNode.users.indexOf(ev.node) === -1) { + configNode.users.push(ev.node); + RED.events.emit("nodes:change", configNode); + } + } + }); } ev.node[i] = ev.changes[i]; } } + ev.node.dirty = true; ev.node.changed = ev.changed; From c8a02d53e89efe39605f859f29acc96f1884b1fe Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:06:45 +0100 Subject: [PATCH 20/56] Ensure the node added to config node users is the proxy object --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index ace560f4a..b62f54b27 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -702,11 +702,11 @@ RED.nodes = (function() { n["_"] = RED._; } 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); + updateConfigNodeUsers(newNode); if (n._def.category == "subflows" && typeof n.i === "undefined") { var nextId = 0; RED.nodes.eachNode(function(node) { From 59a133cc13e5e0fc3b7bc8b84272377b9bd820bd Mon Sep 17 00:00:00 2001 From: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:28:40 +0100 Subject: [PATCH 21/56] Need to guard against subflows that doesn't have a set property Co-authored-by: Nick O'Leary --- .../@node-red/editor-client/src/js/ui/typeSearch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 9a83409da..f284f2464 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -382,7 +382,7 @@ RED.typeSearch = (function() { var items = []; RED.nodes.registry.getNodeTypes().forEach(function(t) { var def = RED.nodes.getType(t); - if (def.set.enabled && def.category !== 'config' && t !== 'unknown' && t !== 'tab') { + if (def.set?.enabled !== false && def.category !== 'config' && t !== 'unknown' && t !== 'tab') { items.push({type:t,def: def, label:getTypeLabel(t,def)}); } }); From 046d56d692bde24a1f3d12ea8a9dcc9c2e5c420a Mon Sep 17 00:00:00 2001 From: Ahmed Ghorab Date: Sun, 10 Nov 2024 13:30:41 +0200 Subject: [PATCH 22/56] Add qoutes when installing local tgz to fix spacing in the file path --- .../@node-red/registry/lib/installer.js | 2 +- .../@node-red/registry/lib/installer_spec.js | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 495a956af..3ffd65513 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -144,7 +144,7 @@ async function installModule(module,version,url) { if (url) { if (pkgurlRe.test(url) || localtgzRe.test(url)) { // Git remote url or Tarball url - check the valid package url - installName = url; + installName = localtgzRe.test(url) && slashRe.test(url) ? `"${url}"` : url; isRegistryPackage = false; } else { log.warn(log._("server.install.install-failed-url",{name:module,url:url})); diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js index 7f514e99d..f7ca223e8 100644 --- a/test/unit/@node-red/registry/lib/installer_spec.js +++ b/test/unit/@node-red/registry/lib/installer_spec.js @@ -258,6 +258,29 @@ describe('nodes/registry/installer', function() { }).catch(done); }); + it("succeeds when file path is valid node-red module", function(done) { + var nodeInfo = {nodes:{module:"foo",types:["a"]}}; + + var res = { + code: 0, + stdout:"", + stderr:"" + } + var p = Promise.resolve(res); + p.catch((err)=>{}); + execResponse = p; + + var addModule = sinon.stub(registry,"addModule").callsFake(function(md) { + return Promise.resolve(nodeInfo); + }); + + installer.installModule("foo",null,"/example path/foo-0.1.1.tgz").then(function(info) { + exec.run.lastCall.args[1].should.eql([ 'install', '--no-audit', '--no-update-notifier', '--no-fund', '--save', '--save-prefix=~', '--omit=dev', '--engine-strict', '"/example path/foo-0.1.1.tgz"' ]); + info.should.eql(nodeInfo); + done(); + }).catch(done); + }); + it("triggers preInstall and postInstall hooks", function(done) { let receivedPreEvent,receivedPostEvent; hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; }) From ad615a76c8bb44686d5f481cefcb271184ef4c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B8=BF=E5=88=99?= Date: Thu, 14 Nov 2024 16:31:36 +0800 Subject: [PATCH 23/56] Change groups.length to groups.size Fix wrong length attribute of Set --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index ff0091e65..d59c651d6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -288,7 +288,7 @@ RED.view = (function() { } selectedLinks.clearUnselected() }, - length: () => groups.length, + length: () => groups.size, forEach: (func) => { groups.forEach(func) }, toArray: () => [...groups], clear: function () { From 6d6e6fa416af2196c393b3bdb3bcb78c30616942 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:30:47 +0100 Subject: [PATCH 24/56] Get the env config node from the parent subflow --- packages/node_modules/@node-red/runtime/lib/flows/util.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js index fa25a26d0..6b7f659b9 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js @@ -113,6 +113,10 @@ async function evaluateEnvProperties(flow, env, credentials) { resolve() }); })) + } else if (type === "conf-type" && /^\${[^}]+}$/.test(value)) { + // Get the config node from the parent subflow + const name = value.substring(2, value.length - 1); + value = flow.getSetting(name); } else { try { value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); From 4cb3ccc9844f1852effdde1aa104ec2232aba41a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 18 Nov 2024 16:20:58 +0000 Subject: [PATCH 25/56] Rename variable to avoid confusion in view.js --- .../@node-red/editor-client/src/js/ui/view.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index d59c651d6..245b6db75 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -2689,22 +2689,21 @@ RED.view = (function() { addToRemovedLinks(reconnectResult.removedLinks) } - var startDirty = RED.nodes.dirty(); - var startChanged = false; - var selectedGroups = []; + const startDirty = RED.nodes.dirty(); + let movingSelectedGroups = []; if (movingSet.length() > 0) { for (var i=0;i=0; i--) { - var g = selectedGroups[i]; + for (i = movingSelectedGroups.length-1; i>=0; i--) { + var g = movingSelectedGroups[i]; removedGroups.push(g); RED.nodes.removeGroup(g); } From 94e3fdd7a9dab67264a46350f8c67b30dc137612 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 18 Nov 2024 17:12:28 +0000 Subject: [PATCH 26/56] Validate json dropped into editor to avoid unhelpful error messages Fixes #4962 --- .../editor-client/src/js/ui/clipboard.js | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index 4e16bd3f6..435e4a8cd 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -334,6 +334,30 @@ RED.clipboard = (function() { },100); } + /** + * Validates if the provided string looks like valid flow json + * @param {string} flowString the string to validate + * @returns If valid, returns the node array + */ + function validateFlowString(flowString) { + const res = JSON.parse(flowString) + if (!Array.isArray(res)) { + throw new Error(RED._("clipboard.import.errors.notArray")); + } + for (let i = 0; i < res.length; i++) { + if (typeof res[i] !== "object") { + throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i})); + } + if (!Object.hasOwn(res[i], 'id')) { + throw new Error(RED._("clipboard.import.errors.missingId",{index:i})); + } + if (!Object.hasOwn(res[i], 'type')) { + throw new Error(RED._("clipboard.import.errors.missingType",{index:i})); + } + } + return res + } + var validateImportTimeout; function validateImport() { if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") { @@ -351,21 +375,7 @@ RED.clipboard = (function() { return; } try { - if (!/^\[[\s\S]*\]$/m.test(v)) { - throw new Error(RED._("clipboard.import.errors.notArray")); - } - var res = JSON.parse(v); - for (var i=0;i Date: Mon, 25 Nov 2024 21:07:42 +0100 Subject: [PATCH 27/56] Add a guard to check if wires exist --- .../@node-red/editor-client/src/js/nodes.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index f7b61f3a6..e3df86f6c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2336,12 +2336,14 @@ RED.nodes = (function() { // The node declares outputs in its defaults, but has not got a valid value // Defer to the length of the wires array - 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); + 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) { From f6cf051282301b1e06aa77709deb1f874197f3d9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 2 Dec 2024 17:03:14 +0000 Subject: [PATCH 28/56] Fix junction insert position via context menu --- .../editor-client/src/js/ui/contextMenu.js | 14 ++++--- .../editor-client/src/js/ui/view-tools.js | 41 +++++++++++-------- .../@node-red/editor-client/src/js/ui/view.js | 4 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 698842dbd..53ebe5c4b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -54,15 +54,15 @@ RED.contextMenu = (function () { } } + const scale = RED.view.scale() const offset = $("#red-ui-workspace-chart").offset() - - let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() - let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() + let addX = (options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / scale + let addY = (options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()) / scale if (RED.view.snapGrid) { const gridSize = RED.view.gridSize() - addX = gridSize * Math.floor(addX / gridSize) - addY = gridSize * Math.floor(addY / gridSize) + addX = gridSize * Math.round(addX / gridSize) + addY = gridSize * Math.round(addY / gridSize) } if (RED.settings.theme("menu.menu-item-action-list", true)) { @@ -87,7 +87,9 @@ RED.contextMenu = (function () { }, (hasLinks) ? { // has least 1 wire selected label: RED._("contextMenu.junction"), - onselect: 'core:split-wires-with-junctions', + onselect: function () { + RED.actions.invoke('core:split-wires-with-junctions', { x: addX, y: addY }) + }, disabled: !canEdit || !hasLinks } : { label: RED._("contextMenu.junction"), diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 22cb1eecd..eecd309d1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1154,11 +1154,11 @@ RED.view.tools = (function() { } } - function addJunctionsToWires(wires) { + function addJunctionsToWires(options = {}) { if (RED.workspaces.isLocked()) { return } - let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); + let wiresToSplit = options.wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); if (!wiresToSplit) { return } @@ -1206,21 +1206,26 @@ RED.view.tools = (function() { if (links.length === 0) { return } - let pointCount = 0 - links.forEach(function(l) { - if (l._sliceLocation) { - junction.x += l._sliceLocation.x - junction.y += l._sliceLocation.y - delete l._sliceLocation - pointCount++ - } else { - junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2 - junction.y += l.source.y + l.target.y - pointCount += 2 - } - }) - junction.x = Math.round(junction.x/pointCount) - junction.y = Math.round(junction.y/pointCount) + if (addedJunctions.length === 0 && Object.hasOwn(options, 'x') && Object.hasOwn(options, 'y')) { + junction.x = options.x + junction.y = options.y + } else { + let pointCount = 0 + links.forEach(function(l) { + if (l._sliceLocation) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + delete l._sliceLocation + pointCount++ + } else { + junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2 + junction.y += l.source.y + l.target.y + pointCount += 2 + } + }) + junction.x = Math.round(junction.x/pointCount) + junction.y = Math.round(junction.y/pointCount) + } if (RED.view.snapGrid) { let gridSize = RED.view.gridSize() junction.x = (gridSize*Math.round(junction.x/gridSize)); @@ -1410,7 +1415,7 @@ RED.view.tools = (function() { RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() }) RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() }); - RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() }); + RED.actions.add("core:split-wires-with-junctions", function (options) { addJunctionsToWires(options) }); RED.actions.add("core:generate-node-names", generateNodeNames ) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 245b6db75..61ee3e3f6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -321,8 +321,8 @@ RED.view = (function() { evt.stopPropagation() RED.contextMenu.show({ type: 'workspace', - x:evt.clientX-5, - y:evt.clientY-5 + x: evt.clientX, + y: evt.clientY }) return false }) From e8d81d814cfb4317a4e663f47ffa19f5c4fa2eac Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 Dec 2024 10:15:59 +0000 Subject: [PATCH 29/56] Apply scaleFactor when calculating junction slice positions --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 61ee3e3f6..8ce6dc630 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5174,8 +5174,8 @@ RED.view = (function() { var delta = Infinity; for (var i = 0; i < lineLength; i++) { var linePos = pathLine.getPointAtLength(i); - var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) - var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) + var posDeltaX = Math.abs(linePos.x-(d3.event.offsetX / scaleFactor)) + var posDeltaY = Math.abs(linePos.y-(d3.event.offsetY / scaleFactor)) var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY if (posDelta < delta) { pos = linePos From 0b09cf5fa9f1edc023587925b68708bef28c01a4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 Dec 2024 14:51:32 +0000 Subject: [PATCH 30/56] Update packages/node_modules/@node-red/editor-client/src/js/nodes.js --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index e3df86f6c..a818d4a38 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2336,7 +2336,7 @@ RED.nodes = (function() { // 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 (node.hasOwnProperty('wires')) { if (isNaN(node.outputs)) { node.outputs = node.wires.length; } else if (node.wires.length > node.outputs) { From aee531bf165f14ffa2df40f8f1a22b5185c3d3ed Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 Dec 2024 17:14:04 +0000 Subject: [PATCH 31/56] Update packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../@node-red/editor-client/src/js/ui/subflow.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index fe5e81319..0bc5f11e1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -508,7 +508,10 @@ RED.subflow = (function() { var activeSubflow = RED.nodes.subflow(id); RED.nodes.eachNode(function(n) { - if (n.z === id || (!keepInstanceNodes && n.type === "subflow:" + id)) { + if (!keepInstanceNodes && n.type == "subflow:"+id) { + removedNodes.push(n); + } + if (n.z == id) { RED.nodes.updateConfigNodeUsers(n, { action: "remove" }); removedNodes.push(n); } From 338ddf17de923421decdfa2d84756a80843b88cd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Dec 2024 09:55:09 +0000 Subject: [PATCH 32/56] Update packages/node_modules/@node-red/editor-client/src/js/nodes.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../@node-red/editor-client/src/js/nodes.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 770400132..2359b9321 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2722,14 +2722,14 @@ RED.nodes = (function() { * Update any config nodes referenced by the provided node to ensure * their 'users' list is correct. * - * Options: - * - `action` - Add or remove the node from the Config Node users list. Default `add`. - * - `emitEvent` - Emit the `nodes:changes` event. Default true. - * * @param {object} node The node in which to check if it contains references - * @param {{ action?: "add" | "remove"; emitEvent?: boolean; }} options Options to apply. + * @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 = {}) { + function updateConfigNodeUsers(node, options) { const defaultOptions = { action: "add", emitEvent: true }; options = Object.assign({}, defaultOptions, options); From 92dff4bacd61f083ef80df012f6fbec034e62ca3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Dec 2024 10:41:10 +0000 Subject: [PATCH 33/56] Apply suggestions from code review Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../@node-red/editor-client/src/js/nodes.js | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2359b9321..366120796 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -812,20 +812,6 @@ RED.nodes = (function() { if (sf) { sf.instances.splice(sf.instances.indexOf(node),1); } - - node.env?.forEach((prop) => { - if (prop.type === "conf-type" && prop.value) { - // Remove the node from the config node users - const configNode = getNode(prop.value); - if (configNode) { - if (configNode.users.indexOf(node) !== -1) { - configNode.users.splice(configNode.users.indexOf(node), 1); - RED.events.emit('nodes:change', configNode); - updatedConfigNode = true; - } - } - } - }); } if (updatedConfigNode) { @@ -2764,15 +2750,27 @@ RED.nodes = (function() { } // Subflows can have config node env - if (n.type.indexOf("subflow:") === 0) { - n.env?.forEach((prop) => { + 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 (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); + } + } } } } From f9877f8d0b6559729d3c3cd6bd2d8b9ee86e79f7 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Dec 2024 10:41:27 +0000 Subject: [PATCH 34/56] Update packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../node_modules/@node-red/editor-client/src/js/ui/subflow.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 603dc8d30..3e1b9a410 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -512,7 +512,6 @@ RED.subflow = (function() { removedNodes.push(n); } if (n.z == id) { - RED.nodes.updateConfigNodeUsers(n, { action: "remove" }); removedNodes.push(n); } }); From 39a85c721d76d39ccdd3d804100a84261ec2f5ea Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Dec 2024 13:10:48 +0000 Subject: [PATCH 35/56] Update packages/node_modules/@node-red/editor-client/src/js/history.js --- .../@node-red/editor-client/src/js/history.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 2b5b259e6..73193e5df 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -491,7 +491,13 @@ RED.history = (function() { } }); } - ev.node[i] = ev.changes[i]; + if (i === "credentials" && ev.changes[i]) { + for (const [key, value] of Object.entries(ev.changes[i])) { + ev.node.credentials[key] = value; + } + } else { + ev.node[i] = ev.changes[i]; + } } } From 4e61c54be55643d9068703d95febeda53c60ff88 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Dec 2024 15:49:39 +0000 Subject: [PATCH 36/56] Update packages/node_modules/@node-red/editor-client/src/js/ui/editor.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../node_modules/@node-red/editor-client/src/js/ui/editor.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 635bc8ca2..1ee73c6ea 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -855,9 +855,6 @@ RED.editor = (function() { for (const prop in editing_node._def.credentials) { if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { if (oldCreds[prop] !== editing_node.credentials[prop]) { - if (editing_node.credentials[prop] === '__PWRD__') { - continue; - } editState.changes.credentials = editState.changes.credentials || {}; editState.changes.credentials[prop] = oldCreds[prop]; editState.changed = true; From bfd98aaf22e138f65a911bac1b0998af147fc72a Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 5 Dec 2024 12:24:02 +0100 Subject: [PATCH 37/56] PERF : make single buffer / string file reading faster --- .../node_modules/@node-red/nodes/core/storage/10-file.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.js b/packages/node_modules/@node-red/nodes/core/storage/10-file.js index fea8c490e..5cff2b631 100644 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.js +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.js @@ -339,7 +339,7 @@ module.exports = function(RED) { } else { msg.filename = filename; - var lines = Buffer.from([]); + const bufferArray = []; var spare = ""; var count = 0; var type = "buffer"; @@ -397,7 +397,7 @@ module.exports = function(RED) { } } else { - lines = Buffer.concat([lines,chunk]); + bufferArray.push(chunk); } } }) @@ -413,10 +413,11 @@ module.exports = function(RED) { }) .on('end', function() { if (node.chunk === false) { + const buffer = Buffer.concat(bufferArray); if (node.format === "utf8") { - msg.payload = decode(lines, node.encoding); + msg.payload = decode(buffer, node.encoding); } - else { msg.payload = lines; } + else { msg.payload = buffer; } nodeSend(msg); } else if (node.format === "lines") { From 43a9a3c3b137e2344d1119c60f589ba263df94b3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Dec 2024 15:34:24 +0000 Subject: [PATCH 38/56] Apply zoom scale when calculating annotation positions Fixes #4978 --- .../editor-client/src/js/ui/view-annotations.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js index b08df80ae..f4cda8d2c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js @@ -11,7 +11,7 @@ RED.view.annotations = (function() { } let badgeRDX = 0; let badgeLDX = 0; - + const scale = RED.view.scale() for (let i=0,l=evt.el.__annotations__.length;i Date: Thu, 5 Dec 2024 16:00:54 +0000 Subject: [PATCH 39/56] Ensure node.sep is honoured when generating CSV --- .../@node-red/nodes/core/parsers/70-CSV.js | 18 +++++++++--------- .../nodes/core/parsers/lib/csv/index.js | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index c6a91d61a..92d12d0df 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -171,7 +171,7 @@ module.exports = function(RED) { } // join lines, don't forget to add the last new line msg.payload = ou.join(node.ret) + node.ret; - msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(','); + msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep); if (msg.payload !== '') { send(msg); } @@ -289,14 +289,14 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store; - msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); + msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); delete msg.parts; send(msg); node.store = []; } } else { - msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); + msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); send(msg); // finally send the array } } @@ -304,7 +304,7 @@ module.exports = function(RED) { var len = a.length; for (var i = 0; i < len; i++) { var newMessage = RED.util.cloneMessage(msg); - newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); + newMessage.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); newMessage.payload = a[i]; if (!has_parts) { newMessage.parts = { @@ -367,7 +367,7 @@ module.exports = function(RED) { const sendHeadersAlways = node.hdrout === "all" const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways) const quoteables = [node.sep, node.quo, "\n", "\r"] - const templateQuoteables = [',', '"', "\n", "\r"] + const templateQuoteables = [node.sep, '"', "\n", "\r"] let badTemplateWarnOnce = true const columnStringToTemplateArray = function (col, sep) { @@ -378,9 +378,9 @@ module.exports = function(RED) { } const templateArrayToColumnString = function (template, keepEmptyColumns) { // NOTE: enforce strict column template parsing in RFC4180 mode - const parsed = csv.parse('', {headers: template, headersOnly:true, separator: ',', quote: node.quo, outputStyle: 'array', strict: true }) + const parsed = csv.parse('', {headers: template, headersOnly:true, separator: node.sep, quote: node.quo, outputStyle: 'array', strict: true }) return keepEmptyColumns - ? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables})) + ? parsed.headers.map(e => addQuotes(e || '', { separator: node.sep, quoteables: templateQuoteables})).join(node.sep) : parsed.header // exclues empty columns // TODO: resolve inconsistency between CSV->JSON and JSON->CSV // CSV->JSON: empty columns are excluded @@ -441,7 +441,7 @@ module.exports = function(RED) { if (sendHeaders && node.hdrSent === false) { if (hasTemplate(template) === false) { if (msg.hasOwnProperty("columns")) { - template = columnStringToTemplateArray(msg.columns || "", ",") || [''] + template = columnStringToTemplateArray(msg.columns || "", node.sep) || [''] } else { template = Object.keys(inputData[0]) || [''] @@ -475,7 +475,7 @@ module.exports = function(RED) { } else { /*** row is an object ***/ if (hasTemplate(template) === false && (msg.hasOwnProperty("columns"))) { - template = columnStringToTemplateArray(msg.columns || "", ",") + template = columnStringToTemplateArray(msg.columns || "", node.sep) } if (hasTemplate(template) === false) { /*** row is an object but we still don't have a template ***/ diff --git a/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js b/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js index 73cf4b292..dd2fb2db9 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js @@ -200,9 +200,9 @@ function parse(csvIn, parseOptions) { if (!headers[i]) { continue } - quotedHeaders.push(quoteCell(headers[i], { quote, separator: ',' })) + quotedHeaders.push(quoteCell(headers[i], { quote, separator })) } - finalResult.header = quotedHeaders.join(',') // always quote headers and join with comma + finalResult.header = quotedHeaders.join(separator) // always quote headers and join with comma // output is an array of arrays [[],[],[]] if (ouputArrays || headersOnly) { From 66bd1feb47b7b4fc2982a6daef21ee39fce772f9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 6 Dec 2024 13:45:48 +0000 Subject: [PATCH 40/56] Apply suggestions from code review Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../node_modules/@node-red/editor-client/src/js/history.js | 6 ++++++ .../@node-red/editor-client/src/js/ui/editor.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 73193e5df..d4fbc5102 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -492,8 +492,14 @@ RED.history = (function() { }); } if (i === "credentials" && ev.changes[i]) { + // Reset - Only want to keep the changes + inverseEv.changes[i] = {}; for (const [key, value] of Object.entries(ev.changes[i])) { + inverseEv.changes[i][key] = ev.node.credentials[key]; ev.node.credentials[key] = value; + if (ev.node._def.credentials[key]?.type === 'password') { + ev.node.credentials['has_' + key] = !!value; + } } } else { ev.node[i] = ev.changes[i]; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 1ee73c6ea..bb4457f8c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -855,6 +855,12 @@ RED.editor = (function() { for (const prop in editing_node._def.credentials) { if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { if (oldCreds[prop] !== editing_node.credentials[prop]) { + if (editing_node.credentials[prop] === '__PWRD__') { + // The password may not exist in oldCreds + // The value '__PWRD__' means the password exists, + // so ignore this change + continue; + } editState.changes.credentials = editState.changes.credentials || {}; editState.changes.credentials[prop] = oldCreds[prop]; editState.changed = true; From 89e40a0b8fe49929cebc578e6f5e45f3bf427057 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 6 Dec 2024 16:15:37 +0000 Subject: [PATCH 41/56] Rework saving of credentials to undo history --- .../@node-red/editor-client/src/js/history.js | 9 +++--- .../editor-client/src/js/ui/editor.js | 29 ------------------- .../src/js/ui/editors/panes/properties.js | 1 + 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index d4fbc5102..2b95b35d3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -495,11 +495,12 @@ RED.history = (function() { // Reset - Only want to keep the changes inverseEv.changes[i] = {}; for (const [key, value] of Object.entries(ev.changes[i])) { - inverseEv.changes[i][key] = ev.node.credentials[key]; - ev.node.credentials[key] = value; - if (ev.node._def.credentials[key]?.type === 'password') { - ev.node.credentials['has_' + key] = !!value; + // Edge case: node.credentials is cleared after a deploy, so we can't + // capture values for the inverse event when undoing past a deploy + if (ev.node.credentials) { + inverseEv.changes[i][key] = ev.node.credentials[key]; } + ev.node.credentials[key] = value; } } else { ev.node[i] = ev.changes[i]; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index bb4457f8c..fbb822418 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -808,17 +808,6 @@ RED.editor = (function() { } } - const oldCreds = {}; - if (editing_node._def.credentials) { - for (const prop in editing_node._def.credentials) { - if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { - if (prop in editing_node.credentials) { - oldCreds[prop] = editing_node.credentials[prop]; - } - } - } - } - try { const rc = editing_node._def.oneditsave.call(editing_node); if (rc === true) { @@ -850,24 +839,6 @@ RED.editor = (function() { } } } - - if (editing_node._def.credentials) { - for (const prop in editing_node._def.credentials) { - if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { - if (oldCreds[prop] !== editing_node.credentials[prop]) { - if (editing_node.credentials[prop] === '__PWRD__') { - // The password may not exist in oldCreds - // The value '__PWRD__' means the password exists, - // so ignore this change - continue; - } - editState.changes.credentials = editState.changes.credentials || {}; - editState.changes.credentials[prop] = oldCreds[prop]; - editState.changed = true; - } - } - } - } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js index 396317052..88472f97e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js @@ -195,6 +195,7 @@ // Like the user sets a value, saves the config, // reopens the config and save the config again } else { + changes['has_' + cred] = node.credentials['has_' + cred]; changes[cred] = node.credentials[cred]; node.credentials[cred] = value; } From 56a4530ec65f7004e2ae05ff4ef06fd4017035d0 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 6 Dec 2024 22:22:37 +0000 Subject: [PATCH 42/56] Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973) * Fix delay node not dropping when nodeMessageBufferMaxLength is set to close #4966 * Rmove redundant codes * Tidy up code removal --------- Co-authored-by: Nick O'Leary --- .../@node-red/nodes/core/function/89-delay.js | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index 8f9964115..17cbd2f4f 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -291,43 +291,23 @@ module.exports = function(RED) { } } else if (!msg.hasOwnProperty("reset")) { - if (maxKeptMsgsCount(node) > 0) { - if (node.intervalID === -1) { - node.send(msg); - node.intervalID = setInterval(sendMsgFromBuffer, node.rate); - } else { - if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) { - node.rate = msg.rate; - clearInterval(node.intervalID); - node.intervalID = setInterval(sendMsgFromBuffer, node.rate); - } - if (node.buffer.length < _maxKeptMsgsCount) { - var m = RED.util.cloneMessage(msg); - node.buffer.push({msg: m, send: send, done: done}); - } else { - node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid); - node.droppedMsgs++; - } - } + if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) { + node.rate = msg.rate; } - else { - if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) { - node.rate = msg.rate; - } - var timeSinceLast; - if (node.lastSent) { - timeSinceLast = process.hrtime(node.lastSent); - } - if (!node.lastSent) { // ensuring that we always send the first message - node.lastSent = process.hrtime(); - send(msg); - } - else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) { - node.lastSent = process.hrtime(); - send(msg); - } else if (node.outputs === 2) { - send([null,msg]) - } + var timeSinceLast; + if (node.lastSent) { + timeSinceLast = process.hrtime(node.lastSent); + } + if (!node.lastSent) { // ensuring that we always send the first message + node.lastSent = process.hrtime(); + send(msg); + } + else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) { + node.lastSent = process.hrtime(); + send(msg); + } + else if (node.outputs === 2) { + send([null,msg]) } done(); } From 00a30109338ed57356dcf095b88d30ce719842b6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 10 Dec 2024 15:25:23 +0000 Subject: [PATCH 43/56] Update dependencies --- package.json | 2 +- packages/node_modules/@node-red/editor-api/package.json | 2 +- packages/node_modules/@node-red/runtime/package.json | 2 +- packages/node_modules/node-red/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c7fe644f7..e0904530d 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "cors": "2.8.5", "cronosjs": "1.7.1", "denque": "2.1.0", - "express": "4.21.1", + "express": "4.21.2", "express-session": "1.18.1", "form-data": "4.0.0", "fs-extra": "11.2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index e58f2e9fb..7f71a5fb7 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -23,7 +23,7 @@ "clone": "2.1.2", "cors": "2.8.5", "express-session": "1.18.1", - "express": "4.21.1", + "express": "4.21.2", "memorystore": "1.6.7", "mime": "3.0.0", "multer": "1.4.5-lts.1", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index a160c402c..73e13f13f 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -20,7 +20,7 @@ "@node-red/util": "4.0.5", "async-mutex": "0.5.0", "clone": "2.1.2", - "express": "4.21.1", + "express": "4.21.2", "fs-extra": "11.2.0", "json-stringify-safe": "5.0.1", "rfdc": "^1.3.1" diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 0969024ff..56711d6ff 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -38,7 +38,7 @@ "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "cors": "2.8.5", - "express": "4.21.1", + "express": "4.21.2", "fs-extra": "11.2.0", "node-red-admin": "^4.0.1", "nopt": "5.0.0", From 7d284ce1575190f937b50b6cb4da12cf0a5749a5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 10 Dec 2024 15:42:42 +0000 Subject: [PATCH 44/56] Update packages/node_modules/@node-red/editor-client/src/js/ui/editor.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../@node-red/editor-client/src/js/ui/editor.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index fbb822418..37dabe663 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -808,6 +808,20 @@ RED.editor = (function() { } } + const oldCreds = {}; + if (editing_node._def.credentials) { + for (const prop in editing_node._def.credentials) { + if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { + if (editing_node._def.credentials[prop].type === 'password') { + oldCreds['has_' + prop] = editing_node.credentials['has_' + prop]; + } + if (prop in editing_node.credentials) { + oldCreds[prop] = editing_node.credentials[prop]; + } + } + } + } + try { const rc = editing_node._def.oneditsave.call(editing_node); if (rc === true) { From 11c4277466238e5e053cb9087fc975bdd69284dd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 10 Dec 2024 15:42:59 +0000 Subject: [PATCH 45/56] Update packages/node_modules/@node-red/editor-client/src/js/ui/editor.js Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com> --- .../editor-client/src/js/ui/editor.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 37dabe663..ca74b3eff 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -853,6 +853,25 @@ RED.editor = (function() { } } } + + if (editing_node._def.credentials) { + for (const prop in editing_node._def.credentials) { + if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) { + if (oldCreds[prop] !== editing_node.credentials[prop]) { + if (editing_node.credentials[prop] === '__PWRD__') { + // The password may not exist in oldCreds + // The value '__PWRD__' means the password exists, + // so ignore this change + continue; + } + editState.changes.credentials = editState.changes.credentials || {}; + editState.changes.credentials['has_' + prop] = oldCreds['has_' + prop]; + editState.changes.credentials[prop] = oldCreds[prop]; + editState.changed = true; + } + } + } + } } } From 2c3fbb14679534cc0a399316619cd32a30a1b976 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 12 Dec 2024 16:40:43 +0000 Subject: [PATCH 46/56] revert changes to legacy mode --- .../node_modules/@node-red/nodes/core/parsers/70-CSV.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 92d12d0df..38cda9a6f 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -171,7 +171,7 @@ module.exports = function(RED) { } // join lines, don't forget to add the last new line msg.payload = ou.join(node.ret) + node.ret; - msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep); + msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(','); if (msg.payload !== '') { send(msg); } @@ -289,14 +289,14 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store; - msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); + msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); delete msg.parts; send(msg); node.store = []; } } else { - msg.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); + msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); send(msg); // finally send the array } } @@ -304,7 +304,7 @@ module.exports = function(RED) { var len = a.length; for (var i = 0; i < len; i++) { var newMessage = RED.util.cloneMessage(msg); - newMessage.columns = template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).filter(v => v).join(node.sep); + newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); newMessage.payload = a[i]; if (!has_parts) { newMessage.parts = { From 6af3c8c2a9422e22129b62ee5f211b9ca5964a83 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 12 Dec 2024 16:41:26 +0000 Subject: [PATCH 47/56] revert changes to csv parser --- .../@node-red/nodes/core/parsers/lib/csv/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js b/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js index dd2fb2db9..73cf4b292 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/lib/csv/index.js @@ -200,9 +200,9 @@ function parse(csvIn, parseOptions) { if (!headers[i]) { continue } - quotedHeaders.push(quoteCell(headers[i], { quote, separator })) + quotedHeaders.push(quoteCell(headers[i], { quote, separator: ',' })) } - finalResult.header = quotedHeaders.join(separator) // always quote headers and join with comma + finalResult.header = quotedHeaders.join(',') // always quote headers and join with comma // output is an array of arrays [[],[],[]] if (ouputArrays || headersOnly) { From b139eb4a18fbfc36793f8bd0b4f088f6642fe7fa Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 12 Dec 2024 16:42:11 +0000 Subject: [PATCH 48/56] update CSV to adhere to strict rfc compliance on msg.columns --- .../@node-red/nodes/core/parsers/70-CSV.js | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 38cda9a6f..26fdb636f 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -367,20 +367,21 @@ module.exports = function(RED) { const sendHeadersAlways = node.hdrout === "all" const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways) const quoteables = [node.sep, node.quo, "\n", "\r"] - const templateQuoteables = [node.sep, '"', "\n", "\r"] + const templateQuoteables = [node.sep, node.quo, "\n", "\r"] + const templateQuoteablesStrict = [',', '"', "\n", "\r"] let badTemplateWarnOnce = true const columnStringToTemplateArray = function (col, sep) { // NOTE: enforce strict column template parsing in RFC4180 mode const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true }) - if (parsed.headers.length > 0) { node.goodtmpl = true } else { node.goodtmpl = false } - return parsed.headers.length ? parsed.headers : null + if (parsed.data?.length === 1) { node.goodtmpl = true } else { node.goodtmpl = false } + return node.goodtmpl ? parsed.data[0] : null } - const templateArrayToColumnString = function (template, keepEmptyColumns) { - // NOTE: enforce strict column template parsing in RFC4180 mode - const parsed = csv.parse('', {headers: template, headersOnly:true, separator: node.sep, quote: node.quo, outputStyle: 'array', strict: true }) + const templateArrayToColumnString = function (template, keepEmptyColumns, separator = ',', quotables = templateQuoteablesStrict) { + // NOTE: defaults to strict column template parsing (commas and double quotes) + const parsed = csv.parse('', {headers: template, headersOnly:true, separator, quote: node.quo, outputStyle: 'array', strict: true }) return keepEmptyColumns - ? parsed.headers.map(e => addQuotes(e || '', { separator: node.sep, quoteables: templateQuoteables})).join(node.sep) + ? parsed.headers.map(e => addQuotes(e || '', { separator, quoteables: quotables })).join(separator) : parsed.header // exclues empty columns // TODO: resolve inconsistency between CSV->JSON and JSON->CSV // CSV->JSON: empty columns are excluded @@ -441,13 +442,13 @@ module.exports = function(RED) { if (sendHeaders && node.hdrSent === false) { if (hasTemplate(template) === false) { if (msg.hasOwnProperty("columns")) { - template = columnStringToTemplateArray(msg.columns || "", node.sep) || [''] + template = columnStringToTemplateArray(msg.columns || "", ",") || [''] } else { template = Object.keys(inputData[0]) || [''] } } - stringBuilder.push(templateArrayToColumnString(template, true)) + stringBuilder.push(templateArrayToColumnString(template, true, node.sep, templateQuoteables)) // use user set separator for output data. if (sendHeadersOnce) { node.hdrSent = true } } @@ -475,7 +476,7 @@ module.exports = function(RED) { } else { /*** row is an object ***/ if (hasTemplate(template) === false && (msg.hasOwnProperty("columns"))) { - template = columnStringToTemplateArray(msg.columns || "", node.sep) + template = columnStringToTemplateArray(msg.columns || "", ",") } if (hasTemplate(template) === false) { /*** row is an object but we still don't have a template ***/ @@ -483,6 +484,7 @@ module.exports = function(RED) { node.warn(RED._("csv.errors.obj_csv")) badTemplateWarnOnce = false } + template = Object.keys(row) || [''] const rowData = [] for (let header in inputData[0]) { if (row.hasOwnProperty(header)) { @@ -518,7 +520,7 @@ module.exports = function(RED) { // join lines, don't forget to add the last new line msg.payload = stringBuilder.join(node.ret) + node.ret - msg.columns = templateArrayToColumnString(template) + msg.columns = templateArrayToColumnString(template) // always strict commas + double quotes for if (msg.payload !== '') { send(msg) } done() } @@ -615,16 +617,15 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store - msg.columns = csvParseResult.header - // msg._mode = 'RFC4180 mode' + // msg.columns = csvParseResult.header + msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns delete msg.parts send(msg) node.store = [] } } else { - msg.columns = csvParseResult.header - // msg._mode = 'RFC4180 mode' + msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns msg.payload = data send(msg); // finally send the array } @@ -633,7 +634,8 @@ module.exports = function(RED) { const len = data.length for (let row = 0; row < len; row++) { const newMessage = RED.util.cloneMessage(msg) - newMessage.columns = csvParseResult.header + // newMessage.columns = csvParseResult.header + newMessage.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns newMessage.payload = data[row] if (!has_parts) { newMessage.parts = { From 82c756b091cd2683e9f4b95f76292051937439e4 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 12 Dec 2024 16:43:38 +0000 Subject: [PATCH 49/56] add test for correct CSV output when separator in non-comma --- test/nodes/core/parsers/70-CSV_spec.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index f362ecdbf..b7ba60f58 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -2077,6 +2077,26 @@ describe('CSV node (RFC Mode)', function () { }); }); + it('should convert a simple object back to a tsv with headers using a tab as a separator', function (done) { + const flow = [{ id: "n1", type: "csv", spec: "rfc", temp: "", sep: "\t", ret: '\n', hdrout: "all", wires: [["n2"]] }, // RFC-vs-Legacy difference - use line separator \n to satisfy original test + { id: "n2", type: "helper" }]; + helper.load(csvNode, flow, function () { + const n1 = helper.getNode("n1"); + const n2 = helper.getNode("n2"); + n2.on("input", function (msg) { + try { + msg.should.have.property('payload', 'd\tb\tc\ta\n1\tfoo\t"ba""r"\tdi,ng\n'); + msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns + done(); + } catch (e) { + done(e); + } + }); + const testJson = { d: 1, b: "foo", c: "ba\"r", a: "di,ng" }; + n1.emit("input", { payload: testJson }); + }); + }); + it('should handle a template with spaces in the property names', function (done) { const flow = [{ id: "n1", type: "csv", spec: "rfc", temp: "a,b o,c p,,e", ret: '\n', wires: [["n2"]] }, // RFC-vs-Legacy difference - use line separator \n to satisfy original test { id: "n2", type: "helper" }]; From 16005a462d4bd32aeba770d663f7be5ecf0bba2d Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 12 Dec 2024 16:44:16 +0000 Subject: [PATCH 50/56] update tests to check msg.columns is strictly RFC compliant --- test/nodes/core/parsers/70-CSV_spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index b7ba60f58..9f6749b9f 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -2067,6 +2067,7 @@ describe('CSV node (RFC Mode)', function () { n2.on("input", function (msg) { try { msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n'); + msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns done(); } catch (e) { done(e); @@ -2106,6 +2107,7 @@ describe('CSV node (RFC Mode)', function () { n2.on("input", function (msg) { try { msg.should.have.property('payload', '4,foo,true,,0\n'); + msg.should.have.property('columns', 'a,b o,c p,e'); // Strict RFC columns done(); } catch (e) { done(e); @@ -2126,6 +2128,7 @@ describe('CSV node (RFC Mode)', function () { try { // 'payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); // Legacy msg.should.have.property('payload', '"a""a",b\'b\nA1,B1\nA2,B2\n'); // RFC-vs-Legacy difference - RFC4180 Section 2.6, 2.7 quote handling + msg.should.have.property('columns', '"a""a",b\'b'); // RCF compliant column names done(); } catch (e) { done(e); @@ -2191,6 +2194,7 @@ describe('CSV node (RFC Mode)', function () { n2.on("input", function (msg) { try { msg.should.have.property('payload', '1,3,2,4\n4,2,3,1\n'); + msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns done(); } catch (e) { done(e); } @@ -2209,6 +2213,7 @@ describe('CSV node (RFC Mode)', function () { n2.on("input", function (msg) { try { msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n'); + msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns done(); } catch (e) { done(e); } @@ -2228,6 +2233,7 @@ describe('CSV node (RFC Mode)', function () { try { // 'payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n'); msg.should.have.property('payload', ',0,1,foo\n'); // RFC-vs-Legacy difference - respect that user has specified a template with 4 columns + msg.should.have.property('columns', 'a,b,c,d'); done(); } catch (e) { done(e); } @@ -2347,6 +2353,7 @@ describe('CSV node (RFC Mode)', function () { n2.on("input", function (msg) { try { msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n'); + msg.should.have.property('columns', 'a,b,c,d'); done(); } catch (e) { done(e); } From e9d5d20e2da0f475f2e8da00d5b0e6fb19df8610 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Dec 2024 11:35:14 +0000 Subject: [PATCH 51/56] Update for 4.0.6 --- CHANGELOG.md | 33 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 ++-- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 2 +- .../@node-red/registry/package.json | 4 +-- .../@node-red/runtime/package.json | 6 ++-- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++--- 9 files changed, 50 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b55ac4c6..db473c61b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +#### 4.0.6: Maintenance Release + +Editor + + - Roll up various fixes on config node change history (#4975) @knolleary + - Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland + - Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary + - Fix junction insert position via context menu (#4974) @knolleary + - Apply zoom scale when calculating annotation positions (#4981) @knolleary + - Handle the import of an incomplete Subflow (#4811) @GogoVega + - Fix updating the Subflow name during a copy (#4809) @GogoVega + - Rename variable to avoid confusion in view.js (#4963) @knolleary + - Change groups.length to groups.size (#4959) @hungtcs + - Remove disabled node types from QuickAddDialog list (#4946) @GogoVega + - Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega + - Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw + - Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega + - Fix `envVar` editable list should be sortable (#4932) @GogoVega + - Improve the node name auto-generated with the first available number (#4912) @GogoVega + +Runtime + + - Get the env config node from the parent subflow (#4960) @GogoVega + - Update dependencies (#4987) @knolleary + +Nodes + + - Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli + - Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay + - Fix trigger node date handling for latest time type input (#4915) @dceejay + - Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973) + - Ensure node.sep is honoured when generating CSV (#4982) @knolleary + #### 4.0.5: Maintenance Release Editor diff --git a/package.json b/package.json index e0904530d..679474fc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.0.5", + "version": "4.0.6", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 7f71a5fb7..14fdfb77a 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "4.0.5", - "@node-red/editor-client": "4.0.5", + "@node-red/util": "4.0.6", + "@node-red/editor-client": "4.0.6", "bcryptjs": "2.4.3", "body-parser": "1.20.3", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index b6b7a2c73..3d13bdc85 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 84db88466..9659717fb 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 9437a5beb..85058d0a7 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "4.0.5", + "@node-red/util": "4.0.6", "clone": "2.1.2", "fs-extra": "11.2.0", "semver": "7.6.3", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 73e13f13f..65ffc47a6 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "4.0.5", - "@node-red/util": "4.0.5", + "@node-red/registry": "4.0.6", + "@node-red/util": "4.0.6", "async-mutex": "0.5.0", "clone": "2.1.2", "express": "4.21.2", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index c02ab3805..3338acd5f 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "4.0.5", + "version": "4.0.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 56711d6ff..35b93bc93 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.0.5", + "version": "4.0.6", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "4.0.5", - "@node-red/runtime": "4.0.5", - "@node-red/util": "4.0.5", - "@node-red/nodes": "4.0.5", + "@node-red/editor-api": "4.0.6", + "@node-red/runtime": "4.0.6", + "@node-red/util": "4.0.6", + "@node-red/nodes": "4.0.6", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "cors": "2.8.5", From b7e96ce6bc1009af2b30b805167b091528be2987 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Dec 2024 16:58:13 +0000 Subject: [PATCH 52/56] Support custom login message and button --- .../@node-red/editor-api/lib/auth/index.js | 8 +++++ .../@node-red/editor-api/lib/editor/theme.js | 16 +++++++++- .../@node-red/editor-client/src/js/user.js | 31 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index 30ff06756..9581983cb 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -126,6 +126,14 @@ async function login(req,res) { if (themeContext.login && themeContext.login.image) { response.image = themeContext.login.image; } + if (themeContext.login?.message) { + response.loginMessage = themeContext.login?.message + } + if (themeContext.login?.button) { + response.prompts = [ + { type: "button", ...themeContext.login.button } + ] + } } res.json(response); } diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 2bbcbcd97..782a9d173 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -206,14 +206,28 @@ module.exports = { } if (theme.login) { + let themeContextLogin = {} + let hasLoginTheme = false if (theme.login.image) { url = serveFile(themeApp,"/login/",theme.login.image); if (url) { - themeContext.login = { + themeContextLogin.login = { image: url } + hasLoginTheme = true } } + if (theme.login.message) { + themeContextLogin.message = theme.login.message + hasLoginTheme = true + } + if (theme.login.button) { + themeContextLogin.button = theme.login.button + hasLoginTheme = true + } + if (hasLoginTheme) { + themeContext.login = themeContextLogin + } } themeApp.get("/", async function(req,res) { const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"}); diff --git a/packages/node_modules/@node-red/editor-client/src/js/user.js b/packages/node_modules/@node-red/editor-client/src/js/user.js index 2deda8970..e2c3cb577 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/user.js +++ b/packages/node_modules/@node-red/editor-client/src/js/user.js @@ -168,6 +168,37 @@ RED.user = (function() { } + } else { + if (data.prompts) { + if (data.loginMessage) { + const sessionMessages = $("
",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields"); + $('
').text(data.loginMessage).appendTo(sessionMessages); + } + + i = 0; + for (;i",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields"); + var loginButton = $('',{style: "padding: 10px"}).appendTo(row).on("click", function() { + document.location = field.url; + }); + if (field.image) { + $("",{src:field.image}).appendTo(loginButton); + } else if (field.label) { + var label = $('').text(field.label); + if (field.icon) { + $('',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton); + label.css({ + "verticalAlign":"middle", + "marginLeft":"8px" + }); + + } + label.appendTo(loginButton); + } + loginButton.button(); + } + } } if (opts.cancelable) { $("#node-dialog-login-cancel").button().on("click", function( event ) { From 1a47e2fc76890c0be3a20a766100848346b3e237 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Dec 2024 17:09:31 +0000 Subject: [PATCH 53/56] Fix login image auth setting --- .../node_modules/@node-red/editor-api/lib/editor/theme.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 782a9d173..cd1d569ac 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -211,9 +211,7 @@ module.exports = { if (theme.login.image) { url = serveFile(themeApp,"/login/",theme.login.image); if (url) { - themeContextLogin.login = { - image: url - } + themeContextLogin.image = url hasLoginTheme = true } } From 7785ce0dc0ca6b6973f7390bfcf61ac024817420 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:36:51 +0100 Subject: [PATCH 54/56] Fix the user list of nested config node --- .../@node-red/editor-client/src/js/nodes.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 366120796..4042f3e12 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -707,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] = newNode; } else { if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; } n.dirty = true; - updateConfigNodeUsers(newNode, { action: "add" }); if (n._def.category == "subflows" && typeof n.i === "undefined") { var nextId = 0; RED.nodes.eachNode(function(node) { @@ -774,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)) { @@ -786,6 +791,8 @@ RED.nodes = (function() { 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)) { @@ -2187,6 +2194,21 @@ RED.nodes = (function() { } } + // Config node can use another config node, must ensure that this other + // config node is added before to exists when updating the user list + const configNodeFilter = function (node) { + let count = 0; + if (node._def?.defaults) { + for (const def of Object.values(node._def.defaults)) { + if (def.type) { + count++; + } + } + } + return count; + }; + new_nodes.sort((a, b) => configNodeFilter(a) - configNodeFilter(b)); + // Find regular flow nodes and subflow instances for (i=0;i Date: Tue, 17 Dec 2024 17:57:01 +0100 Subject: [PATCH 55/56] Fix def can be undefined if the type is missing --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 366120796..d8c89665a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -2357,13 +2357,13 @@ RED.nodes = (function() { node.type = "unknown"; } if (node._def.category != "config") { - if (n.hasOwnProperty('inputs') && def.defaults.hasOwnProperty("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') && def.defaults.hasOwnProperty("outputs")) { + if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) { node.outputs = parseInt(n.outputs, 10); node._config.outputs = JSON.stringify(n.outputs); } else { From 100e5244c887695bdfd08f8d86cd2d2d95434ebd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 17 Dec 2024 17:08:56 +0000 Subject: [PATCH 56/56] Bump for 4.0.7 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 4 ++-- packages/node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 9 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db473c61b..a85502524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +#### 4.0.7: Maintenance Release + +Editor + + - Fix def can be undefined if the type is missing (#4997) @GogoVega + - Fix the user list of nested config node (#4995) @GogoVega + - Support custom login message and button (#4993) @knolleary + #### 4.0.6: Maintenance Release Editor diff --git a/package.json b/package.json index 679474fc8..dbddd1ea7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.0.6", + "version": "4.0.7", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 14fdfb77a..741d8f350 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "4.0.6", - "@node-red/editor-client": "4.0.6", + "@node-red/util": "4.0.7", + "@node-red/editor-client": "4.0.7", "bcryptjs": "2.4.3", "body-parser": "1.20.3", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 3d13bdc85..c51e7e759 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 9659717fb..acb34f868 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 85058d0a7..8c6cb26f9 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "4.0.6", + "@node-red/util": "4.0.7", "clone": "2.1.2", "fs-extra": "11.2.0", "semver": "7.6.3", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 65ffc47a6..de0724263 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "4.0.6", - "@node-red/util": "4.0.6", + "@node-red/registry": "4.0.7", + "@node-red/util": "4.0.7", "async-mutex": "0.5.0", "clone": "2.1.2", "express": "4.21.2", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 3338acd5f..596776a34 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "4.0.6", + "version": "4.0.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 35b93bc93..da21c410a 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.0.6", + "version": "4.0.7", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "4.0.6", - "@node-red/runtime": "4.0.6", - "@node-red/util": "4.0.6", - "@node-red/nodes": "4.0.6", + "@node-red/editor-api": "4.0.7", + "@node-red/runtime": "4.0.7", + "@node-red/util": "4.0.7", + "@node-red/nodes": "4.0.7", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "cors": "2.8.5",