Compare commits

..

28 Commits

Author SHA1 Message Date
Nick O'Leary
902ce68164 Merge branch 'master' into fix-group-unknown 2022-10-26 00:52:34 +01:00
Nick O'Leary
cd0474ce7b Update packages/node_modules/@node-red/editor-client/src/js/nodes.js
Co-authored-by: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com>
2022-10-26 00:51:41 +01:00
Nick O'Leary
946def022f Merge pull request #3922 from node-red/fix-httpreq-tests
Fix httprequest tests to be more lenient on error message
2022-10-26 00:40:47 +01:00
Nick O'Leary
c62a101635 Fix httprequest tests to be more lenient on error message 2022-10-16 23:10:57 +01:00
Nick O'Leary
69a575097d Remove debug 2022-10-16 22:47:34 +01:00
Nick O'Leary
b40551a8fa Handle replacing unknown node inside group or subflow 2022-10-16 22:38:11 +01:00
Nick O'Leary
32999ffa84 Merge pull request #3906 from node-red/Fix-for-csv-undefined-property
Fix for CSV undefined property
2022-10-04 15:38:37 +01:00
Nick O'Leary
f06c53f1f1 Merge pull request #3905 from node-red/mqtt-followups
Fix birth topic handling in MQTT node
2022-10-04 15:36:49 +01:00
Nick O'Leary
a9eec28360 Merge pull request #3884 from node-red/fix-auto-complete
Fix autocomplete entry for responseUrl
2022-10-04 15:35:10 +01:00
Nick O'Leary
5cda972872 Merge pull request #3890 from kazuhitoyokoi/master-fixmqttproperty
Fix pull-down menus of MQTT configuration node
2022-10-04 15:34:50 +01:00
Nick O'Leary
087946876b Merge pull request #3907 from boahc077/github_actions_token_permission
ci: add minimum GitHub token permissions for workflows
2022-10-04 15:32:47 +01:00
Nick O'Leary
318f0f1b7e Merge pull request #3899 from node-red/fix-change-self-overwrite
Fix change node overwriting msg with itself
2022-10-04 15:29:23 +01:00
Nick O'Leary
93c1600980 Merge pull request #3886 from kazuhitoyokoi/master2
Limit number of ports in function node
2022-10-04 11:00:18 +01:00
Nick O'Leary
c3d1e6181f Merge pull request #3894 from hardillb/http-response-status-number
Ensure statusCode is a number
2022-10-04 10:28:04 +01:00
Ashish Kurmi
87e7f3a61c ci: add minimum GitHub token permissions for workflows
Signed-off-by: Ashish Kurmi <akurmi@stepsecurity.io>
2022-10-02 11:16:13 -07:00
Steve-Mcl
b0abba15a6 remove dud code instead of commenting 2022-09-29 19:08:46 +01:00
Steve-Mcl
81b4874a7c fix new test and fix bug found in previous PR 2022-09-29 19:05:53 +01:00
Steve-Mcl
f11b9c1e18 add test bad birth topic
part of #3865
2022-09-29 13:12:15 +01:00
Steve-Mcl
e15ecc00ce remove old unused code (5y+ not used) 2022-09-29 13:11:25 +01:00
Dave Conway-Jones
3e4c45ac6a Fix change node overwriting msg with itself
and add test
to close #3891
2022-09-22 20:22:11 +01:00
Ben Hardill
4115c13a65 Ensure statusCode is a number
Fixes #3893

Used parseInt instead of the suggested fix so that we don't end up
with statusCode = 200.5
2022-09-19 19:43:06 +01:00
Kazuhito Yokoi
f872e2ab80 Add icon to typedInput in MQTT node 2022-09-18 19:11:33 +09:00
Kazuhito Yokoi
a81b1aa0cb Support i18n in MQTT node property 2022-09-18 17:10:19 +09:00
Kazuhito Yokoi
efc0f1ab91 Fix default values for MQTT retain settings 2022-09-18 16:24:25 +09:00
Kazuhito Yokoi
ce31edc803 Fix handling of max and min values in function outputs 2022-09-18 02:22:52 +09:00
Kazuhito Yokoi
199caccbc3 Change the maximum number of ports to 500 2022-09-17 00:25:46 +09:00
Nick O'Leary
9fd4989142 Fix autocomplete entry for responseUrl
Fixes #3883
2022-09-15 21:22:55 +01:00
Kazuhito Yokoi
7507a7b459 Limit number of ports in function node 2022-09-16 02:10:14 +09:00
14 changed files with 120 additions and 29 deletions

View File

@@ -5,6 +5,9 @@ on:
release:
types: [published]
permissions:
contents: read
jobs:
generate:
name: 'Update node-red-docker image'

View File

@@ -6,8 +6,14 @@ on:
pull_request:
branches: [ master, dev ]
permissions:
contents: read
jobs:
build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
strategy:
matrix:

View File

@@ -1965,7 +1965,7 @@ RED.nodes = (function() {
}
}
} else {
const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z)
const keepNodesCurrentZ = reimport && n.z && (RED.workspaces.contains(n.z) || RED.nodes.subflow(n.z))
if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace;
}
@@ -2067,7 +2067,7 @@ RED.nodes = (function() {
node.id = getID();
} else {
node.id = n.id;
const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z)
const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z))
if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
@@ -2740,6 +2740,7 @@ RED.nodes = (function() {
}
});
const nodeGroupMap = {}
var replaceNodeIds = Object.keys(replaceNodes);
if (replaceNodeIds.length > 0) {
var reimportList = [];
@@ -2750,6 +2751,12 @@ RED.nodes = (function() {
} else {
allNodes.removeNode(n);
}
if (n.g) {
// reimporting a node *without* including its group object
// will cause the g property to be cleared. Cache it
// here so we can restore it
nodeGroupMap[n.id] = n.g
}
reimportList.push(convertNode(n));
RED.events.emit('nodes:remove',n);
});
@@ -2771,6 +2778,18 @@ RED.nodes = (function() {
var newNodeMap = {};
result.nodes.forEach(function(n) {
newNodeMap[n.id] = n;
if (nodeGroupMap[n.id]) {
// This node is in a group - need to substitute the
// node reference inside the group
n.g = nodeGroupMap[n.id]
const group = RED.nodes.group(n.g)
if (group) {
var index = group.nodes.findIndex(gn => gn.id === n.id)
if (index > -1) {
group.nodes[index] = n
}
}
}
});
RED.nodes.eachLink(function(l) {
if (newNodeMap.hasOwnProperty(l.source.id)) {

View File

@@ -146,7 +146,7 @@
{ value: "reset", source: ["delay","trigger","join","rbe"] },
{ value: "responseCookies", source: ["http request"] },
{ value: "responseTopic", source: ["mqtt"] },
{ value: "responseURL", source: ["http request"] },
{ value: "responseUrl", source: ["http request"] },
{ value: "restartTimeout", source: ["join"] },
{ value: "retain", source: ["mqtt"] },
{ value: "schema", source: ["json"] },

View File

@@ -451,11 +451,13 @@
tabs.activateTab("func-tab-body");
$( "#node-input-outputs" ).spinner({
min:0,
min: 0,
max: 500,
change: function(event, ui) {
var value = this.value;
if (!value.match(/^\d+$/)) { value = 1; }
else if (value < this.min) { value = this.min; }
var value = parseInt(this.value);
value = isNaN(value) ? 1 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) { $(this).spinner("value", value); }
}
});

View File

@@ -318,7 +318,7 @@ module.exports = function(RED) {
}
var r = node.rules[currentRule];
if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1) && (r.p !== r.to)) {
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
completeApplyingRules(msg,currentRule,done);

View File

@@ -421,7 +421,11 @@
<script type="text/javascript">
(function() {
var typedInputNoneOpt = { value: 'none', label: '', hasValue: false };
var typedInputNoneOpt = {
value: 'none',
label: RED._("node-red:mqtt.label.none"),
hasValue: false
};
var makeTypedInputOpt = function(value){
return {
value: value,
@@ -436,7 +440,11 @@
makeTypedInputOpt("text/csv"),
makeTypedInputOpt("text/html"),
makeTypedInputOpt("text/plain"),
{value:"other", label:""}
{
value: "other",
label: RED._("node-red:mqtt.label.other"),
icon: "red/images/typedInput/az.svg"
}
];
function getDefaultContentType(value) {
@@ -499,17 +507,17 @@
cleansession: {value: true},
birthTopic: {value:"", validate:validateMQTTPublishTopic},
birthQos: {value:"0"},
birthRetain: {value:false},
birthRetain: {value:"false"},
birthPayload: {value:""},
birthMsg: { value: {}},
closeTopic: {value:"", validate:validateMQTTPublishTopic},
closeQos: {value:"0"},
closeRetain: {value:false},
closeRetain: {value:"false"},
closePayload: {value:""},
closeMsg: { value: {}},
willTopic: {value:"", validate:validateMQTTPublishTopic},
willQos: {value:"0"},
willRetain: {value:false},
willRetain: {value:"false"},
willPayload: {value:""},
willMsg: { value: {}},
userProps: { value: ""},

View File

@@ -459,7 +459,6 @@ module.exports = function(RED) {
if(!opts || typeof opts !== "object") {
return; //nothing to change, simply return
}
const originalBrokerURL = node.brokerurl;
//apply property changes (only if the property exists in the opts object)
setIfHasProperty(opts, node, "url", init);
@@ -468,7 +467,6 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "clientid", init);
setIfHasProperty(opts, node, "autoConnect", init);
setIfHasProperty(opts, node, "usetls", init);
setIfHasProperty(opts, node, "usews", init);
setIfHasProperty(opts, node, "verifyservercert", init);
setIfHasProperty(opts, node, "compatmode", init);
setIfHasProperty(opts, node, "protocolVersion", init);
@@ -571,9 +569,6 @@ module.exports = function(RED) {
if (typeof node.usetls === 'undefined') {
node.usetls = false;
}
if (typeof node.usews === 'undefined') {
node.usews = false;
}
if (typeof node.verifyservercert === 'undefined') {
node.verifyservercert = false;
}
@@ -1000,7 +995,7 @@ module.exports = function(RED) {
node.client.publish(msg.topic, msg.payload, options, function (err) {
if (done) {
done(err)
} else {
} else if(err) {
node.error(err, msg)
}
})

View File

@@ -282,7 +282,7 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
var node = this;
this.headers = n.headers||{};
this.statusCode = n.statusCode;
this.statusCode = parseInt(n.statusCode);
this.on("input",function(msg,_send,done) {
if (msg.res) {
var headers = RED.util.cloneMessage(node.headers);
@@ -323,7 +323,7 @@ module.exports = function(RED) {
}
}
}
var statusCode = node.statusCode || msg.statusCode || 200;
var statusCode = node.statusCode || parseInt(msg.statusCode) || 200;
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
msg.res._res.status(statusCode).jsonp(msg.payload);
} else {

View File

@@ -446,7 +446,9 @@
"staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription",
"auto-connect": "Connect automatically",
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode."
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode.",
"none": "none",
"other": "other"
},
"sections-label": {
"birth-message": "Message sent on connection (birth message)",

View File

@@ -446,7 +446,9 @@
"staticTopic": "1つのトピックを購読",
"dynamicTopic": "動的な購読",
"auto-connect": "自動接続",
"auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。"
"auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。",
"none": "なし",
"other": "その他"
},
"sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",

View File

@@ -1717,6 +1717,24 @@ describe('change Node', function() {
changeNode1.receive({topic:{foo:{bar:1}}, payload:"String"});
});
});
it('moves the value of a message property object to itself', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.should.have.property('payload');
msg.payload.should.equal("bar");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"bar"});
});
});
it('moves the value of a message property object to a sub-property', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.foo","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];

View File

@@ -1529,7 +1529,7 @@ describe('HTTP Request Node', function() {
msg.payload.headers.should.have.property('Content-Type').which.startWith('application/json');
//msg.dynamicHeaderName should be present in headers with the value of msg.dynamicHeaderValue
msg.payload.headers.should.have.property('dyn-header-name').which.startWith('dyn-header-value');
//static (custom) header set in Flow UI should be present
//static (custom) header set in Flow UI should be present
msg.payload.headers.should.have.property('static-header-name').which.startWith('static-header-value');
//msg.headers['location'] should be deleted because Flow UI "Location" header has a blank value
//ensures headers with matching characters but different case are eliminated
@@ -2281,7 +2281,7 @@ describe('HTTP Request Node', function() {
let port = testPort++
let server;
before(function() {
server = net.createServer(function (socket) {
socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld")
@@ -2291,7 +2291,7 @@ describe('HTTP Request Node', function() {
server.listen(port,'127.0.0.1', function(err) {
})
});
after(function() {
server.close()
});
@@ -2322,14 +2322,14 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try{
msg.payload.should.equal(`RequestError: Parse Error: Missing expected CR after header value : http://localhost:${port}/`)
msg.payload.should.match(/RequestError: Parse Error/)
done()
} catch (err) {
done(err)
}
})
n1.receive({payload: 'foo'})
});
});
}

View File

@@ -489,6 +489,39 @@ describe('MQTT Nodes', function () {
}
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
});
itConditional('should safely discard bad birth topic', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const baseTopic = nextTopic();
const brokerOptions = {
protocolVersion: 4,
birthTopic: baseTopic + "#", // a publish topic should never have a wildcard
birthPayload: "broker connected",
birthQos: 2,
}
const options = {};
const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null };
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
msg.should.have.a.property("error").type("object");
msg.error.should.have.a.property("source").type("object");
msg.error.source.should.have.a.property("id", mqttIn.id);
done();
} catch (err) {
done(err)
}
});
return true; //handled
}
options.expectMsg = null;
try {
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
done()
} catch(err) {
done(e)
}
});
itConditional('should publish close message', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
@@ -646,12 +679,13 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
const mqttBroker = helper.getNode(brokerOptions.id);
const mqttIn = helper.getNode(nodes.mqtt_in.id);
const mqttOut = helper.getNode(nodes.mqtt_out.id);
let afterLoadHandled = false;
let afterLoadHandled = false, finished = false;
if (hooks.afterLoad) {
afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)
}
if (!afterLoadHandled) {
helperNode.on("input", function (msg) {
finished = true
try {
compareMsgToExpected(msg, expectMsg);
if (hooks.done) { hooks.done(); }
@@ -676,10 +710,12 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
}
})
.catch((e) => {
if(finished) { return }
if (hooks.done) { hooks.done(e); }
else { throw e; }
});
} catch (err) {
if(finished) { return }
if (hooks.done) { hooks.done(err); }
else { throw err; }
}