mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
0c15a8e382
@ -1,4 +0,0 @@
|
|||||||
/Gruntfile.js
|
|
||||||
/.git/*
|
|
||||||
*.backup
|
|
||||||
/public/*
|
|
47
CHANGELOG.md
47
CHANGELOG.md
@ -1,3 +1,50 @@
|
|||||||
|
#### 4.0.8: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix config node sort order when importing (#5000) @knolleary
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
- 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
|
#### 4.0.5: Maintenance Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
16
nodemon.json
Normal file
16
nodemon.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"ignoreRoot": [
|
||||||
|
".git",
|
||||||
|
".nyc_output",
|
||||||
|
".sass-cache",
|
||||||
|
"bower-components",
|
||||||
|
"coverage"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"/Gruntfile.js",
|
||||||
|
"/.git/*",
|
||||||
|
"*.backup",
|
||||||
|
"/public/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"description": "Low-code programming for event-driven applications",
|
"description": "Low-code programming for event-driven applications",
|
||||||
"homepage": "https://nodered.org",
|
"homepage": "https://nodered.org",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
@ -126,6 +126,14 @@ async function login(req,res) {
|
|||||||
if (themeContext.login && themeContext.login.image) {
|
if (themeContext.login && themeContext.login.image) {
|
||||||
response.image = 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);
|
res.json(response);
|
||||||
}
|
}
|
||||||
|
@ -206,14 +206,26 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.login) {
|
if (theme.login) {
|
||||||
|
let themeContextLogin = {}
|
||||||
|
let hasLoginTheme = false
|
||||||
if (theme.login.image) {
|
if (theme.login.image) {
|
||||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||||
if (url) {
|
if (url) {
|
||||||
themeContext.login = {
|
themeContextLogin.image = url
|
||||||
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) {
|
themeApp.get("/", async function(req,res) {
|
||||||
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-api",
|
"name": "@node-red/editor-api",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -16,8 +16,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/util": "4.0.5",
|
"@node-red/util": "4.0.8",
|
||||||
"@node-red/editor-client": "4.0.5",
|
"@node-red/editor-client": "4.0.8",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-client",
|
"name": "@node-red/editor-client",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -453,10 +453,61 @@ RED.history = (function() {
|
|||||||
RED.events.emit("nodes:change",newConfigNode);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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])) {
|
||||||
|
// 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];
|
||||||
}
|
}
|
||||||
ev.node[i] = ev.changes[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.node.dirty = true;
|
ev.node.dirty = true;
|
||||||
ev.node.changed = ev.changed;
|
ev.node.changed = ev.changed;
|
||||||
|
|
||||||
@ -536,6 +587,24 @@ RED.history = (function() {
|
|||||||
RED.editor.updateNodeProperties(ev.node,outputMap);
|
RED.editor.updateNodeProperties(ev.node,outputMap);
|
||||||
RED.editor.validateNode(ev.node);
|
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) {
|
if (ev.links) {
|
||||||
inverseEv.createdLinks = [];
|
inverseEv.createdLinks = [];
|
||||||
for (i=0;i<ev.links.length;i++) {
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
@ -707,12 +707,15 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
n["_"] = RED._;
|
n["_"] = RED._;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both node and config node can use a config node
|
||||||
|
updateConfigNodeUsers(newNode, { action: "add" });
|
||||||
|
|
||||||
if (n._def.category == "config") {
|
if (n._def.category == "config") {
|
||||||
configNodes[n.id] = n;
|
configNodes[n.id] = newNode;
|
||||||
} else {
|
} else {
|
||||||
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
updateConfigNodeUsers(n);
|
|
||||||
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
||||||
var nextId = 0;
|
var nextId = 0;
|
||||||
RED.nodes.eachNode(function(node) {
|
RED.nodes.eachNode(function(node) {
|
||||||
@ -774,9 +777,11 @@ RED.nodes = (function() {
|
|||||||
var removedLinks = [];
|
var removedLinks = [];
|
||||||
var removedNodes = [];
|
var removedNodes = [];
|
||||||
var node;
|
var node;
|
||||||
|
|
||||||
if (id in configNodes) {
|
if (id in configNodes) {
|
||||||
node = configNodes[id];
|
node = configNodes[id];
|
||||||
delete configNodes[id];
|
delete configNodes[id];
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
RED.events.emit('nodes:remove',node);
|
RED.events.emit('nodes:remove',node);
|
||||||
RED.workspaces.refresh();
|
RED.workspaces.refresh();
|
||||||
} else if (allNodes.hasNode(id)) {
|
} else if (allNodes.hasNode(id)) {
|
||||||
@ -785,6 +790,9 @@ RED.nodes = (function() {
|
|||||||
delete nodeLinks[id];
|
delete nodeLinks[id];
|
||||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||||
removedLinks.forEach(removeLink);
|
removedLinks.forEach(removeLink);
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
|
|
||||||
|
// TODO: Legacy code for exclusive config node
|
||||||
var updatedConfigNode = false;
|
var updatedConfigNode = false;
|
||||||
for (var d in node._def.defaults) {
|
for (var d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -798,10 +806,6 @@ RED.nodes = (function() {
|
|||||||
if (configNode._def.exclusive) {
|
if (configNode._def.exclusive) {
|
||||||
removeNode(node[d]);
|
removeNode(node[d]);
|
||||||
removedNodes.push(configNode);
|
removedNodes.push(configNode);
|
||||||
} else {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(node),1);
|
|
||||||
RED.events.emit('nodes:change',configNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1798,9 +1802,20 @@ RED.nodes = (function() {
|
|||||||
// Replace config nodes
|
// Replace config nodes
|
||||||
//
|
//
|
||||||
configNodeIds.forEach(function(id) {
|
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);
|
removeNode(id);
|
||||||
importNodes([newConfigNodes[id]])
|
|
||||||
|
// Import the new one
|
||||||
|
importNodes([newConfigNodes[id]]);
|
||||||
|
|
||||||
|
// Re-attributes the user count
|
||||||
|
getNode(id).users = currentUserCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -2084,6 +2099,8 @@ RED.nodes = (function() {
|
|||||||
activeWorkspace = RED.workspaces.active();
|
activeWorkspace = RED.workspaces.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingConfigNodes = []
|
||||||
|
const pendingConfigNodeIds = new Set()
|
||||||
// Find all config nodes and add them
|
// Find all config nodes and add them
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2143,7 +2160,8 @@ RED.nodes = (function() {
|
|||||||
type:n.type,
|
type:n.type,
|
||||||
info: n.info,
|
info: n.info,
|
||||||
users:[],
|
users:[],
|
||||||
_config:{}
|
_config:{},
|
||||||
|
_configNodeReferences: new Set()
|
||||||
};
|
};
|
||||||
if (!n.z) {
|
if (!n.z) {
|
||||||
delete configNode.z;
|
delete configNode.z;
|
||||||
@ -2158,6 +2176,9 @@ RED.nodes = (function() {
|
|||||||
if (def.defaults.hasOwnProperty(d)) {
|
if (def.defaults.hasOwnProperty(d)) {
|
||||||
configNode[d] = n[d];
|
configNode[d] = n[d];
|
||||||
configNode._config[d] = JSON.stringify(n[d]);
|
configNode._config[d] = JSON.stringify(n[d]);
|
||||||
|
if (def.defaults[d].type) {
|
||||||
|
configNode._configNodeReferences.add(n[d])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||||
@ -2174,11 +2195,55 @@ RED.nodes = (function() {
|
|||||||
configNode.id = getID();
|
configNode.id = getID();
|
||||||
}
|
}
|
||||||
node_map[n.id] = configNode;
|
node_map[n.id] = configNode;
|
||||||
new_nodes.push(configNode);
|
pendingConfigNodes.push(configNode);
|
||||||
|
pendingConfigNodeIds.add(configNode.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to sort new_nodes (which only contains config nodes at this point)
|
||||||
|
// to ensure they get added in the right order. If NodeA depends on NodeB, then
|
||||||
|
// NodeB must be added first.
|
||||||
|
|
||||||
|
// Limit us to 5 full iterations of the list - this should be more than
|
||||||
|
// enough to process the list as config->config node relationships are
|
||||||
|
// not very common
|
||||||
|
let iterationLimit = pendingConfigNodes.length * 5
|
||||||
|
const handledConfigNodes = new Set()
|
||||||
|
while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
|
||||||
|
const node = pendingConfigNodes.shift()
|
||||||
|
let hasPending = false
|
||||||
|
// Loop through the nodes referenced by this node to see if anything
|
||||||
|
// is pending
|
||||||
|
node._configNodeReferences.forEach(id => {
|
||||||
|
if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
|
||||||
|
// This reference is for a node we know is in this import, but
|
||||||
|
// it isn't added yet - flag as pending
|
||||||
|
hasPending = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!hasPending) {
|
||||||
|
// This node has no pending config node references - safe to add
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
handledConfigNodes.add(node.id)
|
||||||
|
} else {
|
||||||
|
// This node has pending config node references
|
||||||
|
// Put to the back of the queue
|
||||||
|
pendingConfigNodes.push(node)
|
||||||
|
}
|
||||||
|
iterationLimit--
|
||||||
|
}
|
||||||
|
if (pendingConfigNodes.length > 0) {
|
||||||
|
// We exceeded the iteration count. Could be due to reference loops
|
||||||
|
// between the config nodes. At this point, just add the remaining
|
||||||
|
// nodes as-is
|
||||||
|
pendingConfigNodes.forEach(node => {
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Find regular flow nodes and subflow instances
|
// Find regular flow nodes and subflow instances
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2349,29 +2414,31 @@ RED.nodes = (function() {
|
|||||||
node.type = "unknown";
|
node.type = "unknown";
|
||||||
}
|
}
|
||||||
if (node._def.category != "config") {
|
if (node._def.category != "config") {
|
||||||
if (n.hasOwnProperty('inputs')) {
|
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
|
||||||
node.inputs = n.inputs;
|
node.inputs = parseInt(n.inputs, 10);
|
||||||
node._config.inputs = JSON.stringify(n.inputs);
|
node._config.inputs = JSON.stringify(n.inputs);
|
||||||
} else {
|
} else {
|
||||||
node.inputs = node._def.inputs;
|
node.inputs = node._def.inputs;
|
||||||
}
|
}
|
||||||
if (n.hasOwnProperty('outputs')) {
|
if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
|
||||||
node.outputs = n.outputs;
|
node.outputs = parseInt(n.outputs, 10);
|
||||||
node._config.outputs = JSON.stringify(n.outputs);
|
node._config.outputs = JSON.stringify(n.outputs);
|
||||||
} else {
|
} else {
|
||||||
node.outputs = node._def.outputs;
|
node.outputs = node._def.outputs;
|
||||||
}
|
}
|
||||||
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
|
|
||||||
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
|
// The node declares outputs in its defaults, but has not got a valid value
|
||||||
// If 'wires' is longer than outputs, clip wires
|
// Defer to the length of the wires array
|
||||||
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
|
if (node.hasOwnProperty('wires')) {
|
||||||
node.wires = node.wires.slice(0,node.outputs);
|
if (isNaN(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;
|
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) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
||||||
node[d] = n[d];
|
node[d] = n[d];
|
||||||
@ -2468,11 +2535,6 @@ RED.nodes = (function() {
|
|||||||
nodeList = nodeList.map(function(id) {
|
nodeList = nodeList.map(function(id) {
|
||||||
var node = node_map[id];
|
var node = node_map[id];
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node._def.category === 'config') {
|
|
||||||
if (node.users.indexOf(n) === -1) {
|
|
||||||
node.users.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node.id;
|
return node.id;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
@ -2699,25 +2761,79 @@ RED.nodes = (function() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
|
/**
|
||||||
function updateConfigNodeUsers(n) {
|
* Update any config nodes referenced by the provided node to ensure
|
||||||
for (var d in n._def.defaults) {
|
* their 'users' list is correct.
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
*
|
||||||
var property = n._def.defaults[d];
|
* @param {object} node The node in which to check if it contains references
|
||||||
|
* @param {object} options Options to apply.
|
||||||
|
* @param {"add" | "remove"} [options.action] Add or remove the node from
|
||||||
|
* the Config Node users list. Default `add`.
|
||||||
|
* @param {boolean} [options.emitEvent] Emit the `nodes:changes` event.
|
||||||
|
* Default true.
|
||||||
|
*/
|
||||||
|
function updateConfigNodeUsers(node, options) {
|
||||||
|
const defaultOptions = { action: "add", emitEvent: true };
|
||||||
|
options = Object.assign({}, defaultOptions, options);
|
||||||
|
|
||||||
|
for (var d in node._def.defaults) {
|
||||||
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
|
var property = node._def.defaults[d];
|
||||||
if (property.type) {
|
if (property.type) {
|
||||||
var type = registry.getNodeType(property.type);
|
var type = registry.getNodeType(property.type);
|
||||||
|
// Need to ensure the type is a config node to not treat links nodes
|
||||||
if (type && type.category == "config") {
|
if (type && type.category == "config") {
|
||||||
var configNode = configNodes[n[d]];
|
var configNode = configNodes[node[d]];
|
||||||
if (configNode) {
|
if (configNode) {
|
||||||
if (configNode.users.indexOf(n) === -1) {
|
if (options.action === "add") {
|
||||||
configNode.users.push(n);
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
RED.events.emit('nodes:change',configNode)
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subflows can have config node env
|
||||||
|
if (node.type.indexOf("subflow:") === 0) {
|
||||||
|
node.env?.forEach((prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
const configNode = getNode(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (options.action === "add") {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function flowVersion(version) {
|
function flowVersion(version) {
|
||||||
|
@ -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 {
|
try {
|
||||||
const rc = editing_node._def.oneditsave.call(editing_node);
|
const rc = editing_node._def.oneditsave.call(editing_node);
|
||||||
if (rc === true) {
|
if (rc === true) {
|
||||||
@ -839,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1481,134 +1514,181 @@ RED.editor = (function() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "node-config-dialog-ok",
|
id: "node-config-dialog-ok",
|
||||||
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
|
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
|
||||||
class: "primary",
|
class: "primary",
|
||||||
click: function() {
|
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: {},
|
changes: {},
|
||||||
changed: false,
|
changed: false,
|
||||||
outputMap: null
|
outputMap: null
|
||||||
};
|
};
|
||||||
var configProperty = name;
|
|
||||||
var configId = editing_config_node.id;
|
// Call `oneditsave` and search for changes
|
||||||
var configType = type;
|
handleEditSave(editing_config_node, editState);
|
||||||
var configAdding = adding;
|
|
||||||
var configTypeDef = RED.nodes.getType(configType);
|
|
||||||
var d;
|
|
||||||
var input;
|
|
||||||
|
|
||||||
if (configTypeDef.oneditsave) {
|
// Search for changes in the edit box (panes)
|
||||||
try {
|
activeEditPanes.forEach(function (pane) {
|
||||||
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) {
|
|
||||||
if (pane.apply) {
|
if (pane.apply) {
|
||||||
pane.apply.call(pane, editState);
|
pane.apply.call(pane, editState);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
editing_config_node.label = configTypeDef.label;
|
// TODO: Why?
|
||||||
|
editing_config_node.label = configTypeDef.label
|
||||||
var scope = $("#red-ui-editor-config-scope").val();
|
|
||||||
editing_config_node.z = scope;
|
|
||||||
|
|
||||||
|
// Check if disabled has changed
|
||||||
if ($("#node-config-input-node-disabled").prop('checked')) {
|
if ($("#node-config-input-node-disabled").prop('checked')) {
|
||||||
if (editing_config_node.d !== true) {
|
if (editing_config_node.d !== true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
editing_config_node.d = true;
|
editing_config_node.d = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editing_config_node.d === true) {
|
if (editing_config_node.d === true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
delete editing_config_node.d;
|
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) {
|
if (scope) {
|
||||||
// Search for nodes that use this one that are no longer
|
const newUsers = editing_config_node.users.filter(function (node) {
|
||||||
// in scope, so must be removed
|
let keepNode = false;
|
||||||
editing_config_node.users = editing_config_node.users.filter(function(n) {
|
let nodeModified = null;
|
||||||
var keep = true;
|
|
||||||
for (var d in n._def.defaults) {
|
for (const d in node._def.defaults) {
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
if (n._def.defaults[d].type === editing_config_node.type &&
|
if (node._def.defaults[d].type === editing_config_node.type) {
|
||||||
n[d] === editing_config_node.id &&
|
if (node[d] === editing_config_node.id) {
|
||||||
n.z !== scope) {
|
if (node.z === editing_config_node.z) {
|
||||||
keep = false;
|
// The node is kept only if at least one property uses
|
||||||
// Remove the reference to this node
|
// this config node in the correct scope.
|
||||||
// and revalidate
|
keepNode = true;
|
||||||
n[d] = null;
|
} else {
|
||||||
n.dirty = true;
|
if (!nodeModified) {
|
||||||
n.changed = true;
|
nodeModified = {
|
||||||
validateNode(n);
|
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 node
|
||||||
|
node[d] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keep;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configAdding) {
|
// Add the node modified to the history
|
||||||
RED.nodes.add(editing_config_node);
|
if (nodeModified) {
|
||||||
}
|
historyEvents.push(nodeModified);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
validateNode(user);
|
|
||||||
|
// Mark as changed and revalidate this node
|
||||||
|
if (!keepNode) {
|
||||||
|
node.changed = true;
|
||||||
|
node.dirty = true;
|
||||||
|
validateNode(node);
|
||||||
|
RED.events.emit("nodes:change", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (editState.changed) {
|
||||||
if (!configAdding) {
|
// Set the congig node as changed
|
||||||
RED.events.emit("editor:save",editing_config_node);
|
editing_config_node.changed = true;
|
||||||
RED.events.emit("nodes:change",editing_config_node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
// when editing a config via subflow edit panel, the `configProperty` will not
|
// when editing a config via subflow edit panel, the `configProperty` will not
|
||||||
@ -1756,8 +1836,18 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let envToRemove = new Set()
|
||||||
if (!isSameObj(old_env, new_env)) {
|
if (!isSameObj(old_env, new_env)) {
|
||||||
|
// Get a list of env properties that have been removed
|
||||||
|
// by comparing old_env and new_env
|
||||||
|
if (old_env) {
|
||||||
|
old_env.forEach(env => { envToRemove.add(env.name) })
|
||||||
|
}
|
||||||
|
if (new_env) {
|
||||||
|
new_env.forEach(env => {
|
||||||
|
envToRemove.delete(env.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
editState.changes.env = editing_node.env;
|
editState.changes.env = editing_node.env;
|
||||||
editing_node.env = new_env;
|
editing_node.env = new_env;
|
||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
@ -1766,10 +1856,11 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
|
|
||||||
if (editState.changed) {
|
if (editState.changed) {
|
||||||
var wasChanged = editing_node.changed;
|
let wasChanged = editing_node.changed;
|
||||||
editing_node.changed = true;
|
editing_node.changed = true;
|
||||||
validateNode(editing_node);
|
validateNode(editing_node);
|
||||||
var subflowInstances = [];
|
let subflowInstances = [];
|
||||||
|
let instanceHistoryEvents = []
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type == "subflow:"+editing_node.id) {
|
if (n.type == "subflow:"+editing_node.id) {
|
||||||
subflowInstances.push({
|
subflowInstances.push({
|
||||||
@ -1779,13 +1870,35 @@ RED.editor = (function() {
|
|||||||
n._def.color = editing_node.color;
|
n._def.color = editing_node.color;
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
|
if (n.env) {
|
||||||
|
const oldEnv = n.env
|
||||||
|
const newEnv = []
|
||||||
|
let envChanged = false
|
||||||
|
n.env.forEach((env, index) => {
|
||||||
|
if (envToRemove.has(env.name)) {
|
||||||
|
envChanged = true
|
||||||
|
} else {
|
||||||
|
newEnv.push(env)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (envChanged) {
|
||||||
|
instanceHistoryEvents.push({
|
||||||
|
t: 'edit',
|
||||||
|
node: n,
|
||||||
|
changes: { env: oldEnv },
|
||||||
|
dirty: n.dirty,
|
||||||
|
changed: n.changed
|
||||||
|
})
|
||||||
|
n.env = newEnv
|
||||||
|
}
|
||||||
|
}
|
||||||
updateNodeProperties(n);
|
updateNodeProperties(n);
|
||||||
validateNode(n);
|
validateNode(n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.events.emit("subflows:change",editing_node);
|
RED.events.emit("subflows:change",editing_node);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
var historyEvent = {
|
let historyEvent = {
|
||||||
t:'edit',
|
t:'edit',
|
||||||
node:editing_node,
|
node:editing_node,
|
||||||
changes:editState.changes,
|
changes:editState.changes,
|
||||||
@ -1795,7 +1908,13 @@ RED.editor = (function() {
|
|||||||
instances:subflowInstances
|
instances:subflowInstances
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (instanceHistoryEvents.length > 0) {
|
||||||
|
historyEvent = {
|
||||||
|
t: 'multi',
|
||||||
|
events: [ historyEvent, ...instanceHistoryEvents ],
|
||||||
|
dirty: wasDirty
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
}
|
}
|
||||||
editing_node.dirty = true;
|
editing_node.dirty = true;
|
||||||
|
@ -20,10 +20,31 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var old_env = node.env;
|
var old_env = node.env;
|
||||||
var new_env = [];
|
var new_env = [];
|
||||||
|
|
||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
|
// Get the list of environment variables from the node properties
|
||||||
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
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
|
// Get the values from the Properties table tab
|
||||||
var items = this.list.editableList('items');
|
var items = this.list.editableList('items');
|
||||||
items.each(function (i,el) {
|
items.each(function (i,el) {
|
||||||
@ -41,7 +62,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (new_env && new_env.length > 0) {
|
if (new_env && new_env.length > 0) {
|
||||||
new_env.forEach(function(prop) {
|
new_env.forEach(function(prop) {
|
||||||
if (prop.type === "cred") {
|
if (prop.type === "cred") {
|
||||||
@ -52,6 +72,15 @@
|
|||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
}
|
}
|
||||||
delete prop.value;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var newValue;
|
var newValue;
|
||||||
var d;
|
var d;
|
||||||
|
// If the node is a subflow, the node's properties (exepts name) are saved by `envProperties`
|
||||||
if (node._def.defaults) {
|
if (node._def.defaults) {
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -131,9 +132,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node._def.credentials) {
|
if (node._def.credentials) {
|
||||||
var credDefinition = node._def.credentials;
|
const credDefinition = node._def.credentials;
|
||||||
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
|
const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass);
|
||||||
editState.changed = editState.changed || credsChanged;
|
|
||||||
|
if (Object.keys(credChanges).length) {
|
||||||
|
editState.changed = true;
|
||||||
|
editState.changes.credentials = {
|
||||||
|
...(editState.changes.credentials || {}),
|
||||||
|
...credChanges
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,10 +169,11 @@
|
|||||||
* @param node - the node containing the credentials
|
* @param node - the node containing the credentials
|
||||||
* @param credDefinition - definition of the credentials
|
* @param credDefinition - definition of the credentials
|
||||||
* @param prefix - prefix of the input fields
|
* @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) {
|
function updateNodeCredentials(node, credDefinition, prefix) {
|
||||||
var changed = false;
|
const changes = {};
|
||||||
|
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
node.credentials = {_:{}};
|
node.credentials = {_:{}};
|
||||||
} else if (!node.credentials._) {
|
} else if (!node.credentials._) {
|
||||||
@ -177,22 +186,33 @@
|
|||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
var value = input.val();
|
var value = input.val();
|
||||||
if (credDefinition[cred].type == 'password') {
|
if (credDefinition[cred].type == 'password') {
|
||||||
node.credentials['has_' + cred] = (value !== "");
|
if (value === '__PWRD__') {
|
||||||
if (value == '__PWRD__') {
|
// A cred value exists - no changes
|
||||||
continue;
|
} 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['has_' + cred] = node.credentials['has_' + cred];
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
|
|
||||||
}
|
node.credentials['has_' + cred] = (value !== '');
|
||||||
node.credentials[cred] = value;
|
} else {
|
||||||
if (value != node.credentials._[cred]) {
|
// Since these creds are loaded by the editor,
|
||||||
changed = true;
|
// values can be directly compared
|
||||||
|
if (value !== node.credentials[cred]) {
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
|
||||||
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -245,10 +245,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -303,10 +308,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
|
@ -1362,7 +1362,7 @@ RED.subflow = (function() {
|
|||||||
item.value = ""+input.prop("checked");
|
item.value = ""+input.prop("checked");
|
||||||
break;
|
break;
|
||||||
case "conf-types":
|
case "conf-types":
|
||||||
item.value = input.val()
|
item.value = input.val() === "_ADD_" ? "" : input.val();
|
||||||
item.type = "conf-type"
|
item.type = "conf-type"
|
||||||
}
|
}
|
||||||
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
||||||
|
@ -18,8 +18,6 @@ RED.sidebar.context = (function() {
|
|||||||
var content;
|
var content;
|
||||||
var sections;
|
var sections;
|
||||||
|
|
||||||
var localCache = {};
|
|
||||||
|
|
||||||
var flowAutoRefresh;
|
var flowAutoRefresh;
|
||||||
var nodeAutoRefresh;
|
var nodeAutoRefresh;
|
||||||
var nodeSection;
|
var nodeSection;
|
||||||
@ -27,6 +25,8 @@ RED.sidebar.context = (function() {
|
|||||||
var flowSection;
|
var flowSection;
|
||||||
var globalSection;
|
var globalSection;
|
||||||
|
|
||||||
|
const expandedPaths = {}
|
||||||
|
|
||||||
var currentNode;
|
var currentNode;
|
||||||
var currentFlow;
|
var currentFlow;
|
||||||
|
|
||||||
@ -212,14 +212,41 @@ RED.sidebar.context = (function() {
|
|||||||
var l = keys.length;
|
var l = keys.length;
|
||||||
for (var i = 0; i < l; i++) {
|
for (var i = 0; i < l; i++) {
|
||||||
sortedData[keys[i]].forEach(function(v) {
|
sortedData[keys[i]].forEach(function(v) {
|
||||||
var k = keys[i];
|
const k = keys[i];
|
||||||
var l2 = sortedData[k].length;
|
let payload = v.msg;
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
let format = v.format;
|
||||||
var obj = $(propRow.children()[0]);
|
const tools = $('<span class="button-group"></span>');
|
||||||
|
expandedPaths[id + "." + k] = expandedPaths[id + "." + k] || new Set()
|
||||||
|
const objectElementOptions = {
|
||||||
|
typeHint: format,
|
||||||
|
sourceId: id + "." + k,
|
||||||
|
tools,
|
||||||
|
path: k,
|
||||||
|
rootPath: k,
|
||||||
|
exposeApi: true,
|
||||||
|
ontoggle: function(path,state) {
|
||||||
|
path = path.substring(k.length+1)
|
||||||
|
if (state) {
|
||||||
|
expandedPaths[id+"."+k].add(path)
|
||||||
|
} else {
|
||||||
|
// if 'a' has been collapsed, we want to remove 'a.b' and 'a[0]...' from the set
|
||||||
|
// of collapsed paths
|
||||||
|
for (let expandedPath of expandedPaths[id+"."+k]) {
|
||||||
|
if (expandedPath.startsWith(path+".") || expandedPath.startsWith(path+"[")) {
|
||||||
|
expandedPaths[id+"."+k].delete(expandedPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandedPaths[id+"."+k].delete(path)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expandPaths: [ ...expandedPaths[id+"."+k] ].sort(),
|
||||||
|
expandLeafNodes: true
|
||||||
|
}
|
||||||
|
const propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
||||||
|
const obj = $(propRow.children()[0]);
|
||||||
obj.text(k);
|
obj.text(k);
|
||||||
var tools = $('<span class="button-group"></span>');
|
|
||||||
const urlSafeK = encodeURIComponent(k)
|
const urlSafeK = encodeURIComponent(k)
|
||||||
var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
const refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
||||||
@ -229,16 +256,14 @@ RED.sidebar.context = (function() {
|
|||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
|
...objectElementOptions,
|
||||||
typeHint: data.format,
|
typeHint: data.format,
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
||||||
var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
const deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var popover = RED.popover.create({
|
var popover = RED.popover.create({
|
||||||
@ -246,7 +271,7 @@ RED.sidebar.context = (function() {
|
|||||||
target: propRow,
|
target: propRow,
|
||||||
direction: "left",
|
direction: "left",
|
||||||
content: function() {
|
content: function() {
|
||||||
var content = $('<div>');
|
const content = $('<div>');
|
||||||
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
||||||
var row = $('<p>').appendTo(content);
|
var row = $('<p>').appendTo(content);
|
||||||
var bg = $('<span class="button-group"></span>').appendTo(row);
|
var bg = $('<span class="button-group"></span>').appendTo(row);
|
||||||
@ -269,16 +294,15 @@ RED.sidebar.context = (function() {
|
|||||||
if (container.children().length === 0) {
|
if (container.children().length === 0) {
|
||||||
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
||||||
}
|
}
|
||||||
|
delete expandedPaths[id + "." + k]
|
||||||
} else {
|
} else {
|
||||||
payload = data.msg;
|
payload = data.msg;
|
||||||
format = data.format;
|
format = data.format;
|
||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
typeHint: data.format,
|
...objectElementOptions,
|
||||||
sourceId: id+"."+k,
|
typeHint: data.format
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -293,14 +317,7 @@ RED.sidebar.context = (function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
||||||
var payload = v.msg;
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]);
|
||||||
var format = v.format;
|
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
|
||||||
typeHint: v.format,
|
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
|
||||||
if (contextStores.length > 1) {
|
if (contextStores.length > 1) {
|
||||||
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ RED.utils = (function() {
|
|||||||
var pinnedPaths = {};
|
var pinnedPaths = {};
|
||||||
var formattedPaths = {};
|
var formattedPaths = {};
|
||||||
|
|
||||||
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
|
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) {
|
||||||
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
||||||
pinnedPaths[sourceId] = {}
|
pinnedPaths[sourceId] = {}
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ RED.utils = (function() {
|
|||||||
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
||||||
})
|
})
|
||||||
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
||||||
if (strippedKey !== undefined && strippedKey !== '') {
|
if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
|
||||||
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
||||||
|
|
||||||
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
@ -281,13 +281,16 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
|
function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) {
|
||||||
if (expandPaths && expandPaths.length > 0) {
|
if (expandPaths && expandPaths.length > 0) {
|
||||||
if (strippedKey === '' && minRange === undefined) {
|
if (strippedKey === '' && minRange === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (var i=0;i<expandPaths.length;i++) {
|
for (var i=0;i<expandPaths.length;i++) {
|
||||||
var p = expandPaths[i];
|
var p = expandPaths[i];
|
||||||
|
if (expandLeafNodes && p === strippedKey) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
||||||
|
|
||||||
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
||||||
@ -394,6 +397,8 @@ RED.utils = (function() {
|
|||||||
var sourceId = options.sourceId;
|
var sourceId = options.sourceId;
|
||||||
var rootPath = options.rootPath;
|
var rootPath = options.rootPath;
|
||||||
var expandPaths = options.expandPaths;
|
var expandPaths = options.expandPaths;
|
||||||
|
const enablePinning = options.enablePinning
|
||||||
|
const expandLeafNodes = options.expandLeafNodes;
|
||||||
var ontoggle = options.ontoggle;
|
var ontoggle = options.ontoggle;
|
||||||
var exposeApi = options.exposeApi;
|
var exposeApi = options.exposeApi;
|
||||||
var tools = options.tools;
|
var tools = options.tools;
|
||||||
@ -416,11 +421,11 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
||||||
if (sourceId) {
|
if (sourceId) {
|
||||||
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
|
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning);
|
||||||
}
|
}
|
||||||
if (!key) {
|
if (!key) {
|
||||||
element.addClass("red-ui-debug-msg-top-level");
|
element.addClass("red-ui-debug-msg-top-level");
|
||||||
if (sourceId) {
|
if (sourceId && !expandPaths) {
|
||||||
var pinned = pinnedPaths[sourceId];
|
var pinned = pinnedPaths[sourceId];
|
||||||
expandPaths = [];
|
expandPaths = [];
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
@ -476,7 +481,7 @@ RED.utils = (function() {
|
|||||||
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
||||||
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
||||||
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
||||||
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
|
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
||||||
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
||||||
@ -592,14 +597,16 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -623,21 +630,23 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
||||||
checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
|
checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes}));
|
||||||
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
||||||
}
|
}
|
||||||
if (fullLength < originalLength) {
|
if (fullLength < originalLength) {
|
||||||
@ -646,7 +655,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
} else if (typeof obj === 'object') {
|
} else if (typeof obj === 'object') {
|
||||||
element.addClass('collapsed');
|
element.addClass('collapsed');
|
||||||
@ -680,14 +689,16 @@ RED.utils = (function() {
|
|||||||
typeHint: false,
|
typeHint: false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: newPath,
|
path: newPath,
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -696,7 +707,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
if (key) {
|
if (key) {
|
||||||
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
||||||
|
@ -1265,11 +1265,6 @@ RED.view = (function() {
|
|||||||
var targetGroup = options.group;
|
var targetGroup = options.group;
|
||||||
var touchTrigger = options.touchTrigger;
|
var touchTrigger = options.touchTrigger;
|
||||||
|
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
RED.view.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// `point` is the place in the workspace the mouse has clicked.
|
// `point` is the place in the workspace the mouse has clicked.
|
||||||
// This takes into account scrolling and scaling of the workspace.
|
// This takes into account scrolling and scaling of the workspace.
|
||||||
var ox = point[0];
|
var ox = point[0];
|
||||||
@ -1591,9 +1586,6 @@ RED.view = (function() {
|
|||||||
// auto select dropped node - so info shows (if visible)
|
// auto select dropped node - so info shows (if visible)
|
||||||
clearSelection();
|
clearSelection();
|
||||||
nn.selected = true;
|
nn.selected = true;
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
}
|
|
||||||
movingSet.add(nn);
|
movingSet.add(nn);
|
||||||
updateActiveNodes();
|
updateActiveNodes();
|
||||||
updateSelection();
|
updateSelection();
|
||||||
|
@ -168,6 +168,37 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (data.prompts) {
|
||||||
|
if (data.loginMessage) {
|
||||||
|
const sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
$('<div>').text(data.loginMessage).appendTo(sessionMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
var loginButton = $('<a href="#" class="red-ui-button"></a>',{style: "padding: 10px"}).appendTo(row).on("click", function() {
|
||||||
|
document.location = field.url;
|
||||||
|
});
|
||||||
|
if (field.image) {
|
||||||
|
$("<img>",{src:field.image}).appendTo(loginButton);
|
||||||
|
} else if (field.label) {
|
||||||
|
var label = $('<span></span>').text(field.label);
|
||||||
|
if (field.icon) {
|
||||||
|
$('<i></i>',{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) {
|
if (opts.cancelable) {
|
||||||
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
||||||
|
@ -148,7 +148,7 @@ module.exports = function(RED) {
|
|||||||
var st = (typeof output === 'string') ? output : util.inspect(output);
|
var st = (typeof output === 'string') ? output : util.inspect(output);
|
||||||
var fill = "grey";
|
var fill = "grey";
|
||||||
var shape = "dot";
|
var shape = "dot";
|
||||||
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
|
if (typeof output === 'object' && output?.fill && output?.shape && output?.text) {
|
||||||
fill = output.fill;
|
fill = output.fill;
|
||||||
shape = output.shape;
|
shape = output.shape;
|
||||||
st = output.text;
|
st = output.text;
|
||||||
|
@ -511,9 +511,10 @@ RED.debug = (function() {
|
|||||||
typeHint: format,
|
typeHint: format,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path,
|
path: path,
|
||||||
sourceId: sourceNode&&sourceNode.id,
|
sourceId: sourceNode && sourceNode.id,
|
||||||
rootPath: path,
|
rootPath: path,
|
||||||
nodeSelector: config.messageSourceClick,
|
nodeSelector: config.messageSourceClick,
|
||||||
|
enablePinning: true
|
||||||
});
|
});
|
||||||
// Do this in a separate step so the element functions aren't stripped
|
// Do this in a separate step so the element functions aren't stripped
|
||||||
debugMessage.appendTo(el);
|
debugMessage.appendTo(el);
|
||||||
|
@ -352,7 +352,9 @@ module.exports = function(RED) {
|
|||||||
if (msgs.length === 0) {
|
if (msgs.length === 0) {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
drainMessageGroup(msgs,count,done);
|
setImmediate(() => {
|
||||||
|
drainMessageGroup(msgs,count,done);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -505,7 +507,9 @@ module.exports = function(RED) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
node.error(err,nextMsg);
|
node.error(err,nextMsg);
|
||||||
}
|
}
|
||||||
processMessageQueue()
|
setImmediate(() => {
|
||||||
|
processMessageQueue()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,20 +367,21 @@ module.exports = function(RED) {
|
|||||||
const sendHeadersAlways = node.hdrout === "all"
|
const sendHeadersAlways = node.hdrout === "all"
|
||||||
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
|
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
|
||||||
const quoteables = [node.sep, node.quo, "\n", "\r"]
|
const quoteables = [node.sep, node.quo, "\n", "\r"]
|
||||||
const templateQuoteables = [',', '"', "\n", "\r"]
|
const templateQuoteables = [node.sep, node.quo, "\n", "\r"]
|
||||||
|
const templateQuoteablesStrict = [',', '"', "\n", "\r"]
|
||||||
let badTemplateWarnOnce = true
|
let badTemplateWarnOnce = true
|
||||||
|
|
||||||
const columnStringToTemplateArray = function (col, sep) {
|
const columnStringToTemplateArray = function (col, sep) {
|
||||||
// NOTE: enforce strict column template parsing in RFC4180 mode
|
// NOTE: enforce strict column template parsing in RFC4180 mode
|
||||||
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
|
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 }
|
if (parsed.data?.length === 1) { node.goodtmpl = true } else { node.goodtmpl = false }
|
||||||
return parsed.headers.length ? parsed.headers : null
|
return node.goodtmpl ? parsed.data[0] : null
|
||||||
}
|
}
|
||||||
const templateArrayToColumnString = function (template, keepEmptyColumns) {
|
const templateArrayToColumnString = function (template, keepEmptyColumns, separator = ',', quotables = templateQuoteablesStrict) {
|
||||||
// NOTE: enforce strict column template parsing in RFC4180 mode
|
// 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 })
|
const parsed = csv.parse('', {headers: template, headersOnly:true, separator, quote: node.quo, outputStyle: 'array', strict: true })
|
||||||
return keepEmptyColumns
|
return keepEmptyColumns
|
||||||
? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables}))
|
? parsed.headers.map(e => addQuotes(e || '', { separator, quoteables: quotables })).join(separator)
|
||||||
: parsed.header // exclues empty columns
|
: parsed.header // exclues empty columns
|
||||||
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
|
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
|
||||||
// CSV->JSON: empty columns are excluded
|
// CSV->JSON: empty columns are excluded
|
||||||
@ -447,7 +448,7 @@ module.exports = function(RED) {
|
|||||||
template = Object.keys(inputData[0]) || ['']
|
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 }
|
if (sendHeadersOnce) { node.hdrSent = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +484,7 @@ module.exports = function(RED) {
|
|||||||
node.warn(RED._("csv.errors.obj_csv"))
|
node.warn(RED._("csv.errors.obj_csv"))
|
||||||
badTemplateWarnOnce = false
|
badTemplateWarnOnce = false
|
||||||
}
|
}
|
||||||
|
template = Object.keys(row) || ['']
|
||||||
const rowData = []
|
const rowData = []
|
||||||
for (let header in inputData[0]) {
|
for (let header in inputData[0]) {
|
||||||
if (row.hasOwnProperty(header)) {
|
if (row.hasOwnProperty(header)) {
|
||||||
@ -518,7 +520,7 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
// join lines, don't forget to add the last new line
|
// join lines, don't forget to add the last new line
|
||||||
msg.payload = stringBuilder.join(node.ret) + node.ret
|
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) }
|
if (msg.payload !== '') { send(msg) }
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
@ -615,16 +617,15 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
if (msg.parts.index + 1 === msg.parts.count) {
|
if (msg.parts.index + 1 === msg.parts.count) {
|
||||||
msg.payload = node.store
|
msg.payload = node.store
|
||||||
msg.columns = csvParseResult.header
|
// msg.columns = csvParseResult.header
|
||||||
// msg._mode = 'RFC4180 mode'
|
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
|
||||||
delete msg.parts
|
delete msg.parts
|
||||||
send(msg)
|
send(msg)
|
||||||
node.store = []
|
node.store = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg.columns = csvParseResult.header
|
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
|
||||||
// msg._mode = 'RFC4180 mode'
|
|
||||||
msg.payload = data
|
msg.payload = data
|
||||||
send(msg); // finally send the array
|
send(msg); // finally send the array
|
||||||
}
|
}
|
||||||
@ -633,7 +634,8 @@ module.exports = function(RED) {
|
|||||||
const len = data.length
|
const len = data.length
|
||||||
for (let row = 0; row < len; row++) {
|
for (let row = 0; row < len; row++) {
|
||||||
const newMessage = RED.util.cloneMessage(msg)
|
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]
|
newMessage.payload = data[row]
|
||||||
if (!has_parts) {
|
if (!has_parts) {
|
||||||
newMessage.parts = {
|
newMessage.parts = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/nodes",
|
"name": "@node-red/nodes",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -144,7 +144,7 @@ async function installModule(module,version,url) {
|
|||||||
if (url) {
|
if (url) {
|
||||||
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
||||||
// Git remote url or Tarball url - check the valid package 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;
|
isRegistryPackage = false;
|
||||||
} else {
|
} else {
|
||||||
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/registry",
|
"name": "@node-red/registry",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/util": "4.0.5",
|
"@node-red/util": "4.0.8",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"semver": "7.6.3",
|
"semver": "7.6.3",
|
||||||
|
@ -719,6 +719,14 @@ class Flow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContext(scope) {
|
||||||
|
if (scope === 'flow') {
|
||||||
|
return this.context
|
||||||
|
} else if (scope === 'global') {
|
||||||
|
return context.get('global')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dump() {
|
dump() {
|
||||||
console.log("==================")
|
console.log("==================")
|
||||||
console.log(this.TYPE, this.id);
|
console.log(this.TYPE, this.id);
|
||||||
|
@ -49,6 +49,14 @@ class Group {
|
|||||||
}
|
}
|
||||||
return this.parent.getSetting(key);
|
return this.parent.getSetting(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error(msg) {
|
||||||
|
this.parent.error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(scope) {
|
||||||
|
return this.parent.getContext(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -100,7 +100,24 @@ async function evaluateEnvProperties(flow, env, credentials) {
|
|||||||
}
|
}
|
||||||
} else if (type ==='jsonata') {
|
} else if (type ==='jsonata') {
|
||||||
pendingEvaluations.push(new Promise((resolve, _) => {
|
pendingEvaluations.push(new Promise((resolve, _) => {
|
||||||
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
|
redUtil.evaluateNodeProperty(value, 'jsonata',{
|
||||||
|
// Fake a node object to provide access to _flow and context
|
||||||
|
_flow: flow,
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
flow: {
|
||||||
|
get: (value, store, callback) => {
|
||||||
|
return flow.getContext('flow').get(value, store, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
get: (value, store, callback) => {
|
||||||
|
return flow.getContext('global').get(value, store, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, (err, result) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (typeof result === 'object') {
|
if (typeof result === 'object') {
|
||||||
result = { value: result, __clone__: true}
|
result = { value: result, __clone__: true}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/runtime",
|
"name": "@node-red/runtime",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -16,8 +16,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/registry": "4.0.5",
|
"@node-red/registry": "4.0.8",
|
||||||
"@node-red/util": "4.0.5",
|
"@node-red/util": "4.0.8",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/util",
|
"name": "@node-red/util",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
10
packages/node_modules/node-red/package.json
vendored
10
packages/node_modules/node-red/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"description": "Low-code programming for event-driven applications",
|
"description": "Low-code programming for event-driven applications",
|
||||||
"homepage": "https://nodered.org",
|
"homepage": "https://nodered.org",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -31,10 +31,10 @@
|
|||||||
"flow"
|
"flow"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/editor-api": "4.0.5",
|
"@node-red/editor-api": "4.0.8",
|
||||||
"@node-red/runtime": "4.0.5",
|
"@node-red/runtime": "4.0.8",
|
||||||
"@node-red/util": "4.0.5",
|
"@node-red/util": "4.0.8",
|
||||||
"@node-red/nodes": "4.0.5",
|
"@node-red/nodes": "4.0.8",
|
||||||
"basic-auth": "2.0.1",
|
"basic-auth": "2.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
|
@ -2067,6 +2067,27 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n');
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const testJson = { d: 1, b: "foo", c: "ba\"r", a: "di,ng" };
|
||||||
|
n1.emit("input", { payload: testJson });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2086,6 +2107,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '4,foo,true,,0\n');
|
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();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2106,6 +2128,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
try {
|
try {
|
||||||
// 'payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); // Legacy
|
// '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('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();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2171,6 +2194,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1,3,2,4\n4,2,3,1\n');
|
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();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2189,6 +2213,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n');
|
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();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2208,6 +2233,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
try {
|
try {
|
||||||
// 'payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n');
|
// '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('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();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2327,6 +2353,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
|
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();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
|
@ -258,6 +258,29 @@ describe('nodes/registry/installer', function() {
|
|||||||
}).catch(done);
|
}).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) {
|
it("triggers preInstall and postInstall hooks", function(done) {
|
||||||
let receivedPreEvent,receivedPostEvent;
|
let receivedPreEvent,receivedPostEvent;
|
||||||
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user