Merge branch 'dev' into fix-name-generator

This commit is contained in:
Nick O'Leary
2022-04-25 20:31:33 +01:00
committed by GitHub
154 changed files with 24779 additions and 20873 deletions

View File

@@ -108,7 +108,9 @@ module.exports = function(RED) {
}
})
this.on("input", function(msg, send, done) {
if (hasOwnProperty.call(msg, "status") && hasOwnProperty.call(msg.status, "source") && hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
if (hasOwnProperty.call(msg, "status") && msg.status &&
hasOwnProperty.call(msg.status, "source") && msg.status.source &&
hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
done();
return;
}
@@ -129,7 +131,8 @@ module.exports = function(RED) {
fill = "red";
st = msg.error.message;
}
if (hasOwnProperty.call(msg, "status")) {
if (hasOwnProperty.call(msg, "status") &&
msg.status) {
fill = msg.status.fill || "grey";
shape = msg.status.shape || "ring";
st = msg.status.text || "";

View File

@@ -48,7 +48,7 @@
<script type="text/javascript">
(function() {
var treeList;
let treeList;
function onEditPrepare(node,targetType) {
if (!node.links) {
@@ -56,7 +56,7 @@
}
node.oldLinks = [];
var activeSubflow = RED.nodes.subflow(node.z);
const activeSubflow = RED.nodes.subflow(node.z);
treeList = $("<div>")
.css({width: "100%", height: "100%"})
@@ -76,10 +76,10 @@
RED.view.redraw();
}
});
var candidateNodes = RED.nodes.filterNodes({type:targetType});
var candidateNodesCount = 0;
const candidateNodes = RED.nodes.filterNodes({type:targetType});
let candidateNodesCount = 0;
var search = $("#node-input-link-target-filter").searchBox({
const search = $("#node-input-link-target-filter").searchBox({
style: "compact",
delay: 300,
change: function() {
@@ -88,7 +88,7 @@
treeList.treeList("filter", null);
search.searchBox("count","");
} else {
var count = treeList.treeList("filter", function(item) {
const count = treeList.treeList("filter", function(item) {
return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1)
});
search.searchBox("count",count+" / "+candidateNodesCount);
@@ -96,25 +96,27 @@
}
});
var flows = [];
var flowMap = {};
const flows = [];
const flowMap = {};
if (activeSubflow) {
flowMap[activeSubflow.id] = {
id: activeSubflow.id,
class: 'red-ui-palette-header',
label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
expanded: true,
children: []
};
flows.push(flowMap[activeSubflow.id])
} else {
RED.nodes.eachWorkspace(function(ws) {
}
if (!activeSubflow || node.type === "link call") {
// Only "Link Call" can look outside of its own subflow
// Link In and Link Out nodes outside of a subflow should be ignored
RED.nodes.eachWorkspace(function (ws) {
flowMap[ws.id] = {
id: ws.id,
class: 'red-ui-palette-header',
label: (ws.label || ws.id)+(node.z===ws.id ? " *":""),
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
expanded: true,
children: []
}
@@ -122,22 +124,21 @@
})
}
candidateNodes.forEach(function(n) {
candidateNodes.forEach(function (n) {
if (flowMap[n.z]) {
if (targetType === "link out" && n.mode === 'return') {
// Link In nodes looking for Link Out nodes should not
// include return-mode nodes.
return
return;
}
var isChecked = false;
isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
if (isChecked) {
node.oldLinks.push(n.id);
}
flowMap[n.z].children.push({
id: n.id,
node: n,
label: n.name||n.id,
label: n.name || n.id,
selected: isChecked,
checkbox: node.type !== "link call",
radio: node.type === "link call"
@@ -145,8 +146,8 @@
candidateNodesCount++;
}
});
flows = flows.filter(function(f) { return f.children.length > 0 })
treeList.treeList('data',flows);
const flowsFiltered = flows.filter(function(f) { return f.children.length > 0 })
treeList.treeList('data',flowsFiltered);
setTimeout(function() {
treeList.treeList('show',node.z);
},100);
@@ -222,7 +223,6 @@
this.name = ''
RED.actions.invoke("core:generate-node-names", this)
}
for (var i=0;i<this.links.length;i++) {
var n = RED.nodes.node(this.links[i]);
if (n && n.links.indexOf(this.id) === -1) {

View File

@@ -65,6 +65,11 @@
/* opacity: 0.3;
pointer-events: none; */
}
.form-row.form-row-mqtt-datatype-tip > .form-tips {
width: calc(70% - 18px);
display: inline-block;
margin-top: -8px;
}
</style>
@@ -121,6 +126,7 @@
<div class="form-row">
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="mqtt.label.output"></span></label>
<select id="node-input-datatype" style="width:70%;">
<option value="auto-detect" data-i18n="mqtt.output.auto-detect"></option>
<option value="auto" data-i18n="mqtt.output.auto"></option>
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
<option value="utf8" data-i18n="mqtt.output.string"></option>
@@ -128,6 +134,10 @@
<option value="base64" data-i18n="mqtt.output.base64"></option>
</select>
</div>
<div class="form-row form-row-mqtt-datatype-tip">
<label> &nbsp; </label>
<div class="form-tips" id="mqtt-in-datatype-depreciated-tip"><span data-i18n="mqtt.label.auto-mode-depreciated"></span></div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -750,7 +760,7 @@
}
},
qos: {value: "2"},
datatype: {value:"auto",required:true},
datatype: {value:"auto-detect",required:true},
broker: {type:"mqtt-broker", required:true},
// subscriptionIdentifier: {value:0},
nl: {value:false},
@@ -787,6 +797,16 @@
$("div.form-row-mqtt5").toggleClass("form-row-mqtt5-active",!!v5);
$("div.form-row.form-row-mqtt-static").toggleClass("form-row-mqtt-static-disabled", !!dynamic)
}
$("#node-input-datatype").on("change", function() {
if($(this).val() === "auto") {
$(".form-row.form-row-mqtt-datatype-tip").show();
} else {
$(".form-row.form-row-mqtt-datatype-tip").hide();
}
})
$("#node-input-datatype").trigger("change");
$("#node-input-broker").on("change",function(d){
updateVisibility();
});
@@ -807,7 +827,7 @@
$("#node-input-qos").val("2");
}
if (this.datatype === undefined) {
$("#node-input-datatype").val("auto");
$("#node-input-datatype").val("auto-detect");
}
},
oneditsave: function() {

View File

@@ -20,7 +20,30 @@ module.exports = function(RED) {
var isUtf8 = require('is-utf8');
var HttpsProxyAgent = require('https-proxy-agent');
var url = require('url');
const knownMediaTypes = {
"text/css":"string",
"text/html":"string",
"text/plain":"string",
"text/html":"string",
"application/json":"json",
"application/octet-stream":"buffer",
"application/pdf":"buffer",
"application/x-gtar":"buffer",
"application/x-gzip":"buffer",
"application/x-tar":"buffer",
"application/xml":"string",
"application/zip":"buffer",
"audio/aac":"buffer",
"audio/ac3":"buffer",
"audio/basic":"buffer",
"audio/mp4":"buffer",
"audio/ogg":"buffer",
"image/bmp":"buffer",
"image/gif":"buffer",
"image/jpeg":"buffer",
"image/tiff":"buffer",
"image/png":"buffer",
}
//#region "Supporting functions"
function matchTopic(ts,t) {
if (ts == "#") {
@@ -188,24 +211,7 @@ module.exports = function(RED) {
*/
function subscriptionHandler(node, datatype ,topic, payload, packet) {
const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
if (datatype === "buffer") {
// payload = payload;
} else if (datatype === "base64") {
payload = payload.toString('base64');
} else if (datatype === "utf8") {
payload = payload.toString('utf8');
} else if (datatype === "json") {
if (isUtf8(payload)) {
payload = payload.toString();
try { payload = JSON.parse(payload); }
catch(e) { node.error(RED._("mqtt.errors.invalid-json-parse"),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
}
else { node.error((RED._("mqtt.errors.invalid-json-string")),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
} else {
if (isUtf8(payload)) { payload = payload.toString(); }
}
var msg = {topic:topic, payload:payload, qos:packet.qos, retain:packet.retain};
var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
if(v5 && packet.properties) {
setStrProp(packet.properties, msg, "responseTopic");
setBufferProp(packet.properties, msg, "correlationData");
@@ -215,6 +221,76 @@ module.exports = function(RED) {
setStrProp(packet.properties, msg, "reasonString");
setUserProperties(packet.properties.userProperties, msg);
}
const v5isUtf8 = v5 ? msg.payloadFormatIndicator === true : null;
const v5HasMediaType = v5 ? !!msg.contentType : null;
const v5MediaTypeLC = v5 ? (msg.contentType + "").toLowerCase() : null;
if (datatype === "buffer") {
// payload = payload;
} else if (datatype === "base64") {
payload = payload.toString('base64');
} else if (datatype === "utf8") {
payload = payload.toString('utf8');
} else if (datatype === "json") {
if (v5isUtf8 || isUtf8(payload)) {
try {
payload = JSON.parse(payload.toString());
} catch (e) {
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
}
} else {
node.error((RED._("mqtt.errors.invalid-json-string")), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
}
} else {
//"auto" (legacy) or "auto-detect" (new default)
if (v5isUtf8 || v5HasMediaType) {
const outputType = knownMediaTypes[v5MediaTypeLC]
switch (outputType) {
case "string":
payload = payload.toString();
break;
case "buffer":
//no change
break;
case "json":
try {
//since v5 type states this should be JSON, parse it & error out if NOT JSON
payload = payload.toString()
const obj = JSON.parse(payload);
if (datatype === "auto-detect") {
payload = obj; //as mode is "auto-detect", return the parsed JSON
}
} catch (e) {
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
}
break;
default:
if (v5isUtf8 || isUtf8(payload)) {
payload = payload.toString(); //auto String
if (datatype === "auto-detect") {
try {
payload = JSON.parse(payload); //auto to parsed object (attempt)
} catch (e) {
/* mute error - it simply isnt JSON, just leave payload as a string */
}
}
}
break;
}
} else if (isUtf8(payload)) {
payload = payload.toString(); //auto String
if (datatype === "auto-detect") {
try {
payload = JSON.parse(payload);
} catch (e) {
/* mute error - it simply isnt JSON, just leave payload as a string */
}
}
} //else {
//leave as buffer
//}
}
msg.payload = payload;
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
msg._topic = topic;
}

View File

@@ -35,8 +35,6 @@ module.exports = function(RED) {
}
}
var listenerNodes = {};
var activeListenerNodes = 0;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
@@ -166,7 +164,6 @@ module.exports = function(RED) {
}
if (node.isServer) {
activeListenerNodes++;
if (!serverUpgradeAdded) {
RED.server.on('upgrade', handleServerUpgrade);
serverUpgradeAdded = true
@@ -210,7 +207,7 @@ module.exports = function(RED) {
startconn(); // start outbound connection
}
node.on("close", function() {
node.on("close", function(done) {
if (node.heartbeatInterval) {
clearInterval(node.heartbeatInterval);
}
@@ -218,19 +215,25 @@ module.exports = function(RED) {
delete listenerNodes[node.fullPath];
node.server.close();
node._inputNodes = [];
activeListenerNodes--;
// if (activeListenerNodes === 0 && serverUpgradeAdded) {
// RED.server.removeListener('upgrade', handleServerUpgrade);
// serverUpgradeAdded = false;
// }
}
else {
node.closing = true;
node.server.close();
if (node.tout) {
clearTimeout(node.tout);
node.tout = null;
}
//wait 20*50 (1000ms max) for ws to close.
//call done when readyState === ws.CLOSED (or 1000ms, whichever comes fist)
const closeMonitorInterval = 20;
let closeMonitorCount = 50;
let si = setInterval(() => {
if(node.server.readyState === ws.CLOSED || closeMonitorCount <= 0) {
if (node.tout) {
clearTimeout(node.tout);
node.tout = null;
}
clearInterval(si);
return done();
}
closeMonitorCount--;
}, closeMonitorInterval);
}
});
}

View File

@@ -50,7 +50,8 @@
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
<div class="form-row">
@@ -76,6 +77,7 @@
datatype:{value:"buffer"},
newline:{value:""},
topic: {value:""},
trim: {value:false},
base64: {/*deprecated*/ value:false, required:true},
tls: {type:"tls-config", value:'', required:false}
},
@@ -286,7 +288,8 @@
<span id="node-units"></span>
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@@ -306,6 +309,7 @@
ret: {value:"buffer"},
splitc: {value:"0", required:true},
newline: {value:""},
trim: {value:false},
tls: {type:"tls-config", value:'', required:false}
},
inputs:1,

View File

@@ -88,6 +88,7 @@ module.exports = function(RED) {
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
this.base64 = n.base64;
this.trim = n.trim || false;
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
this.closing = false;
this.connected = false;
@@ -136,6 +137,7 @@ module.exports = function(RED) {
var parts = buffer.split(node.newline);
for (var i = 0; i<parts.length-1; i+=1) {
msg = {topic:node.topic, payload:parts[i]};
if (node.trim == true) { msg.payload += node.newline; }
msg._session = {type:"tcp",id:id};
node.send(msg);
}
@@ -230,6 +232,7 @@ module.exports = function(RED) {
var parts = buffer.split(node.newline);
for (var i = 0; i<parts.length-1; i+=1) {
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
if (node.trim == true) { msg.payload += node.newline; }
msg._session = {type:"tcp",id:id};
node.send(msg);
}
@@ -518,6 +521,7 @@ module.exports = function(RED) {
this.out = n.out;
this.ret = n.ret || "buffer";
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
this.trim = n.trim || false;
this.splitc = n.splitc;
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
@@ -653,7 +657,8 @@ module.exports = function(RED) {
let parts = chunk.split(node.newline);
for (var p=0; p<parts.length-1; p+=1) {
let m = RED.util.cloneMessage(msg);
m.payload = parts[p] + node.newline.trimEnd();
m.payload = parts[p];
if (node.trim == true) { m.payload += node.newline; }
nodeSend(m);
}
chunk = parts[parts.length-1];

View File

@@ -423,6 +423,7 @@
"string": "Ein String",
"base64": "Ein Base64-kodierter String",
"auto": "Auto-Erkennung (string oder buffer)",
"auto-detect": "Auto-Erkennung (parsed JSON-Objekt, string oder buffer)",
"json": "Ein analysiertes (parsed) JSON-Objekt"
},
"true": "wahr",

View File

@@ -424,7 +424,8 @@
"action": "Action",
"staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription",
"auto-connect": "Connect automatically"
"auto-connect": "Connect automatically",
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode."
},
"sections-label":{
"birth-message": "Message sent on connection (birth message)",
@@ -455,6 +456,7 @@
"string": "a String",
"base64": "a Base64 encoded string",
"auto": "auto-detect (string or buffer)",
"auto-detect": "auto-detect (parsed JSON object, string or buffer)",
"json": "a parsed JSON object"
},
"true": "true",
@@ -587,7 +589,8 @@
"ms": "ms",
"chars": "chars",
"close": "Close",
"optional": "(optional)"
"optional": "(optional)",
"reattach": "re-attach delimiter"
},
"type": {
"listen": "Listen on",

View File

@@ -455,6 +455,7 @@
"string": "文字列",
"base64": "Base64文字列",
"auto": "自動判定(文字列もしくはバイナリバッファ)",
"auto-detect": "自動判定(JSONオブジェクト、文字列もしくはバイナリバッファ)",
"json": "JSONオブジェクト"
},
"true": "する",

View File

@@ -362,6 +362,7 @@
"string": "문자열",
"base64": "Base64문자열",
"auto": "자동판정(문자열혹은 바이너리버퍼)",
"auto-detect": "자동판정(JSON오브젝트, 문자열혹은 바이너리버퍼)",
"json": "JSON오브젝트"
},
"true": "한다",

View File

@@ -385,6 +385,7 @@
"string": "строка",
"base64": "строка в кодировке Base64",
"auto": "автоопределение (строка или буфер)",
"auto-detect": "автоопределение (разобрать объект JSON, строка или буфер)",
"json": "объект JSON"
},
"true": "да",

View File

@@ -382,6 +382,7 @@
"string": "字符串",
"base64": "Base64编码字符串",
"auto": "自动检测 (字符串或buffer)",
"auto-detect": "自动检测 (已解析的JSON对象、字符串或buffer)",
"json": "解析的JSON对象"
},
"true": "是",

View File

@@ -386,6 +386,7 @@
"string": "字串",
"base64": "Base64編碼字串",
"auto": "自動檢測 (字符串或buffer)",
"auto-detect": "自动检测 (已解析的JSON对象、字符串或buffer)",
"json": "解析的JSON對象"
},
"true": "是",