mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into master
This commit is contained in:
5
packages/node_modules/@node-red/nodes/core/common/05-junction.html
vendored
Normal file
5
packages/node_modules/@node-red/nodes/core/common/05-junction.html
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<!--
|
||||
05-junction.html
|
||||
This file exists so that the runtime loads the Junction node into the registry,
|
||||
but it is empty so it doesn't appear in the editor palette
|
||||
-->
|
||||
12
packages/node_modules/@node-red/nodes/core/common/05-junction.js
vendored
Normal file
12
packages/node_modules/@node-red/nodes/core/common/05-junction.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function JunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.on("input",function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("junction",JunctionNode);
|
||||
}
|
||||
@@ -225,28 +225,47 @@
|
||||
color:"#a6bbcf",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v) {
|
||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
|
||||
if (!v || v.length === 0) { return true }
|
||||
for (var i=0;i<v.length;i++) {
|
||||
if (/msg|flow|global/.test(v[i].vt)) {
|
||||
if (!RED.utils.validatePropertyExpression(v[i].v)) {
|
||||
return false;
|
||||
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
|
||||
}
|
||||
} else if (v[i].vt === "jsonata") {
|
||||
try{jsonata(v[i].v);}catch(e){return false;}
|
||||
try{ jsonata(v[i].v); }
|
||||
catch(e){
|
||||
return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message });
|
||||
}
|
||||
} else if (v[i].vt === "json") {
|
||||
try{JSON.parse(v[i].v);}catch(e){return false;}
|
||||
try{ JSON.parse(v[i].v); }
|
||||
catch(e){
|
||||
return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message });
|
||||
}
|
||||
} else if (v[i].vt === "num"){
|
||||
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
|
||||
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
|
||||
repeat: {
|
||||
value:"", validate: function(v, opt) {
|
||||
if ((v === "") ||
|
||||
(RED.validators.number(v) &&
|
||||
(v >= 0) && (v <= 2147483))) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:inject.errors.invalid-repeat");
|
||||
}
|
||||
},
|
||||
crontab: {value:""},
|
||||
once: {value:false},
|
||||
onceDelay: {value:0.1},
|
||||
topic: {value:""},
|
||||
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
|
||||
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
|
||||
payloadType: {value:"date"},
|
||||
},
|
||||
icon: "inject.svg",
|
||||
@@ -370,13 +389,6 @@
|
||||
var id = $("#inject-time-type-select").val();
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-row-"+id).show();
|
||||
if ((id == "none") || (id == "interval") || (id == "interval-time")) {
|
||||
$("#node-once").show();
|
||||
}
|
||||
else {
|
||||
$("#node-once").hide();
|
||||
$("#node-input-once").prop('checked', false);
|
||||
}
|
||||
|
||||
// Scroll down
|
||||
var scrollDiv = $("#dialog-form").parent();
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
RED.nodes.registerType('debug',{
|
||||
category: 'common',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: {value:"_DEFAULT_"},
|
||||
active: {value:true},
|
||||
tosidebar: {value:true},
|
||||
console: {value:false},
|
||||
@@ -160,6 +160,10 @@
|
||||
},
|
||||
messageSourceClick: function(sourceId, aliasId, path) {
|
||||
// Get all of the nodes that could have logged this message
|
||||
if (RED.nodes.workspace(sourceId)) {
|
||||
RED.view.reveal(sourceId);
|
||||
return
|
||||
}
|
||||
var candidateNodes = [RED.nodes.node(sourceId)]
|
||||
if (path) {
|
||||
for (var i=2;i<path.length;i++) {
|
||||
@@ -235,10 +239,11 @@
|
||||
// sourceNode should be the top-level node - one that is on a flow.
|
||||
var sourceNode;
|
||||
var pathParts;
|
||||
var pathHierarchy;
|
||||
if (o.path) {
|
||||
// Path is a `/`-separated list of ids that identifies the
|
||||
// complete parentage of the node that generated this message.
|
||||
// flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
|
||||
// flow-id/subflow-A-instance/subflow-B-instance
|
||||
|
||||
// If it has one id, that is a top level flow
|
||||
// each subsequent id is the instance id of a subflow node
|
||||
@@ -251,11 +256,41 @@
|
||||
// Highlight the subflow instance node.
|
||||
sourceNode = RED.nodes.node(pathParts[1]);
|
||||
}
|
||||
pathHierarchy = pathParts.map((id,index) => {
|
||||
if (index === 0) {
|
||||
return {
|
||||
id: id,
|
||||
label: RED.nodes.workspace(id).label
|
||||
}
|
||||
} else {
|
||||
var instanceNode = RED.nodes.node(id)
|
||||
return {
|
||||
id: id,
|
||||
label: (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (pathParts.length === 1) {
|
||||
pathHierarchy.push({
|
||||
id: o.id,
|
||||
label: sourceNode.name || sourceNode.type+":"+sourceNode.id
|
||||
})
|
||||
}
|
||||
if (o._alias) {
|
||||
let aliasNode = RED.nodes.node(o._alias)
|
||||
if (aliasNode) {
|
||||
pathHierarchy.push({
|
||||
id: o._alias,
|
||||
label: aliasNode.name || aliasNode.type+":"+aliasNode.id
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is probably redundant...
|
||||
sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
|
||||
}
|
||||
if (sourceNode) {
|
||||
var sourceFlow = RED.nodes.workspace(sourceNode.z)
|
||||
o._source = {
|
||||
id:sourceNode.id,
|
||||
z:sourceNode.z,
|
||||
@@ -266,7 +301,9 @@
|
||||
// the top-level subflow instance node.
|
||||
// This means the node's name is displayed in the sidebar.
|
||||
_alias:o._alias,
|
||||
path: pathParts
|
||||
flowName: sourceFlow?(sourceFlow.label||sourceNode.z):sourceNode.z,
|
||||
path: pathParts,
|
||||
pathHierarchy: pathHierarchy
|
||||
};
|
||||
}
|
||||
RED.debug.handleDebugMessage(o);
|
||||
@@ -416,9 +453,16 @@
|
||||
label: RED._("node-red:debug.autostatus"),
|
||||
hasValue: false
|
||||
};
|
||||
|
||||
var counter = {
|
||||
value: "counter",
|
||||
label: RED._("node-red:debug.messageCount"),
|
||||
hasValue: false
|
||||
};
|
||||
|
||||
$("#node-input-typed-status").typedInput({
|
||||
default: "auto",
|
||||
types:[autoType, "msg", "jsonata"],
|
||||
types:[autoType, "msg", "jsonata", counter],
|
||||
typeField: $("#node-input-statusType")
|
||||
});
|
||||
var that = this;
|
||||
@@ -455,7 +499,7 @@
|
||||
types:['msg', fullType, "jsonata"],
|
||||
typeField: $("#node-input-targetType")
|
||||
});
|
||||
if (this.targetType === "jsonata") {
|
||||
if (this.targetType === "jsonata") {
|
||||
var property = this.complete || "";
|
||||
$("#node-input-typed-complete").typedInput('type','jsonata');
|
||||
$("#node-input-typed-complete").typedInput('value',property);
|
||||
@@ -477,12 +521,16 @@
|
||||
|
||||
$("#node-input-tostatus").on('change',function() {
|
||||
if ($(this).is(":checked")) {
|
||||
if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
|
||||
if (that.statusType === "counter") {
|
||||
that.statusVal = "";
|
||||
}
|
||||
else if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
|
||||
var type = $("#node-input-typed-complete").typedInput('type');
|
||||
var comp = "payload";
|
||||
if (type !== 'full') {
|
||||
comp = $("#node-input-typed-complete").typedInput('value');
|
||||
}
|
||||
console.log('hihi')
|
||||
that.statusType = "auto";
|
||||
that.statusVal = comp;
|
||||
}
|
||||
@@ -507,6 +555,12 @@
|
||||
$("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
|
||||
}
|
||||
$("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
|
||||
},
|
||||
onadd: function() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
this.name = ''
|
||||
RED.actions.invoke("core:generate-node-names", this)
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -21,6 +21,9 @@ module.exports = function(RED) {
|
||||
this.statusType = n.statusType || "auto";
|
||||
this.statusVal = n.statusVal || this.complete;
|
||||
this.tosidebar = n.tosidebar;
|
||||
this.counter = 0;
|
||||
this.lastTime = new Date().getTime();
|
||||
this.timeout = null;
|
||||
if (this.tosidebar === undefined) { this.tosidebar = true; }
|
||||
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
|
||||
if (this.tostatus) {
|
||||
@@ -32,6 +35,12 @@ module.exports = function(RED) {
|
||||
var statExpression = hasStatExpression ? n.statusVal : null;
|
||||
|
||||
var node = this;
|
||||
if ( node.statusType === "counter" ){
|
||||
node.status({fill:"blue", shape:"ring", text: node.counter});
|
||||
}
|
||||
else {
|
||||
node.status({fill:"", shape:"", text: ""});
|
||||
}
|
||||
var preparedEditExpression = null;
|
||||
var preparedStatExpression = null;
|
||||
if (editExpression) {
|
||||
@@ -106,46 +115,64 @@ module.exports = function(RED) {
|
||||
if (this.oldState) {
|
||||
this.status({});
|
||||
}
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
}
|
||||
})
|
||||
this.on("input", function(msg, send, done) {
|
||||
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)) {
|
||||
if (hasOwnProperty.call(msg, "status") && hasOwnProperty.call(msg.status, "source") && hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
if (node.tostatus === true) {
|
||||
prepareStatus(msg, function(err,debugMsg) {
|
||||
if (err) { node.error(err); return; }
|
||||
var output = debugMsg.msg;
|
||||
var st = (typeof output === 'string') ? output : util.inspect(output);
|
||||
var fill = "grey";
|
||||
var shape = "dot";
|
||||
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
|
||||
fill = output.fill;
|
||||
shape = output.shape;
|
||||
st = output.text;
|
||||
if ( node.statusType === "counter" ){
|
||||
const differenceOfTime = (new Date().getTime() - node.lastTime);
|
||||
node.lastTime = new Date().getTime();
|
||||
node.counter++;
|
||||
if ( differenceOfTime > 100 ){
|
||||
node.status({fill:"blue", shape:"ring", text: node.counter});
|
||||
}
|
||||
if (node.statusType === "auto") {
|
||||
if (hasOwnProperty.call(msg, "error")) {
|
||||
fill = "red";
|
||||
st = msg.error.message;
|
||||
}
|
||||
if (hasOwnProperty.call(msg, "status") &&
|
||||
msg.status) {
|
||||
fill = msg.status.fill || "grey";
|
||||
shape = msg.status.shape || "ring";
|
||||
st = msg.status.text || "";
|
||||
else {
|
||||
if (node.timeout) {
|
||||
clearTimeout(node.timeout)
|
||||
}
|
||||
node.timeout = setTimeout(() => {
|
||||
node.status({fill:"blue", shape:"ring", text: node.counter})
|
||||
}, 200)
|
||||
}
|
||||
} else {
|
||||
prepareStatus(msg, function(err,debugMsg) {
|
||||
if (err) { node.error(err); return; }
|
||||
var output = debugMsg.msg;
|
||||
var st = (typeof output === 'string') ? output : util.inspect(output);
|
||||
var fill = "grey";
|
||||
var shape = "dot";
|
||||
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
|
||||
fill = output.fill;
|
||||
shape = output.shape;
|
||||
st = output.text;
|
||||
}
|
||||
if (node.statusType === "auto") {
|
||||
if (hasOwnProperty.call(msg, "error")) {
|
||||
fill = "red";
|
||||
st = msg.error.message;
|
||||
}
|
||||
if (hasOwnProperty.call(msg, "status")) {
|
||||
fill = msg.status.fill || "grey";
|
||||
shape = msg.status.shape || "ring";
|
||||
st = msg.status.text || "";
|
||||
}
|
||||
}
|
||||
|
||||
if (st.length > 32) { st = st.substr(0,32) + "..."; }
|
||||
var newStatus = {fill:fill, shape:shape, text:st};
|
||||
if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to
|
||||
node.status(newStatus);
|
||||
node.oldState = JSON.stringify(newStatus);
|
||||
}
|
||||
});
|
||||
if (st.length > 32) { st = st.substr(0,32) + "..."; }
|
||||
|
||||
var newStatus = {fill:fill, shape:shape, text:st};
|
||||
if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to
|
||||
node.status(newStatus);
|
||||
node.oldState = JSON.stringify(newStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.complete === "true") {
|
||||
@@ -288,7 +315,7 @@ module.exports = function(RED) {
|
||||
res.sendFile(
|
||||
req.params[0],
|
||||
options,
|
||||
err => {
|
||||
err => {
|
||||
if (err) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
|
||||
@@ -32,14 +32,23 @@
|
||||
<label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
|
||||
<input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
|
||||
</div>
|
||||
<div style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
|
||||
<div class="form-row node-input-link-row"></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-linkType" data-i18n="link.linkCallType"></label>
|
||||
<select id="node-input-linkType" style="width: 70%">
|
||||
<option value="static" data-i18n="link.staticLinkCall"></option>
|
||||
<option value="dynamic" data-i18n="link.dynamicLinkCall"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="link-call-target-tree" style="position:relative; height: 30px; text-align: right;">
|
||||
<div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div>
|
||||
</div>
|
||||
<div class="form-row node-input-link-row link-call-target-tree"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
var treeList;
|
||||
let treeList;
|
||||
|
||||
function onEditPrepare(node,targetType) {
|
||||
if (!node.links) {
|
||||
@@ -47,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%"})
|
||||
@@ -67,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() {
|
||||
@@ -79,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);
|
||||
@@ -87,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: []
|
||||
}
|
||||
@@ -113,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"
|
||||
@@ -136,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);
|
||||
@@ -209,6 +219,10 @@
|
||||
}
|
||||
|
||||
function onAdd() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
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) {
|
||||
@@ -221,7 +235,7 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: { value: "_DEFAULT_" },
|
||||
links: { value: [], type:"link out[]" }
|
||||
},
|
||||
inputs:0,
|
||||
@@ -257,9 +271,14 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
links: { value: [], type:"link in[]"},
|
||||
timeout: { value: "30", validate:RED.validators.number(true) }
|
||||
name: { value: "" },
|
||||
links: { value: [], type:"link in[]" },
|
||||
linkType: { value:"static" },
|
||||
timeout: {
|
||||
value: "30",
|
||||
label: RED._("node-red:link.timeout"),
|
||||
validate:RED.validators.number(true)
|
||||
}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
@@ -271,7 +290,9 @@
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.links.length > 0) {
|
||||
if (this.linkType === "dynamic") {
|
||||
return this._("link.dynamicLinkLabel");
|
||||
} else if (this.links.length > 0) {
|
||||
var targetNode = RED.nodes.node(this.links[0]);
|
||||
return targetNode && (targetNode.name || this._("link.linkCall"));
|
||||
}
|
||||
@@ -281,6 +302,22 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
console.log("link call oneditprepare")
|
||||
const updateVisibility = function() {
|
||||
const static = $('#node-input-linkType').val() !== "dynamic";
|
||||
if(static) {
|
||||
$("div.link-call-target-tree").show();
|
||||
} else {
|
||||
$("div.link-call-target-tree").hide();
|
||||
}
|
||||
}
|
||||
$("#node-input-linkType").on("change",function(d){
|
||||
updateVisibility();
|
||||
});
|
||||
if (["static","dynamic"].indexOf(this.linkType) < 0) {
|
||||
$("#node-input-linkType").val('static');
|
||||
}
|
||||
updateVisibility();
|
||||
onEditPrepare(this,"link in");
|
||||
},
|
||||
oneditsave: function() {
|
||||
@@ -293,9 +330,9 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: { value:"_DEFAULT_" },
|
||||
mode: { value: "link" },// link || return
|
||||
links: { value: [], type:"link in[]"}
|
||||
links: { value: [], type:"link in[]" }
|
||||
},
|
||||
align:"right",
|
||||
inputs:1,
|
||||
|
||||
@@ -14,10 +14,119 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef LinkTarget
|
||||
* @type {object}
|
||||
* @property {string} id - ID of the target node.
|
||||
* @property {string} name - Name of target Node
|
||||
* @property {number} flowId - ID of flow where the target node exists
|
||||
* @property {string} flowName - Name of flow where the target node exists
|
||||
* @property {boolean} isSubFlow - True if the link-in node exists in a subflow instance
|
||||
*/
|
||||
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
const crypto = require("crypto");
|
||||
const targetCache = (function () {
|
||||
const registry = { id: {}, name: {} };
|
||||
function getIndex(/** @type {[LinkTarget]}*/ targets, id) {
|
||||
for (let index = 0; index < (targets || []).length; index++) {
|
||||
const element = targets[index];
|
||||
if (element.id === id) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Generate a target object from a node
|
||||
* @param {LinkInNode} node
|
||||
* @returns {LinkTarget} a link target object
|
||||
*/
|
||||
function generateTarget(node) {
|
||||
const isSubFlow = node._flow.TYPE === "subflow";
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name || node.id,
|
||||
flowId: node._flow.flow.id,
|
||||
flowName: isSubFlow ? node._flow.subflowDef.name : node._flow.flow.label,
|
||||
isSubFlow: isSubFlow
|
||||
}
|
||||
}
|
||||
return {
|
||||
/**
|
||||
* Get a list of targets registerd to this name
|
||||
* @param {string} name Name of the target
|
||||
* @param {boolean} [excludeSubflows] set `true` to exclude
|
||||
* @returns {[LinkTarget]} Targets registerd to this name.
|
||||
*/
|
||||
getTargets(name, excludeSubflows) {
|
||||
const targets = registry.name[name] || [];
|
||||
if (excludeSubflows) {
|
||||
return targets.filter(e => e.isSubFlow != true);
|
||||
}
|
||||
return targets;
|
||||
},
|
||||
/**
|
||||
* Get a single target by registered name.
|
||||
* To restrict to a single flow, include the `flowId`
|
||||
* If there is no targets OR more than one target, null is returned
|
||||
* @param {string} name Name of the node
|
||||
* @param {string} [flowId]
|
||||
* @returns {LinkTarget} target
|
||||
*/
|
||||
getTarget(name, flowId) {
|
||||
/** @type {[LinkTarget]}*/
|
||||
let possibleTargets = this.getTargets(name);
|
||||
/** @type {LinkTarget}*/
|
||||
let target;
|
||||
if (possibleTargets.length && flowId) {
|
||||
possibleTargets = possibleTargets.filter(e => e.flowId == flowId);
|
||||
}
|
||||
if (possibleTargets.length === 1) {
|
||||
target = possibleTargets[0];
|
||||
}
|
||||
return target;
|
||||
},
|
||||
/**
|
||||
* Get a target by node ID
|
||||
* @param {string} nodeId ID of the node
|
||||
* @returns {LinkTarget} target
|
||||
*/
|
||||
getTargetById(nodeId) {
|
||||
return registry.id[nodeId];
|
||||
},
|
||||
register(/** @type {LinkInNode} */ node) {
|
||||
const target = generateTarget(node);
|
||||
const tByName = this.getTarget(target.name, target.flowId);
|
||||
if (!tByName || tByName.id !== target.id) {
|
||||
registry.name[target.name] = registry.name[target.name] || [];
|
||||
registry.name[target.name].push(target)
|
||||
}
|
||||
registry.id[target.id] = target;
|
||||
return target;
|
||||
},
|
||||
remove(node) {
|
||||
const target = generateTarget(node);
|
||||
const tn = this.getTarget(target.name, target.flowId);
|
||||
if (tn) {
|
||||
const targs = this.getTargets(tn.name);
|
||||
const idx = getIndex(targs, tn.id);
|
||||
if (idx > -1) {
|
||||
targs.splice(idx, 1);
|
||||
}
|
||||
if (targs.length === 0) {
|
||||
delete registry.name[tn.name];
|
||||
}
|
||||
}
|
||||
delete registry.id[target.id];
|
||||
},
|
||||
clear() {
|
||||
registry = { id: {}, name: {} };
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function LinkInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -27,12 +136,14 @@ module.exports = function(RED) {
|
||||
msg._event = n.event;
|
||||
node.receive(msg);
|
||||
}
|
||||
targetCache.register(node);
|
||||
RED.events.on(event,handler);
|
||||
this.on("input", function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
this.on("close",function() {
|
||||
targetCache.remove(node);
|
||||
RED.events.removeListener(event,handler);
|
||||
});
|
||||
}
|
||||
@@ -74,31 +185,69 @@ module.exports = function(RED) {
|
||||
function LinkCallNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
const node = this;
|
||||
const target = n.links[0];
|
||||
const staticTarget = typeof n.links === "string" ? n.links : n.links[0];
|
||||
const linkType = n.linkType;
|
||||
const messageEvents = {};
|
||||
let timeout = parseFloat(n.timeout || "30")*1000;
|
||||
|
||||
let timeout = parseFloat(n.timeout || "30") * 1000;
|
||||
if (isNaN(timeout)) {
|
||||
timeout = 30000;
|
||||
}
|
||||
function getTargetNode(msg) {
|
||||
const dynamicMode = linkType === "dynamic";
|
||||
const target = dynamicMode ? msg.target : staticTarget
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
msg._linkSource = msg._linkSource || [];
|
||||
const messageEvent = {
|
||||
id: crypto.randomBytes(14).toString('hex'),
|
||||
node: node.id,
|
||||
////1st see if the target is a direct node id
|
||||
let foundNode;
|
||||
if (targetCache.getTargetById(target)) {
|
||||
foundNode = RED.nodes.getNode(target)
|
||||
}
|
||||
messageEvents[messageEvent.id] = {
|
||||
msg: RED.util.cloneMessage(msg),
|
||||
send,
|
||||
done,
|
||||
ts: setTimeout(function() {
|
||||
timeoutMessage(messageEvent.id)
|
||||
}, timeout )
|
||||
};
|
||||
msg._linkSource.push(messageEvent);
|
||||
var targetNode = RED.nodes.getNode(target);
|
||||
if (targetNode) {
|
||||
targetNode.receive(msg);
|
||||
if (target && !foundNode && dynamicMode) {
|
||||
//next, look in **this flow only** for the node
|
||||
let cachedTarget = targetCache.getTarget(target, node._flow.flow.id);
|
||||
if (!cachedTarget) {
|
||||
//single target node not found in registry!
|
||||
//get all possible targets from regular flows (exclude subflow instances)
|
||||
const possibleTargets = targetCache.getTargets(target, true);
|
||||
if (possibleTargets.length === 1) {
|
||||
//only 1 link-in found with this name - good, lets use it
|
||||
cachedTarget = possibleTargets[0];
|
||||
} else if (possibleTargets.length > 1) {
|
||||
//more than 1 link-in has this name, raise an error
|
||||
throw new Error(`Multiple link-in nodes named '${target}' found`);
|
||||
}
|
||||
}
|
||||
if (cachedTarget) {
|
||||
foundNode = RED.nodes.getNode(cachedTarget.id);
|
||||
}
|
||||
}
|
||||
if (foundNode instanceof LinkInNode) {
|
||||
return foundNode;
|
||||
}
|
||||
throw new Error(`target link-in node '${target || ""}' not found`);
|
||||
}
|
||||
this.on("input", function (msg, send, done) {
|
||||
try {
|
||||
const targetNode = getTargetNode(msg);
|
||||
if (targetNode instanceof LinkInNode) {
|
||||
msg._linkSource = msg._linkSource || [];
|
||||
const messageEvent = {
|
||||
id: crypto.randomBytes(14).toString('hex'),
|
||||
node: node.id,
|
||||
}
|
||||
messageEvents[messageEvent.id] = {
|
||||
msg: RED.util.cloneMessage(msg),
|
||||
send,
|
||||
done,
|
||||
ts: setTimeout(function () {
|
||||
timeoutMessage(messageEvent.id)
|
||||
}, timeout)
|
||||
};
|
||||
msg._linkSource.push(messageEvent);
|
||||
targetNode.receive(msg);
|
||||
}
|
||||
} catch (error) {
|
||||
node.error(error, msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -581,12 +581,45 @@ RED.debug = (function() {
|
||||
var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
|
||||
$('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
|
||||
if (sourceNode) {
|
||||
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
|
||||
|
||||
var nodeLink = $('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text("node: "+(o.name||sourceNode.name||sourceNode.id))
|
||||
.appendTo(metaRow)
|
||||
.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path);
|
||||
});
|
||||
|
||||
if (sourceNode.pathHierarchy) {
|
||||
RED.popover.create({
|
||||
tooltip: true,
|
||||
target:nodeLink,
|
||||
trigger: "hover",
|
||||
size: "small",
|
||||
direction: "bottom",
|
||||
interactive: true,
|
||||
content: function() {
|
||||
const content = $("<div>")
|
||||
sourceNode.pathHierarchy.forEach((pathPart,idx) => {
|
||||
const link = $("<a>", {href:"#" ,style:'display: block'})
|
||||
.css({
|
||||
paddingLeft:((idx*10)+((idx === sourceNode.pathHierarchy.length - 1)?10:0))+"px",
|
||||
paddingRight:'2px'
|
||||
})
|
||||
.text(pathPart.label)
|
||||
.appendTo(content)
|
||||
.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
config.messageSourceClick(pathPart.id);
|
||||
})
|
||||
if (idx < sourceNode.pathHierarchy.length - 1) {
|
||||
$('<i class="fa fa-angle-down" style="margin-right: 3px"></i>').prependTo(link)
|
||||
}
|
||||
})
|
||||
return content
|
||||
},
|
||||
delay: { show: 50, hide: 150 }
|
||||
});
|
||||
}
|
||||
} else if (name) {
|
||||
$('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);
|
||||
}
|
||||
|
||||
@@ -355,27 +355,41 @@
|
||||
color:"#fdd0a2",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: {value:"_DEFAULT_"},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||
noerr: {value:0,required:true,
|
||||
validate: function(v, opt) {
|
||||
if (!v) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:function.error.invalid-js");
|
||||
}},
|
||||
initialize: {value:""},
|
||||
finalize: {value:""},
|
||||
libs: {value: [], validate: function(v) {
|
||||
libs: {value: [], validate: function(v, opt) {
|
||||
if (!v) { return true; }
|
||||
for (var i=0,l=v.length;i<l;i++) {
|
||||
var m = v[i];
|
||||
if (!RED.utils.checkModuleAllowed(m.module,null,installAllowList,installDenyList)) {
|
||||
return false
|
||||
return RED._("node-red:function.error.moduleNotAllowed", {
|
||||
module: m.module
|
||||
});
|
||||
}
|
||||
if (m.var === "" || / /.test(m.var)) {
|
||||
return false;
|
||||
return RED._("node-red:function.error.moduleNameError", {
|
||||
name: m.var
|
||||
});
|
||||
}
|
||||
if (missingModules.indexOf(m.module) > -1) {
|
||||
return false;
|
||||
return RED._("node-red:function.error.missing-module", {
|
||||
module: m.module
|
||||
});
|
||||
}
|
||||
if (invalidModuleVNames.indexOf(m.var) !== -1){
|
||||
return false;
|
||||
return RED._("node-red:function.error.moduleNameError", {
|
||||
name: m.var
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -399,11 +413,19 @@
|
||||
$("#func-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
let editor = $("#" + tab.id).find('.monaco-editor').first();
|
||||
if(editor.length) {
|
||||
if(editor.length) {
|
||||
if(that.editor.nodered && that.editor.type == "monaco") {
|
||||
that.editor.nodered.refreshModuleLibs(getLibsList());
|
||||
}
|
||||
RED.tray.resize();
|
||||
//auto focus editor on tab switch
|
||||
if (that.initEditor.getDomNode() == editor[0]) {
|
||||
that.initEditor.focus();
|
||||
} else if (that.editor.getDomNode() == editor[0]) {
|
||||
that.editor.focus();
|
||||
} else if (that.finalizeEditor.getDomNode() == editor[0]) {
|
||||
that.finalizeEditor.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -438,11 +460,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
var buildEditor = function(id, value, defaultValue, extraLibs) {
|
||||
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs) {
|
||||
var editor = RED.editor.createEditor({
|
||||
id: id,
|
||||
mode: 'ace/mode/nrjavascript',
|
||||
value: value || defaultValue || "",
|
||||
stateId: stateId,
|
||||
focus: true,
|
||||
globals: {
|
||||
msg:true,
|
||||
context:true,
|
||||
@@ -462,11 +486,12 @@
|
||||
if (defaultValue && value === "") {
|
||||
editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
|
||||
}
|
||||
editor.__stateId = stateId;
|
||||
return editor;
|
||||
}
|
||||
this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize"))
|
||||
this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val(), undefined, that.libs || [])
|
||||
this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize"))
|
||||
this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize"))
|
||||
this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || [])
|
||||
this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize"))
|
||||
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
@@ -505,28 +530,33 @@
|
||||
],
|
||||
ext:"js"
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
|
||||
var expandButtonClickHandler = function(editor) {
|
||||
return function(e) {
|
||||
return function (e) {
|
||||
e.preventDefault();
|
||||
var value = editor.getValue();
|
||||
editor.saveView(`inside function-expandButtonClickHandler ${editor.__stateId}`);
|
||||
var extraLibs = that.libs || [];
|
||||
RED.editor.editJavaScript({
|
||||
value: value,
|
||||
width: "Infinity",
|
||||
cursor: editor.getCursorPosition(),
|
||||
stateId: editor.__stateId,
|
||||
mode: "ace/mode/nrjavascript",
|
||||
complete: function(v,cursor) {
|
||||
editor.setValue(v, -1);
|
||||
editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
focus: true,
|
||||
cancel: function () {
|
||||
setTimeout(function () {
|
||||
editor.focus();
|
||||
},300);
|
||||
}, 250);
|
||||
},
|
||||
complete: function (v, cursor) {
|
||||
editor.setValue(v, -1);
|
||||
setTimeout(function () {
|
||||
editor.restoreView();
|
||||
editor.focus();
|
||||
}, 250);
|
||||
},
|
||||
extraLibs: extraLibs
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
$("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor));
|
||||
@@ -605,6 +635,12 @@
|
||||
this.finalizeEditor.resize();
|
||||
|
||||
$("#node-input-libs-container").css("height", (height - 192)+"px");
|
||||
},
|
||||
onadd: function() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
this.name = ''
|
||||
RED.actions.invoke("core:generate-node-names", this)
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -163,7 +163,9 @@
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload", required:true, validate: RED.validators.typedInput("propertyType")},
|
||||
property: {value:"payload", required:true,
|
||||
label:RED._("node-red:common.label.payload"),
|
||||
validate: RED.validators.typedInput("propertyType", false)},
|
||||
propertyType: { value:"msg" },
|
||||
rules: {value:[{t:"eq", v:"", vt:"str"}]},
|
||||
checkall: {value:"true", required:true},
|
||||
|
||||
@@ -55,6 +55,7 @@ module.exports = function(RED) {
|
||||
catch(e) { return false;}
|
||||
}
|
||||
else if (b === "null") { return a === null; }
|
||||
else if (b === "number") { return typeof a === b && !isNaN(a) }
|
||||
else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
|
||||
},
|
||||
'head': function(a, b, c, d, parts) {
|
||||
|
||||
@@ -19,38 +19,66 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
function validateProperty(v,vt) {
|
||||
function isInvalidProperty(v,vt) {
|
||||
if (/msg|flow|global/.test(vt)) {
|
||||
if (!RED.utils.validatePropertyExpression(v)) {
|
||||
return false;
|
||||
return RED._("node-red:change.errors.invalid-prop", {
|
||||
property: v
|
||||
});
|
||||
}
|
||||
} else if (vt === "jsonata") {
|
||||
try{jsonata(v);}catch(e){return false;}
|
||||
try{ jsonata(v); } catch(e) {
|
||||
return RED._("node-red:change.errors.invalid-expr", {
|
||||
error: e.message
|
||||
});
|
||||
}
|
||||
} else if (vt === "json") {
|
||||
try{JSON.parse(v);}catch(e){return false;}
|
||||
try{ JSON.parse(v); } catch(e) {
|
||||
return RED._("node-red:change.errors.invalid-json-data", {
|
||||
error: e.message
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
RED.nodes.registerType('change', {
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules) {
|
||||
rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules, opt) {
|
||||
var msg;
|
||||
if (!rules || rules.length === 0) { return true }
|
||||
for (var i=0;i<rules.length;i++) {
|
||||
var r = rules[i];
|
||||
if (r.t === 'set') {
|
||||
if (!validateProperty(r.p,r.pt) || !validateProperty(r.to,r.tot)) {
|
||||
return false;
|
||||
if (msg = isInvalidProperty(r.p,r.pt)) {
|
||||
return msg;
|
||||
}
|
||||
if (msg = isInvalidProperty(r.to,r.tot)) {
|
||||
return msg;
|
||||
}
|
||||
} else if (r.t === 'change') {
|
||||
if (!validateProperty(r.p,r.pt) || !validateProperty(r.from,r.fromt) || !validateProperty(r.to,r.tot)) {
|
||||
return false;
|
||||
if (msg = isInvalidProperty(r.p,r.pt)) {
|
||||
return msg;
|
||||
}
|
||||
if(msg = isInvalidProperty(r.from,r.fromt)) {
|
||||
return msg;
|
||||
}
|
||||
if(msg = isInvalidProperty(r.to,r.tot)) {
|
||||
return msg;
|
||||
}
|
||||
} else if (r.t === 'delete') {
|
||||
if (msg = isInvalidProperty(r.p,r.pt)) {
|
||||
return msg;
|
||||
}
|
||||
} else if (r.t === 'move') {
|
||||
if (!validateProperty(r.p,r.pt)) {
|
||||
return false;
|
||||
if (msg = isInvalidProperty(r.p,r.pt)) {
|
||||
return msg;
|
||||
}
|
||||
if (msg = isInvalidProperty(r.to,r.tot)) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,22 @@
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
minin: {value:"",required:true,validate:RED.validators.number()},
|
||||
maxin: {value:"",required:true,validate:RED.validators.number()},
|
||||
minout: {value:"",required:true,validate:RED.validators.number()},
|
||||
maxout: {value:"",required:true,validate:RED.validators.number()},
|
||||
minin: {value:"", required: true,
|
||||
label:RED._("node-red:range.label.minin"),
|
||||
validate:RED.validators.number(false)},
|
||||
maxin: {value:"", required: true,
|
||||
label:RED._("node-red:range.label.maxin"),
|
||||
validate:RED.validators.number(false)},
|
||||
minout: {value:"", required:true,
|
||||
label:RED._("node-red:range.label.minout"),
|
||||
validate:RED.validators.number(false)},
|
||||
maxout: {value:"", required:true,
|
||||
label:RED._("node-red:range.label.maxout"),
|
||||
validate:RED.validators.number(false)},
|
||||
action: {value:"scale"},
|
||||
round: {value:false},
|
||||
property: {value:"payload",required:true},
|
||||
property: {value:"payload",required:true,
|
||||
label:RED._("node-red:common.label.property")},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs: 1,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<option value="handlebars">mustache</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="javascript">Javascript</option>
|
||||
<option value="javascript">JavaScript</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="python">Python</option>
|
||||
@@ -56,7 +56,9 @@
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
field: {value:"payload", validate:RED.validators.typedInput("fieldType")},
|
||||
field: {value:"payload",
|
||||
label:"payload",
|
||||
validate:RED.validators.typedInput("fieldType", false)},
|
||||
fieldType: {value:"msg"},
|
||||
format: {value:"handlebars"},
|
||||
syntax: {value:"mustache"},
|
||||
@@ -73,7 +75,8 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
const that = this;
|
||||
const stateId = RED.editor.generateViewStateId("node", this, "");
|
||||
if (!this.field) {
|
||||
this.field = 'payload';
|
||||
$("#node-input-field").val("payload");
|
||||
@@ -90,10 +93,10 @@
|
||||
types: ['msg','flow','global'],
|
||||
typeField: $("#node-input-fieldType")
|
||||
});
|
||||
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-template-editor',
|
||||
mode: 'ace/mode/html',
|
||||
stateId: stateId,
|
||||
value: $("#node-input-template").val()
|
||||
});
|
||||
RED.library.create({
|
||||
@@ -103,7 +106,6 @@
|
||||
fields:['name','format','output','syntax'],
|
||||
ext: "txt"
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
$("#node-input-format").on("change", function() {
|
||||
var mod = "ace/mode/"+$("#node-input-format").val();
|
||||
@@ -113,20 +115,22 @@
|
||||
});
|
||||
});
|
||||
RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand"));
|
||||
$("#node-template-expand-editor").on("click", function(e) {
|
||||
$("#node-template-expand-editor").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
var value = that.editor.getValue();
|
||||
const value = that.editor.getValue();
|
||||
that.editor.saveView();
|
||||
RED.editor.editText({
|
||||
mode: $("#node-input-format").val(),
|
||||
value: value,
|
||||
stateId: stateId,
|
||||
width: "Infinity",
|
||||
cursor: that.editor.getCursorPosition(),
|
||||
complete: function(v,cursor) {
|
||||
focus: true,
|
||||
complete: function (v, cursor) {
|
||||
that.editor.setValue(v, -1);
|
||||
that.editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
that.editor.restoreView();
|
||||
that.editor.focus();
|
||||
},300);
|
||||
}, 250);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -111,14 +111,54 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pauseType: {value:"delay", required:true},
|
||||
timeout: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
timeout: {
|
||||
value:"5", required:true,
|
||||
label:RED._("node-red:delay.label.delay"),
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-timeout");
|
||||
}},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
nbRateUnits: {value:"1", required:false,
|
||||
validate:function(v) { return v === undefined || (RED.validators.number(v) && (v >= 0)); }},
|
||||
rate: {
|
||||
value:"1",
|
||||
required:true,
|
||||
label:RED._("node-red:delay.label.rate"),
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-rate");
|
||||
}
|
||||
},
|
||||
nbRateUnits: {
|
||||
value:"1",
|
||||
required:false,
|
||||
validate:function(v,opt) {
|
||||
if (v === undefined || (RED.validators.number(v) && (v >= 0))) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-rate-unit");
|
||||
}
|
||||
},
|
||||
rateUnits: {value: "second"},
|
||||
randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
randomFirst: {
|
||||
value:"1", required:true,
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-random-first");
|
||||
}},
|
||||
randomLast: {
|
||||
value:"5", required:true,
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-random-last");
|
||||
}},
|
||||
randomUnits: {value: "seconds"},
|
||||
drop: {value:false},
|
||||
allowrate: {value:false},
|
||||
|
||||
@@ -87,17 +87,25 @@
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
op1: {value:"1", validate: RED.validators.typedInput("op1type")},
|
||||
op2: {value:"0", validate: RED.validators.typedInput("op2type")},
|
||||
op1: {value:"1",
|
||||
label: RED._("node-red:trigger.send"),
|
||||
validate: RED.validators.typedInput("op1type", false)},
|
||||
op2: {value:"0",
|
||||
label: RED._("node-red:trigger.then-send"),
|
||||
validate: RED.validators.typedInput("op2type", false)},
|
||||
op1type: {value:"val"},
|
||||
op2type: {value:"val"},
|
||||
duration: {value:"250",required:true,validate:RED.validators.number()},
|
||||
duration: {
|
||||
value:"250", required:true,
|
||||
label:RED._("node-red:trigger.label.duration"),
|
||||
validate:RED.validators.number(false)},
|
||||
extend: {value:"false"},
|
||||
overrideDelay: {value:"false"},
|
||||
units: {value:"ms"},
|
||||
reset: {value:""},
|
||||
bytopic: {value:"all"},
|
||||
topic: {value:"topic",required:true},
|
||||
topic: {value:"topic", required:true,
|
||||
label:RED._("node-red:trigger.label.topic")},
|
||||
outputs: {value:1}
|
||||
},
|
||||
inputs:1,
|
||||
|
||||
@@ -49,12 +49,16 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"rbe"},
|
||||
gap: {value:"",validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)},
|
||||
gap: {value:"",
|
||||
label: RED._("node-red:rbe.label.gap"),
|
||||
validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)},
|
||||
start: {value:""},
|
||||
inout: {value:"out"},
|
||||
septopics: {value:true},
|
||||
property: {value:"payload",required:true},
|
||||
topi: {value:"topic",required:true}
|
||||
property: {value:"payload", required:true,
|
||||
label:RED._("node-red:rbe.label.property")},
|
||||
topi: {value:"topic", required:true,
|
||||
label:RED._("node-red:rbe.label.topic")}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
|
||||
@@ -83,19 +83,25 @@
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
cert: {value:"", validate: function(v) {
|
||||
cert: {value:"", validate: function(v,opt) {
|
||||
var currentKey = $("#node-config-input-key").val();
|
||||
if (currentKey === undefined) {
|
||||
currentKey = this.key;
|
||||
}
|
||||
return currentKey === '' || v != '';
|
||||
if (currentKey === '' || v != '') {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:tls.error.invalid-cert");
|
||||
}},
|
||||
key: {value:"", validate: function(v) {
|
||||
key: {value:"", validate: function(v,opt) {
|
||||
var currentCert = $("#node-config-input-cert").val();
|
||||
if (currentCert === undefined) {
|
||||
currentCert = this.cert;
|
||||
}
|
||||
return currentCert === '' || v != '';
|
||||
if (currentCert === '' || v != '') {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:tls.error.invalid-key");
|
||||
}},
|
||||
ca: {value:""},
|
||||
certname: {value:""},
|
||||
|
||||
@@ -50,7 +50,16 @@
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: {value:''},
|
||||
url: {value:'',validate:function(v) { return (v && (v.indexOf('://') !== -1) && (v.trim().indexOf('http') === 0)); }},
|
||||
url: {
|
||||
value:'',
|
||||
validate:function(v, opt) {
|
||||
if ((v && (v.indexOf('://') !== -1) &&
|
||||
(v.trim().indexOf('http') === 0))) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:httpin.errors.invalid-url");
|
||||
}
|
||||
},
|
||||
noproxy: {value:[]}
|
||||
},
|
||||
credentials: {
|
||||
|
||||
@@ -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> </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">
|
||||
@@ -442,40 +452,62 @@
|
||||
}
|
||||
return defaultContentType || 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a topic string is valid for publishing
|
||||
* @param {string} topic
|
||||
* @returns `true` if it is a valid topic
|
||||
*/
|
||||
function validateMQTTPublishTopic(topic, opts) {
|
||||
if(!topic || topic == "" || !/[\+#\b\f\n\r\t\v\0]/.test(topic)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:mqtt.errors.invalid-topic");
|
||||
}
|
||||
RED.nodes.registerType('mqtt-broker',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
broker: {value:"",required:true},
|
||||
port: {value:1883,required:false,validate:RED.validators.number(true)},
|
||||
tls: {type:"tls-config",required: false},
|
||||
clientid: {value:"", validate: function(v) {
|
||||
port: {
|
||||
value:1883,required:false,
|
||||
label: RED._("node-red:mqtt.label.port"),
|
||||
validate:RED.validators.number(true)},
|
||||
tls: {type:"tls-config",required: false,
|
||||
label:RED._("node-red:mqtt.label.use-tls") },
|
||||
clientid: {value:"", validate: function(v, opt) {
|
||||
var ok = false;
|
||||
if ($("#node-config-input-clientid").length) {
|
||||
// Currently editing the node
|
||||
return $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
|
||||
ok = $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
|
||||
} else {
|
||||
return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
|
||||
ok = (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
|
||||
}
|
||||
if (ok) {
|
||||
return ok;
|
||||
}
|
||||
return RED._("node-red:mqtt.errors.invalid-client-id");
|
||||
}},
|
||||
autoConnect: {value: true},
|
||||
usetls: {value: false},
|
||||
verifyservercert: { value: false},
|
||||
compatmode: { value: false},
|
||||
protocolVersion: { value: 4},
|
||||
keepalive: {value:60,validate:RED.validators.number()},
|
||||
keepalive: {
|
||||
value:60,
|
||||
label: RED._("node-red:mqtt.label.keepalive"),
|
||||
validate:RED.validators.number(false)},
|
||||
cleansession: {value: true},
|
||||
birthTopic: {value:""},
|
||||
birthTopic: {value:"", validate:validateMQTTPublishTopic},
|
||||
birthQos: {value:"0"},
|
||||
birthRetain: {value:false},
|
||||
birthPayload: {value:""},
|
||||
birthMsg: { value: {}},
|
||||
closeTopic: {value:""},
|
||||
closeTopic: {value:"", validate:validateMQTTPublishTopic},
|
||||
closeQos: {value:"0"},
|
||||
closeRetain: {value:false},
|
||||
closePayload: {value:""},
|
||||
closeMsg: { value: {}},
|
||||
willTopic: {value:""},
|
||||
willTopic: {value:"", validate:validateMQTTPublishTopic},
|
||||
willQos: {value:"0"},
|
||||
willRetain: {value:false},
|
||||
willPayload: {value:""},
|
||||
@@ -750,18 +782,21 @@
|
||||
name: {value:""},
|
||||
topic: {
|
||||
value:"",
|
||||
validate: function(v) {
|
||||
validate: function(v, opt) {
|
||||
var isDynamic = this.inputs === 1;
|
||||
var topicTypeSelect = $("#node-input-topicType");
|
||||
if (topicTypeSelect.length) {
|
||||
isDynamic = topicTypeSelect.val()==='dynamic'
|
||||
}
|
||||
return isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v));
|
||||
if (isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v))) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:mqtt.errors.invalid-topic");
|
||||
}
|
||||
},
|
||||
qos: {value: "2"},
|
||||
datatype: {value:"auto",required:true},
|
||||
broker: {type:"mqtt-broker", required:true},
|
||||
datatype: {value:"auto-detect",required:true},
|
||||
broker: {type:"mqtt-broker", required:true, label:RED._("node-red:mqtt.label.broker")},
|
||||
// subscriptionIdentifier: {value:0},
|
||||
nl: {value:false},
|
||||
rap: {value:true},
|
||||
@@ -797,6 +832,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();
|
||||
});
|
||||
@@ -817,7 +862,7 @@
|
||||
$("#node-input-qos").val("2");
|
||||
}
|
||||
if (this.datatype === undefined) {
|
||||
$("#node-input-datatype").val("auto");
|
||||
$("#node-input-datatype").val("auto-detect");
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
@@ -831,7 +876,7 @@
|
||||
category: 'network',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
topic: {value:"", validate:validateMQTTPublishTopic},
|
||||
qos: {value:""},
|
||||
retain: {value:""},
|
||||
respTopic: {value:""},
|
||||
@@ -839,7 +884,8 @@
|
||||
userProps: {value:''},
|
||||
correl: {value:''},
|
||||
expiry: {value:''},
|
||||
broker: {type:"mqtt-broker", required:true}
|
||||
broker: {type:"mqtt-broker", required:true,
|
||||
label:RED._("node-red:mqtt.label.broker") }
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
inputs:1,
|
||||
|
||||
@@ -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 == "#") {
|
||||
@@ -68,12 +91,21 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a topic string is valid
|
||||
* Test a topic string is valid for subscription
|
||||
* @param {string} topic
|
||||
* @returns `true` if it is a valid topic
|
||||
*/
|
||||
function isValidSubscriptionTopic(topic) {
|
||||
return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic)
|
||||
return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a topic string is valid for publishing
|
||||
* @param {string} topic
|
||||
* @returns `true` if it is a valid topic
|
||||
*/
|
||||
function isValidPublishTopic(topic) {
|
||||
return !/[\+#\b\f\n\r\t\v\0]/.test(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,24 +220,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 +230,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;
|
||||
}
|
||||
@@ -264,38 +349,15 @@ module.exports = function(RED) {
|
||||
msg.messageExpiryInterval = node.messageExpiryInterval;
|
||||
}
|
||||
}
|
||||
if (msg.userProperties && typeof msg.userProperties !== "object") {
|
||||
delete msg.userProperties;
|
||||
}
|
||||
if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && (msg.topicAlias === 0 || bsp.topicAliasMaximum === 0 || msg.topicAlias > bsp.topicAliasMaximum)) {
|
||||
delete msg.topicAlias;
|
||||
}
|
||||
|
||||
if (hasProperty(msg, "payload")) {
|
||||
|
||||
//check & sanitise topic
|
||||
let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (msg.topic !== "");
|
||||
|
||||
if (!topicOK && v5) {
|
||||
//NOTE: A value of 0 (in server props topicAliasMaximum) indicates that the Server does not accept any Topic Aliases on this connection
|
||||
if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && msg.topicAlias >= 0 && bsp.topicAliasMaximum && bsp.topicAliasMaximum >= msg.topicAlias) {
|
||||
topicOK = true;
|
||||
msg.topic = ""; //must be empty string
|
||||
} else if (hasProperty(msg, "responseTopic") && (typeof msg.responseTopic === "string") && (msg.responseTopic !== "")) {
|
||||
//TODO: if topic is empty but responseTopic has a string value, use that instead. Is this desirable?
|
||||
topicOK = true;
|
||||
msg.topic = msg.responseTopic;
|
||||
//TODO: delete msg.responseTopic - to prevent it being resent?
|
||||
// send the message
|
||||
node.brokerConn.publish(msg, function(err) {
|
||||
if(err && err.warn) {
|
||||
node.warn(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
topicOK = topicOK && !/[\+#\b\f\n\r\t\v\0]/.test(msg.topic);
|
||||
|
||||
if (topicOK) {
|
||||
node.brokerConn.publish(msg, done); // send the message
|
||||
} else {
|
||||
node.warn(RED._("mqtt.errors.invalid-topic"));
|
||||
done();
|
||||
}
|
||||
done(err);
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@@ -362,7 +424,7 @@ module.exports = function(RED) {
|
||||
node.brokerConn.connect(function () {
|
||||
done();
|
||||
});
|
||||
}, true)
|
||||
})
|
||||
} else {
|
||||
// Without force flag, we will refuse to cycle an active connection
|
||||
done(new Error(RED._('mqtt.errors.invalid-action-alreadyconnected')));
|
||||
@@ -754,35 +816,48 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
};
|
||||
node.disconnect = function (callback, force) {
|
||||
const _callback = function (resetNodeConnectedState, _force) {
|
||||
setStatusDisconnected(node, true);
|
||||
if(resetNodeConnectedState || _force) {
|
||||
node.client.removeAllListeners();
|
||||
node.closing = true;
|
||||
node.connecting = false;
|
||||
node.connected = false;
|
||||
|
||||
node.disconnect = function (callback) {
|
||||
const _callback = function () {
|
||||
if(node.connected || node.connecting) {
|
||||
setStatusDisconnected(node, true);
|
||||
}
|
||||
if(node.client) { node.client.removeAllListeners(); }
|
||||
node.connecting = false;
|
||||
node.connected = false;
|
||||
callback && typeof callback == "function" && callback();
|
||||
};
|
||||
if(node.closing) {
|
||||
return _callback(false, force);
|
||||
}
|
||||
var endCallBack = function endCallBack() {
|
||||
}
|
||||
if(!node.client) { return _callback(); }
|
||||
if(node.closing) { return _callback(); }
|
||||
|
||||
let waitEnd = (client, ms) => {
|
||||
return new Promise( (resolve, reject) => {
|
||||
node.closing = true;
|
||||
if(!client) {
|
||||
resolve();
|
||||
} else {
|
||||
const t = setTimeout(reject, ms);
|
||||
client.end(() => {
|
||||
clearTimeout(t);
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if(node.connected && node.closeMessage) {
|
||||
node.publish(node.closeMessage, function (err) {
|
||||
node.client.end(endCallBack);
|
||||
_callback(true, force);
|
||||
waitEnd(node.client, 2000).then(() => {
|
||||
_callback();
|
||||
}).catch((e) => {
|
||||
_callback();
|
||||
})
|
||||
});
|
||||
} else if(node.connected) {
|
||||
node.client.end(endCallBack);
|
||||
_callback(true, force);
|
||||
} else if(node.connecting) {
|
||||
node.client.end();
|
||||
_callback(true, true);
|
||||
} else {
|
||||
_callback(false, force);
|
||||
waitEnd(node.client, 2000).then(() => {
|
||||
_callback();
|
||||
}).catch((e) => {
|
||||
_callback();
|
||||
})
|
||||
}
|
||||
}
|
||||
node.subscriptionIds = {};
|
||||
@@ -864,8 +939,18 @@ module.exports = function(RED) {
|
||||
qos: msg.qos || 0,
|
||||
retain: msg.retain || false
|
||||
};
|
||||
let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
|
||||
//https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
|
||||
if(node.options.protocolVersion == 5) {
|
||||
const bsp = node.serverProperties || {};
|
||||
if (msg.userProperties && typeof msg.userProperties !== "object") {
|
||||
delete msg.userProperties;
|
||||
}
|
||||
if (hasProperty(msg, "topicAlias") && !isNaN(Number(msg.topicAlias))) {
|
||||
msg.topicAlias = parseInt(msg.topicAlias);
|
||||
} else {
|
||||
delete msg.topicAlias;
|
||||
}
|
||||
options.properties = options.properties || {};
|
||||
setStrProp(msg, options.properties, "responseTopic");
|
||||
setBufferProp(msg, options.properties, "correlationData");
|
||||
@@ -875,29 +960,46 @@ module.exports = function(RED) {
|
||||
setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
|
||||
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
||||
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
||||
if (options.properties.topicAlias) {
|
||||
if (!node.topicAliases.hasOwnProperty(options.properties.topicAlias) && msg.topic == "") {
|
||||
|
||||
//check & sanitise topic
|
||||
if (topicOK && options.properties.topicAlias) {
|
||||
let aliasValid = (bsp.topicAliasMaximum && bsp.topicAliasMaximum >= options.properties.topicAlias);
|
||||
if (!aliasValid) {
|
||||
done("Invalid topicAlias");
|
||||
return
|
||||
}
|
||||
if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
|
||||
msg.topic = ""
|
||||
msg.topic = "";
|
||||
} else {
|
||||
node.topicAliases[options.properties.topicAlias] = msg.topic
|
||||
node.topicAliases[options.properties.topicAlias] = msg.topic;
|
||||
}
|
||||
} else if (!msg.topic && options.properties.responseTopic) {
|
||||
msg.topic = msg.responseTopic;
|
||||
topicOK = isValidPublishTopic(msg.topic);
|
||||
delete msg.responseTopic; //prevent responseTopic being resent?
|
||||
}
|
||||
}
|
||||
|
||||
node.client.publish(msg.topic, msg.payload, options, function(err) {
|
||||
done && done(err);
|
||||
return
|
||||
});
|
||||
if (topicOK) {
|
||||
node.client.publish(msg.topic, msg.payload, options, function(err) {
|
||||
done && done(err);
|
||||
return
|
||||
});
|
||||
} else {
|
||||
const error = new Error(RED._("mqtt.errors.invalid-topic"));
|
||||
error.warn = true;
|
||||
done(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.on('close', function(done) {
|
||||
node.closing = true;
|
||||
node.disconnect(done);
|
||||
node.disconnect(function() {
|
||||
if(node.client) {
|
||||
node.client.removeAllListeners();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1074,6 +1176,7 @@ module.exports = function(RED) {
|
||||
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
||||
}
|
||||
node.brokerConn.deregister(node, done);
|
||||
node.brokerConn = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@@ -1138,6 +1241,7 @@ module.exports = function(RED) {
|
||||
node.on('close', function(done) {
|
||||
if (node.brokerConn) {
|
||||
node.brokerConn.deregister(node,done);
|
||||
node.brokerConn = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
url: {value:"",required:true},
|
||||
url: {value:"", required:true,
|
||||
label:RED._("node-red:httpin.label.url")},
|
||||
method: {value:"get",required:true},
|
||||
upload: {value:false},
|
||||
swaggerDoc: {type:"swagger-doc", required:false}
|
||||
@@ -146,7 +147,10 @@
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
statusCode: {value:"",validate: RED.validators.number(true)},
|
||||
statusCode: {
|
||||
value:"",
|
||||
label: RED._("node-red:httpin.label.status"),
|
||||
validate: RED.validators.number(true)},
|
||||
headers: {value:{}}
|
||||
},
|
||||
inputs:1,
|
||||
|
||||
@@ -100,14 +100,107 @@
|
||||
<option value="obj" data-i18n="httpin.json"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
|
||||
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-headers-container-row">
|
||||
<ol id="node-input-headers-container"></ol>
|
||||
</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">
|
||||
</div>
|
||||
<div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
const headerTypes = [
|
||||
{ value: "Accept", label: "Accept", hasValue: false },
|
||||
{ value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
|
||||
{ value: "Accept-Language", label: "Accept-Language", hasValue: false },
|
||||
{ value: "Authorization", label: "Authorization", hasValue: false },
|
||||
{ value: "Content-Type", label: "Content-Type", hasValue: false },
|
||||
{ value: "Cache-Control", label: "Cache-Control", hasValue: false },
|
||||
{ value: "User-Agent", label: "User-Agent", hasValue: false },
|
||||
{ value: "Location", label: "Location", hasValue: false },
|
||||
{ value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" },
|
||||
{ value: "msg", label: "msg.", hasValue: true },
|
||||
]
|
||||
const headerOptions = {};
|
||||
const defaultOptions = [
|
||||
{ value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" },
|
||||
{ value: "msg", label: "msg.", hasValue: true },
|
||||
];
|
||||
headerOptions["accept"] = [
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["accept-encoding"] = [
|
||||
{ value: "gzip", label: "gzip", hasValue: false },
|
||||
{ value: "deflate", label: "deflate", hasValue: false },
|
||||
{ value: "compress", label: "compress", hasValue: false },
|
||||
{ value: "br", label: "br", hasValue: false },
|
||||
{ value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
|
||||
{ value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["accept-language"] = [
|
||||
{ value: "*", label: "*", hasValue: false },
|
||||
{ value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
|
||||
{ value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
|
||||
{ value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
|
||||
{ value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
|
||||
{ value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
|
||||
{ value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["content-type"] = [
|
||||
{ value: "text/css", label: "text/css", hasValue: false },
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
|
||||
{ value: "application/pdf", label: "application/pdf", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
{ value: "application/zip", label: "application/zip", hasValue: false },
|
||||
{ value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
|
||||
{ value: "audio/aac", label: "audio/aac", hasValue: false },
|
||||
{ value: "audio/ac3", label: "audio/ac3", hasValue: false },
|
||||
{ value: "audio/basic", label: "audio/basic", hasValue: false },
|
||||
{ value: "audio/mp4", label: "audio/mp4", hasValue: false },
|
||||
{ value: "audio/ogg", label: "audio/ogg", hasValue: false },
|
||||
{ value: "image/bmp", label: "image/bmp", hasValue: false },
|
||||
{ value: "image/gif", label: "image/gif", hasValue: false },
|
||||
{ value: "image/jpeg", label: "image/jpeg", hasValue: false },
|
||||
{ value: "image/png", label: "image/png", hasValue: false },
|
||||
{ value: "image/tiff", label: "image/tiff", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["cache-control"] = [
|
||||
{ value: "max-age=0", label: "max-age=0", hasValue: false },
|
||||
{ value: "max-age=86400", label: "max-age=86400", hasValue: false },
|
||||
{ value: "no-cache", label: "no-cache", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
headerOptions["user-agent"] = [
|
||||
{ value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
function getHeaderOptions(headerName) {
|
||||
const lc = (headerName || "").toLowerCase();
|
||||
let opts = headerOptions[lc];
|
||||
return opts || defaultOptions;
|
||||
}
|
||||
|
||||
RED.nodes.registerType('http request',{
|
||||
category: 'network',
|
||||
color:"rgb(231, 231, 174)",
|
||||
@@ -116,12 +209,25 @@
|
||||
method:{value:"GET"},
|
||||
ret: {value:"txt"},
|
||||
paytoqs: {value: false},
|
||||
url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} },
|
||||
tls: {type:"tls-config",required: false},
|
||||
url:{
|
||||
value:"",
|
||||
validate: function(v, opt) {
|
||||
if ((v.trim().length === 0) ||
|
||||
(v.indexOf("://") === -1) ||
|
||||
(v.trim().indexOf("http") === 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:httpin.errors.invalid-url");
|
||||
}
|
||||
},
|
||||
tls: {type:"tls-config",required: false,
|
||||
label:RED._("node-red:httpin.tls-config") },
|
||||
persist: {value:false},
|
||||
proxy: {type:"http proxy",required: false},
|
||||
proxy: {type:"http proxy",required: false,
|
||||
label:RED._("node-red:httpin.proxy-config") },
|
||||
authType: {value: ""},
|
||||
senderr: {value: false}
|
||||
senderr: {value: false},
|
||||
headers: { value: [] }
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
@@ -144,6 +250,7 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
const node = this;
|
||||
$("#node-input-useAuth").on("change", function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$(".node-input-useAuth-row").show();
|
||||
@@ -157,9 +264,10 @@
|
||||
$('#node-input-user').val('');
|
||||
$('#node-input-password').val('');
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
$("#node-input-authType-select").on("change", function() {
|
||||
var val = $(this).val();
|
||||
const val = $(this).val();
|
||||
$("#node-input-authType").val(val);
|
||||
if (val === "basic" || val === "digest") {
|
||||
$(".node-input-basic-row").show();
|
||||
@@ -171,6 +279,7 @@
|
||||
$('#node-span-token').show();
|
||||
$('#node-input-user').val('');
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
$("#node-input-method").on("change", function() {
|
||||
if ($(this).val() == "GET") {
|
||||
@@ -178,17 +287,18 @@
|
||||
} else {
|
||||
$(".node-input-paytoqs-row").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
if (this.paytoqs === true || this.paytoqs == "query") {
|
||||
if (node.paytoqs === true || node.paytoqs == "query") {
|
||||
$("#node-input-paytoqs").val("query");
|
||||
} else if (this.paytoqs === "body") {
|
||||
} else if (node.paytoqs === "body") {
|
||||
$("#node-input-paytoqs").val("body");
|
||||
} else {
|
||||
$("#node-input-paytoqs").val("ignore");
|
||||
}
|
||||
if (this.authType) {
|
||||
if (node.authType) {
|
||||
$('#node-input-useAuth').prop('checked', true);
|
||||
$("#node-input-authType-select").val(this.authType);
|
||||
$("#node-input-authType-select").val(node.authType);
|
||||
$("#node-input-authType-select").change();
|
||||
} else {
|
||||
$('#node-input-useAuth').prop('checked', false);
|
||||
@@ -201,8 +311,9 @@
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
if (this.tls) {
|
||||
if (node.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
@@ -218,8 +329,9 @@
|
||||
} else {
|
||||
$("#node-input-useProxy-row").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
if (this.proxy) {
|
||||
if (node.proxy) {
|
||||
$("#node-input-useProxy").prop("checked", true);
|
||||
} else {
|
||||
$("#node-input-useProxy").prop("checked", false);
|
||||
@@ -235,7 +347,70 @@
|
||||
} else {
|
||||
$("#tip-json").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
const hasMatch = function (arr, value) {
|
||||
return arr.some(function (ht) {
|
||||
return ht.value === value
|
||||
});
|
||||
}
|
||||
const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
||||
addItem: function (container, i, header) {
|
||||
const row = $('<div/>').css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex'
|
||||
}).appendTo(container);
|
||||
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
|
||||
const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertNameCell)
|
||||
.typedInput({ types: headerTypes });
|
||||
|
||||
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
|
||||
const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertyValueCell)
|
||||
.typedInput({
|
||||
types: getHeaderOptions(header.keyType)
|
||||
});
|
||||
|
||||
const setup = function(_header) {
|
||||
const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
|
||||
const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
|
||||
const {keyType, keyValue, valueType, valueValue} = header;
|
||||
if(keyType == "msg" || keyType == "other") {
|
||||
propertyName.typedInput('type', keyType);
|
||||
propertyName.typedInput('value', keyValue);
|
||||
} else if (headerTypeIsAPreset(keyType)) {
|
||||
propertyName.typedInput('type', keyType);
|
||||
} else {
|
||||
propertyName.typedInput('type', "other");
|
||||
propertyName.typedInput('value', keyValue);
|
||||
}
|
||||
if(valueType == "msg" || valueType == "other") {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
} else {
|
||||
propertyValue.typedInput('type', "other");
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
}
|
||||
}
|
||||
setup(header);
|
||||
|
||||
propertyName.on('change', function (event) {
|
||||
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
|
||||
});
|
||||
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
if (node.headers) {
|
||||
for (let index = 0; index < node.headers.length; index++) {
|
||||
const element = node.headers[index];
|
||||
headerList.editableList('addItem', node.headers[index]);
|
||||
}
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
@@ -244,6 +419,36 @@
|
||||
if (!$("#node-input-useProxy").is(":checked")) {
|
||||
$("#node-input-proxy").val("_ADD_");
|
||||
}
|
||||
const headers = $("#node-input-headers-container").editableList('items');
|
||||
const node = this;
|
||||
node.headers = [];
|
||||
headers.each(function(i) {
|
||||
const header = $(this);
|
||||
const keyType = header.find(".node-input-header-name").typedInput('type');
|
||||
const keyValue = header.find(".node-input-header-name").typedInput('value');
|
||||
const valueType = header.find(".node-input-header-value").typedInput('type');
|
||||
const valueValue = header.find(".node-input-header-value").typedInput('value');
|
||||
if (keyType !== '' || keyType === 'other' || keyType === 'msg') {
|
||||
node.headers.push({
|
||||
keyType, keyValue, valueType, valueValue
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
const dlg = $("#dialog-form");
|
||||
const expandRow = dlg.find('.node-input-headers-container-row');
|
||||
let height = dlg.height() - 5;
|
||||
if(expandRow && expandRow.length){
|
||||
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
|
||||
for (let i = 0; i < siblingRows.size(); i++) {
|
||||
const cr = $(siblingRows[i]);
|
||||
if(cr.is(":visible"))
|
||||
height -= cr.outerHeight(true);
|
||||
}
|
||||
$("#node-input-headers-container").editableList('height',height);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -73,7 +73,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
|
||||
node.headers = n.headers || [];
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
@@ -105,6 +105,37 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
timingLog = RED.settings.httpRequestTimingLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case insensitive header value update util function
|
||||
* @param {object} headersObject The opt.headers object to update
|
||||
* @param {string} name The header name
|
||||
* @param {string} value The header value to set (if blank, header is removed)
|
||||
*/
|
||||
const updateHeader = function(headersObject, name, value ) {
|
||||
const hn = name.toLowerCase();
|
||||
const keys = Object.keys(headersObject);
|
||||
const matchingKeys = keys.filter(e => e.toLowerCase() == hn)
|
||||
const updateKey = (k,v) => {
|
||||
delete headersObject[k]; //delete incase of case change
|
||||
if(v) { headersObject[name] = v } //re-add with requested name & value
|
||||
}
|
||||
if(matchingKeys.length == 0) {
|
||||
updateKey(name, value)
|
||||
} else {
|
||||
matchingKeys.forEach(k => {
|
||||
updateKey(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Object} headersObject
|
||||
* @param {string} name
|
||||
* @return {any} value
|
||||
*/
|
||||
const getHeaderValue = (headersObject, name) => {
|
||||
const asLowercase = name.toLowercase();
|
||||
return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
|
||||
}
|
||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||
checkNodeAgentPatch();
|
||||
//reset redirectList on each request
|
||||
@@ -183,7 +214,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
||||
opts.decompress = false;
|
||||
opts.method = method;
|
||||
opts.headers = {};
|
||||
opts.retry = 0;
|
||||
opts.responseType = 'buffer';
|
||||
opts.maxRedirects = 21;
|
||||
@@ -229,34 +259,85 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
]
|
||||
}
|
||||
|
||||
var ctSet = "Content-Type"; // set default camel case
|
||||
var clSet = "Content-Length";
|
||||
let ctSet = "Content-Type"; // set default camel case
|
||||
let clSet = "Content-Length";
|
||||
const normaliseKnownHeader = function (name) {
|
||||
const _name = name.toLowerCase();
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
switch (_name) {
|
||||
case "content-type":
|
||||
ctSet = name;
|
||||
name = _name;
|
||||
break;
|
||||
case "content-length":
|
||||
clSet = name;
|
||||
name = _name;
|
||||
break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
opts.headers = {};
|
||||
//add msg.headers
|
||||
//NOTE: ui headers will take precidence over msg.headers
|
||||
if (msg.headers) {
|
||||
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
|
||||
var headerHash = msg.headers['x-node-red-request-node'];
|
||||
const headerHash = msg.headers['x-node-red-request-node'];
|
||||
delete msg.headers['x-node-red-request-node'];
|
||||
var hash = hashSum(msg.headers);
|
||||
const hash = hashSum(msg.headers);
|
||||
if (hash === headerHash) {
|
||||
delete msg.headers;
|
||||
}
|
||||
}
|
||||
if (msg.headers) {
|
||||
for (var v in msg.headers) {
|
||||
if (msg.headers.hasOwnProperty(v)) {
|
||||
var name = v.toLowerCase();
|
||||
if (name !== "content-type" && name !== "content-length") {
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
name = v;
|
||||
}
|
||||
else if (name === 'content-type') { ctSet = v; }
|
||||
else { clSet = v; }
|
||||
opts.headers[name] = msg.headers[v];
|
||||
}
|
||||
for (let hn in msg.headers) {
|
||||
const name = normaliseKnownHeader(hn);
|
||||
updateHeader(opts.headers, name, msg.headers[hn]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//add/remove/update headers from UI.
|
||||
if (node.headers.length) {
|
||||
for (let index = 0; index < node.headers.length; index++) {
|
||||
const header = node.headers[index];
|
||||
let headerName, headerValue;
|
||||
if (header.keyType === "other") {
|
||||
headerName = header.keyValue
|
||||
} else if (header.keyType === "msg") {
|
||||
RED.util.evaluateNodeProperty(header.keyValue, header.keyType, node, msg, (err, value) => {
|
||||
if (err) {
|
||||
//ignore header
|
||||
} else {
|
||||
headerName = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
headerName = header.keyType
|
||||
}
|
||||
if (!headerName) {
|
||||
continue; //skip if header name is empyy
|
||||
}
|
||||
if (header.valueType === "other") {
|
||||
headerValue = header.valueValue
|
||||
} else if (header.valueType === "msg") {
|
||||
RED.util.evaluateNodeProperty(header.valueValue, header.valueType, node, msg, (err, value) => {
|
||||
if (err) {
|
||||
//ignore header
|
||||
} else {
|
||||
headerValue = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
headerValue = header.valueType
|
||||
}
|
||||
const hn = normaliseKnownHeader(headerName);
|
||||
updateHeader(opts.headers, hn, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (msg.hasOwnProperty('followRedirects')) {
|
||||
opts.followRedirect = !!msg.followRedirects;
|
||||
}
|
||||
|
||||
@@ -83,13 +83,19 @@
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return RED.nodes.node(this.server) != null;
|
||||
if (RED.nodes.node(this.server) != null) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:websocket.errors.missing-server");
|
||||
}
|
||||
}
|
||||
|
||||
function ws_validateclient() {
|
||||
if ($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return RED.nodes.node(this.client) != null;
|
||||
if (RED.nodes.node(this.client) != null) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:websocket.errors.missing-client");
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
@@ -138,7 +144,9 @@
|
||||
RED.nodes.registerType('websocket-listener',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
path: {value:"",required:true,
|
||||
label:RED._("node-red:websocket.label.path"),
|
||||
validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
@@ -174,10 +182,16 @@
|
||||
RED.nodes.registerType('websocket-client',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
path: {
|
||||
value:"",required:true,
|
||||
label:RED._("node-red:websocket.label.path"),
|
||||
validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
tls: {type:"tls-config",required: false},
|
||||
wholemsg: {value:"false"},
|
||||
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) },
|
||||
hb: {
|
||||
value: "",
|
||||
label:RED._("node-red:websocket.sendheartbeat"),
|
||||
validate: RED.validators.number(/*blank allowed*/true) },
|
||||
subprotocol: {value:"",required: false}
|
||||
},
|
||||
inputs:0,
|
||||
|
||||
@@ -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">
|
||||
@@ -70,14 +71,27 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {value:"server", required:true},
|
||||
host: {value:"", validate:function(v) { return (this.server == "server")||v.length > 0;} },
|
||||
port: {value:"", required:true, validate:RED.validators.number()},
|
||||
host: {
|
||||
value:"",
|
||||
validate:function(v, opt) {
|
||||
if ((this.server == "server")||v.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:tcpin.errors.invalid-host");
|
||||
}
|
||||
},
|
||||
port: {
|
||||
value:"", required:true,
|
||||
label:RED._("node-red:tcpin.label.port"),
|
||||
validate:RED.validators.number(false)},
|
||||
datamode:{value:"stream"},
|
||||
datatype:{value:"buffer"},
|
||||
newline:{value:""},
|
||||
topic: {value:""},
|
||||
trim: {value:false},
|
||||
base64: {/*deprecated*/ value:false, required:true},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
tls: {type:"tls-config", value:'', required:false,
|
||||
label:RED._("node-red:httpin.tls-config") }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
@@ -186,12 +200,29 @@
|
||||
color: "Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
|
||||
port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } },
|
||||
host: {
|
||||
value:"",
|
||||
validate:function(v, opt) {
|
||||
if ((this.beserver != "client")||v.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:tcpin.errors.invalid-host");
|
||||
}
|
||||
},
|
||||
port: {
|
||||
value:"",
|
||||
validate:function(v) {
|
||||
if ((this.beserver == "reply")||RED.validators.number()(v)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:tcpin.errors.invalid-port");
|
||||
}
|
||||
},
|
||||
beserver: {value:"client", required:true},
|
||||
base64: {value:false, required:true},
|
||||
end: {value:false, required:true},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
tls: {type:"tls-config", value:'', required:false,
|
||||
label:RED._("node-red:httpin.tls-config") }
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
@@ -286,7 +317,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>
|
||||
@@ -301,12 +333,17 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {value:""},
|
||||
port: {value:"", validate:RED.validators.regex(/^(\d*|)$/)},
|
||||
port: {
|
||||
value:"",
|
||||
label: RED._("node-red:tcpin.label.port"),
|
||||
validate:RED.validators.regex(/^(\d*|)$/)
|
||||
},
|
||||
out: {value:"time", required:true},
|
||||
ret: {value:"buffer"},
|
||||
splitc: {value:"0", required:true},
|
||||
newline: {value:""},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
trim: {value:false},
|
||||
tls: {type:"tls-config", value:'', required:false, label:RED._("node-red:httpin.tls-config")}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -62,10 +62,22 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:"",required:true,validate:RED.validators.number()},
|
||||
port: {
|
||||
value:"", required:true,
|
||||
label:RED._("node-red:udp.label.port"),
|
||||
validate:RED.validators.number(false)
|
||||
},
|
||||
ipv: {value:"udp4"},
|
||||
multicast: {value:"false"},
|
||||
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} },
|
||||
group: {
|
||||
value:"",
|
||||
validate:function(v,opt) {
|
||||
if ((this.multicast !== "true")||v.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:udp.errors.invalid-group");
|
||||
}
|
||||
},
|
||||
datatype: {value:"buffer",required:true}
|
||||
},
|
||||
inputs:0,
|
||||
|
||||
@@ -75,7 +75,10 @@
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
|
||||
sep: {
|
||||
value:',', required:true,
|
||||
label:RED._("node-red:csv.label.separator"),
|
||||
validate:RED.validators.regex(/^.{1,2}$/)},
|
||||
//quo: {value:'"',required:true},
|
||||
hdrin: {value:""},
|
||||
hdrout: {value:"none"},
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload",required:true},
|
||||
property: {value:"payload",required:true,
|
||||
label:RED._("node-red:json.label.property")},
|
||||
action: {value:""},
|
||||
pretty: {value:false}
|
||||
},
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload",required:true},
|
||||
property: {value:"payload",required:true,
|
||||
label:RED._("node-red:common.label.property")},
|
||||
attr: {value:""},
|
||||
chr: {value:""}
|
||||
},
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
property: {value:"payload",required:true},
|
||||
property: {value:"payload",required:true,
|
||||
label:RED._("node-red:common.label.property")},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
|
||||
@@ -202,7 +202,11 @@
|
||||
name: {value:""},
|
||||
mode: {value:"auto"},
|
||||
build: { value:"object"},
|
||||
property: { value:"payload", validate:RED.validators.typedInput("propertyType")},
|
||||
property: {
|
||||
value:"payload",
|
||||
label: RED._("node-red:join.message-prop"),
|
||||
validate:RED.validators.typedInput("propertyType", false)
|
||||
},
|
||||
propertyType: { value:"msg"},
|
||||
key: {value:"topic"},
|
||||
joiner: { value:"\\n"},
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
|
||||
<label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-row-msg-concat">
|
||||
@@ -73,9 +73,33 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
mode: {value:"count"},
|
||||
count: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
|
||||
overlap: {value:0,validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
interval: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
|
||||
count: {
|
||||
value:10,
|
||||
validate:function(v, opt) {
|
||||
if (RED.validators.number(v) && (v >= 1)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:batch.error.invalid-count");
|
||||
}
|
||||
},
|
||||
overlap: {
|
||||
value:0,
|
||||
validate:function(v, opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:batch.error.invalid-overlap");
|
||||
}
|
||||
},
|
||||
interval: {
|
||||
value:10,
|
||||
validate:function(v, opt) {
|
||||
if (RED.validators.number(v) && (v >= 1)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:batch.error.invalid-interval");
|
||||
}
|
||||
},
|
||||
allowEmptySequence: {value:false},
|
||||
topics: {value:[{topic:""}]}
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
|
||||
<input id="node-input-filename" type="text">
|
||||
<input type="hidden" id="node-input-filenameType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
|
||||
@@ -29,7 +30,7 @@
|
||||
</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">
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-tips"><span data-i18n="file.tip"></span></div>
|
||||
</script>
|
||||
@@ -37,7 +38,8 @@
|
||||
<script type="text/html" data-template-name="file in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
|
||||
<input id="node-input-filename" type="text" data-i18n="[placeholder]file.label.filename">
|
||||
<input id="node-input-filename" type="text">
|
||||
<input type="hidden" id="node-input-filenameType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
|
||||
@@ -60,7 +62,7 @@
|
||||
</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">
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-tips"><span data-i18n="file.tip"></span></div>
|
||||
</script>
|
||||
@@ -196,7 +198,8 @@
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
filename: {value:"filename"},
|
||||
filenameType: {value:"msg"},
|
||||
appendNewline: {value:true},
|
||||
createDir: {value:false},
|
||||
overwriteFile: {value:"false"},
|
||||
@@ -207,10 +210,13 @@
|
||||
outputs:1,
|
||||
icon: "file-out.svg",
|
||||
label: function() {
|
||||
var fn = this.filename;
|
||||
if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; }
|
||||
if(this.filenameType === "env") { fn = "env."+fn; }
|
||||
if (this.overwriteFile === "delete") {
|
||||
return this.name||this._("file.label.deletelabel",{file:this.filename});
|
||||
return this.name||this._("file.label.deletelabel",{file:fn});
|
||||
} else {
|
||||
return this.name||this.filename||this._("file.label.write");
|
||||
return this.name||fn||this._("file.label.write");
|
||||
}
|
||||
},
|
||||
paletteLabel: RED._("node-red:file.label.write"),
|
||||
@@ -229,6 +235,31 @@
|
||||
value: "setbymsg",
|
||||
label: node._("file.encoding.setbymsg")
|
||||
}).text(label).appendTo(encSel);
|
||||
$("#node-input-filename").typedInput({
|
||||
default: "msg",
|
||||
types:[{ value: "str", label:"", icon:"red/images/typedInput/az.svg"}, "msg", "jsonata", "env"],
|
||||
typeField: $("#node-input-filenameType")
|
||||
});
|
||||
if(typeof node.filenameType == 'undefined') {
|
||||
//existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput
|
||||
if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match
|
||||
node.filename = "filename";
|
||||
node.filenameType = "msg";
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
} else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR}
|
||||
node.filenameType = "env";
|
||||
node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) {
|
||||
return (name === undefined)?"":name;
|
||||
});
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
} else { //was using a static filename - set typedInput type to str
|
||||
node.filenameType = "str";
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
}
|
||||
}
|
||||
encodings.forEach(function(item) {
|
||||
if(Array.isArray(item)) {
|
||||
var group = $("<optgroup/>", {
|
||||
@@ -266,7 +297,8 @@
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
filename: {value:"filename"},
|
||||
filenameType: {value:"msg"},
|
||||
format: {value:"utf8"},
|
||||
chunk: {value:false},
|
||||
sendError: {value: false},
|
||||
@@ -291,7 +323,10 @@
|
||||
},
|
||||
icon: "file-in.svg",
|
||||
label: function() {
|
||||
return this.name||this.filename||this._("file.label.read");
|
||||
var fn = this.filename;
|
||||
if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; }
|
||||
if(this.filenameType === "env") { fn = "env."+fn; }
|
||||
return this.name||fn||this._("file.label.read");
|
||||
},
|
||||
paletteLabel: RED._("node-red:file.label.read"),
|
||||
labelStyle: function() {
|
||||
@@ -305,6 +340,31 @@
|
||||
value: "none",
|
||||
label: label
|
||||
}).text(label).appendTo(encSel);
|
||||
$("#node-input-filename").typedInput({
|
||||
default: "msg",
|
||||
types:[{ value: "str", label:"", icon:"red/images/typedInput/az.svg"}, "msg", "jsonata", "env"],
|
||||
typeField: $("#node-input-filenameType")
|
||||
});
|
||||
if(typeof node.filenameType == 'undefined') {
|
||||
//existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput
|
||||
if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match
|
||||
node.filename = "filename";
|
||||
node.filenameType = "msg";
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
} else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR}
|
||||
node.filenameType = "env";
|
||||
node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) {
|
||||
return (name === undefined)?"":name;
|
||||
});
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
} else { //was using a static filename - set typedInput type to str
|
||||
node.filenameType = "str";
|
||||
$("#node-input-filename").typedInput("type", node.filenameType);
|
||||
$("#node-input-filename").typedInput("value", node.filename);
|
||||
}
|
||||
}
|
||||
encodings.forEach(function(item) {
|
||||
if(Array.isArray(item)) {
|
||||
var group = $("<optgroup/>", {
|
||||
|
||||
@@ -39,6 +39,7 @@ module.exports = function(RED) {
|
||||
// Write/delete a file
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename;
|
||||
this.filenameType = n.filenameType;
|
||||
this.appendNewline = n.appendNewline;
|
||||
this.overwriteFile = n.overwriteFile.toString();
|
||||
this.createDir = n.createDir || false;
|
||||
@@ -50,7 +51,28 @@ module.exports = function(RED) {
|
||||
node.closeCallback = null;
|
||||
|
||||
function processMsg(msg,nodeSend, done) {
|
||||
var filename = node.filename || msg.filename || "";
|
||||
var filename = node.filename || "";
|
||||
//Pre V3 compatibility - if filenameType is empty, do in place upgrade
|
||||
if(typeof node.filenameType == 'undefined' || node.filenameType == "") {
|
||||
//existing node AND filenameType is not set - inplace (compatible) upgrade
|
||||
if(filename == "") { //was using empty value to denote msg.filename
|
||||
node.filename = "filename";
|
||||
node.filenameType = "msg";
|
||||
} else { //was using a static filename - set typedInput type to str
|
||||
node.filenameType = "str";
|
||||
}
|
||||
}
|
||||
|
||||
RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = value;
|
||||
}
|
||||
});
|
||||
filename = filename || "";
|
||||
msg.filename = filename;
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
@@ -158,7 +180,7 @@ module.exports = function(RED) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
if (node.filename) {
|
||||
if (node.filenameType === "str" || node.filenameType === "env") {
|
||||
// Static filename - write and reuse the stream next time
|
||||
node.wstream.write(buf, function() {
|
||||
nodeSend(msg);
|
||||
@@ -256,6 +278,7 @@ module.exports = function(RED) {
|
||||
// Read a file
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename;
|
||||
this.filenameType = n.filenameType;
|
||||
this.format = n.format;
|
||||
this.chunk = false;
|
||||
this.encoding = n.encoding || "none";
|
||||
@@ -270,8 +293,28 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
|
||||
this.on("input",function(msg, nodeSend, nodeDone) {
|
||||
var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
|
||||
var filename = node.filename || "";
|
||||
//Pre V3 compatibility - if filenameType is empty, do in place upgrade
|
||||
if(typeof node.filenameType == 'undefined' || node.filenameType == "") {
|
||||
//existing node AND filenameType is not set - inplace (compatible) upgrade
|
||||
if(filename == "") { //was using empty value to denote msg.filename
|
||||
node.filename = "filename";
|
||||
node.filenameType = "msg";
|
||||
} else { //was using a static filename - set typedInput type to str
|
||||
node.filenameType = "str";
|
||||
}
|
||||
}
|
||||
RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = (value || "").replace(/\t|\r|\n/g,'');
|
||||
}
|
||||
});
|
||||
filename = filename || "";
|
||||
var fullFilename = filename;
|
||||
var filePath = "";
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
files: {value:"",required:true},
|
||||
files: {value:"",required:true,
|
||||
label:RED._("node-red:watch.label.files")},
|
||||
recursive: {value:""}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
|
||||
@@ -16,24 +16,9 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var Notify = require("fs.notify");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var getAllDirs = function (dir, filelist) {
|
||||
filelist = filelist || [];
|
||||
fs.readdirSync(dir).forEach(file => {
|
||||
try {
|
||||
if (fs.statSync(path.join(dir, file)).isDirectory() ) {
|
||||
filelist.push(path.join(dir, file));
|
||||
getAllDirs(path.join(dir, file), filelist);
|
||||
}
|
||||
} catch (error) {
|
||||
//should we raise an error?
|
||||
}
|
||||
});
|
||||
return filelist;
|
||||
}
|
||||
const watch = require('node-watch')
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
function WatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -44,52 +29,45 @@ module.exports = function(RED) {
|
||||
this.files[f] = this.files[f].trim();
|
||||
}
|
||||
this.p = (this.files.length === 1) ? this.files[0] : JSON.stringify(this.files);
|
||||
var node = this;
|
||||
const node = this;
|
||||
|
||||
if (node.recursive) {
|
||||
for (var fi in node.files) {
|
||||
if (node.files.hasOwnProperty(fi)) {
|
||||
node.files = node.files.concat(getAllDirs( node.files[fi]));
|
||||
}
|
||||
}
|
||||
}
|
||||
const watcher = watch(this.files, { recursive: this.recursive });
|
||||
|
||||
var notifications = new Notify(node.files);
|
||||
notifications.on('change', function (file, event, fpath) {
|
||||
var stat;
|
||||
watcher.on('change', function (event, fpath) {
|
||||
const file = path.basename(fpath)
|
||||
let stat;
|
||||
try {
|
||||
if (fs.statSync(fpath).isDirectory()) { fpath = path.join(fpath,file); }
|
||||
stat = fs.statSync(fpath);
|
||||
} catch(e) { }
|
||||
var type = "none";
|
||||
var msg = { payload:fpath, topic:node.p, file:file, filename:fpath };
|
||||
let type = "none";
|
||||
const msg = {
|
||||
payload:fpath,
|
||||
topic:node.p,
|
||||
file:file,
|
||||
filename:fpath,
|
||||
event: event
|
||||
};
|
||||
if (stat) {
|
||||
if (stat.isFile()) { type = "file"; msg.size = stat.size; }
|
||||
else if (stat.isBlockDevice()) { type = "blockdevice"; }
|
||||
else if (stat.isCharacterDevice()) { type = "characterdevice"; }
|
||||
else if (stat.isSocket()) { type = "socket"; }
|
||||
else if (stat.isFIFO()) { type = "fifo"; }
|
||||
else if (stat.isDirectory()) {
|
||||
type = "directory";
|
||||
if (node.recursive) {
|
||||
notifications.add([fpath]);
|
||||
notifications.add(getAllDirs(fpath));
|
||||
}
|
||||
}
|
||||
else if (stat.isDirectory()) { type = "directory"; }
|
||||
else { type = "n/a"; }
|
||||
}
|
||||
msg.type = type;
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
notifications.on('error', function (error, fpath) {
|
||||
var msg = { payload:fpath };
|
||||
watcher.on('error', function (error) {
|
||||
const msg = { payload: "" };
|
||||
node.error(error,msg);
|
||||
});
|
||||
|
||||
this.close = function() {
|
||||
notifications.close();
|
||||
watcher.close();
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("watch",WatchNode);
|
||||
RED.nodes.registerType("watch", WatchNode);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<p>Create virtual wires between flows.</p>
|
||||
<h3>Details</h3>
|
||||
<p>This node can be configured to either send messages to all <code>link in</code>
|
||||
nodes it is connected to, or to send a response back to the <code>link call</code>
|
||||
node that triggered the flow.</p>
|
||||
nodes it is connected to, or to send a response back to the <code>link call</code>
|
||||
node that triggered the flow.</p>
|
||||
<p>When in 'send to all' mode, the wires between link nodes are only displayed when
|
||||
the node is selected. If there are any wires to other tabs, a virtual node
|
||||
is shown that can be clicked on to jump to the appropriate tab.</p>
|
||||
@@ -39,12 +39,27 @@
|
||||
|
||||
<script type="text/html" data-help-name="link call">
|
||||
<p>Calls a flow that starts with a <code>link in</code> node and passes on the response.</p>
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">target<span class="property-type">string</span></dt>
|
||||
<dd>When the option <b>Link Type</b> is set to "Dynamic target", set <code>msg.target</code> to the name of the
|
||||
<code>link in</code> node you wish to call.</dd>
|
||||
</dl>
|
||||
<h3>Details</h3>
|
||||
<p>This node can be connected to a <code>link in</code> node that exists on any tab.
|
||||
The flow connected to that node must end with a <code>link out</code> node configured
|
||||
in 'return' mode.</p>
|
||||
<p>When this node receives a message, it is passed to the connected <code>link in</code> node.
|
||||
It then waits for a response which it then sends on.</o>
|
||||
It then waits for a response which it then sends on.</p>
|
||||
<p>If no response is received within the configured timeout, default 30 seconds, the node
|
||||
will log an error that can be caught using the <code>catch</code> node.</p>
|
||||
<p>When the option <b>Link Type</b> is set to "Dynamic target" <code>msg.target</code> can be used to call a
|
||||
<code>link in</code> by name. The target <code>link in</code> node must be named.
|
||||
<ul>
|
||||
<li>If there are 2 <code>link in</code> nodes with the same name, an error will be raised</li>
|
||||
<li>A <code>link call</code> cannot call a <code>link in</code> node inside a subflow</li>
|
||||
</ul>
|
||||
</p>
|
||||
The flow connected to that node must end with a <code>link out</code> node configured
|
||||
in 'return' mode.</p>
|
||||
</script>
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
"errors": {
|
||||
"failed": "inject failed, see log for details",
|
||||
"toolong": "Interval too large",
|
||||
"invalid-expr": "Invalid JSONata expression: __error__"
|
||||
"invalid-expr": "Invalid JSONata expression: __error__",
|
||||
"invalid-jsonata": "__prop__: invalid property expression: __error__",
|
||||
"invalid-prop": "__prop__: invalid property expression: __error__",
|
||||
"invalid-json": "__prop__: invalid JSON data: __error__",
|
||||
"invalid-repeat": "Invalid repeat value"
|
||||
}
|
||||
},
|
||||
"catch": {
|
||||
@@ -125,6 +129,7 @@
|
||||
"msgprop": "message property",
|
||||
"msgobj": "complete msg object",
|
||||
"autostatus": "same as debug output",
|
||||
"messageCount": "message count",
|
||||
"to": "To",
|
||||
"debtab": "debug tab",
|
||||
"tabcon": "debug tab and console",
|
||||
@@ -170,6 +175,11 @@
|
||||
"outMode": "Mode",
|
||||
"sendToAll": "Send to all connected link nodes",
|
||||
"returnToCaller": "Return to calling link node",
|
||||
"timeout": "timeout",
|
||||
"linkCallType": "Link Type",
|
||||
"staticLinkCall": "Fixed target",
|
||||
"dynamicLinkCall": "Dynamic target (msg.target)",
|
||||
"dynamicLinkLabel": "Dynamic",
|
||||
"error": {
|
||||
"missingReturn": "Missing return node information"
|
||||
}
|
||||
@@ -196,7 +206,9 @@
|
||||
"alpnprotocol":"for use with ALPN"
|
||||
},
|
||||
"error": {
|
||||
"missing-file": "No certificate/key file provided"
|
||||
"missing-file": "No certificate/key file provided",
|
||||
"invalid-cert": "Certificate not specified",
|
||||
"invalid-key": "Private key not specified"
|
||||
}
|
||||
},
|
||||
"exec": {
|
||||
@@ -251,7 +263,9 @@
|
||||
"moduleNameError": "Invalid module variable name: __name__",
|
||||
"moduleNameReserved": "Reserved variable name: __name__",
|
||||
"inputListener":"Cannot add listener to 'input' event within Function",
|
||||
"non-message-returned":"Function tried to send a message of type __type__"
|
||||
"non-message-returned":"Function tried to send a message of type __type__",
|
||||
"invalid-js": "Error in JavaScript code",
|
||||
"missing-module": "Module __module__ missing"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
@@ -305,6 +319,9 @@
|
||||
"limit": "limit",
|
||||
"limitTopic": "limit topic",
|
||||
"random": "random",
|
||||
"rate": "rate",
|
||||
"random-first": "first random value",
|
||||
"random-last": "last random value",
|
||||
"units" : {
|
||||
"second": {
|
||||
"plural" : "Seconds",
|
||||
@@ -325,7 +342,12 @@
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"too-many" : "too many pending messages in delay node"
|
||||
"too-many" : "too many pending messages in delay node",
|
||||
"invalid-timeout": "Invalid delay value",
|
||||
"invalid-rate": "Invalid rate value",
|
||||
"invalid-rate-unit": "Invalid rate unit value",
|
||||
"invalid-random-first": "Invalid first random value",
|
||||
"invalid-random-last": "Invalid last random value"
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
@@ -362,7 +384,9 @@
|
||||
"reset": "Reset the trigger if:",
|
||||
"resetMessage":"msg.reset is set",
|
||||
"resetPayload":"msg.payload equals",
|
||||
"resetprompt": "optional"
|
||||
"resetprompt": "optional",
|
||||
"duration": "duration",
|
||||
"topic": "topic"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
@@ -420,7 +444,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)",
|
||||
@@ -451,6 +476,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",
|
||||
@@ -465,7 +491,9 @@
|
||||
"invalid-json-parse": "Failed to parse JSON string",
|
||||
"invalid-action-action": "Invalid action specified",
|
||||
"invalid-action-alreadyconnected": "Disconnect from broker before connecting",
|
||||
"invalid-action-badsubscription": "msg.topic is missing or invalid"
|
||||
"invalid-action-badsubscription": "msg.topic is missing or invalid",
|
||||
"invalid-client-id": "Missing Client ID"
|
||||
|
||||
}
|
||||
},
|
||||
"httpin": {
|
||||
@@ -521,7 +549,8 @@
|
||||
"invalid-transport":"non-http transport requested",
|
||||
"timeout-isnan": "Timeout value is not a valid number, ignoring",
|
||||
"timeout-isnegative": "Timeout value is negative, ignoring",
|
||||
"invalid-payload": "Invalid payload"
|
||||
"invalid-payload": "Invalid payload",
|
||||
"invalid-url": "Invalid url"
|
||||
},
|
||||
"status": {
|
||||
"requesting": "requesting"
|
||||
@@ -554,7 +583,9 @@
|
||||
"connect-error": "An error occurred on the ws connection: ",
|
||||
"send-error": "An error occurred while sending: ",
|
||||
"missing-conf": "Missing server configuration",
|
||||
"duplicate-path": "Cannot have two WebSocket listeners on the same path: __path__"
|
||||
"duplicate-path": "Cannot have two WebSocket listeners on the same path: __path__",
|
||||
"missing-server": "Missing server configuration",
|
||||
"missing-client": "Missing client configuration"
|
||||
}
|
||||
},
|
||||
"watch": {
|
||||
@@ -583,7 +614,8 @@
|
||||
"ms": "ms",
|
||||
"chars": "chars",
|
||||
"close": "Close",
|
||||
"optional": "(optional)"
|
||||
"optional": "(optional)",
|
||||
"reattach": "re-attach delimiter"
|
||||
},
|
||||
"type": {
|
||||
"listen": "Listen on",
|
||||
@@ -624,7 +656,9 @@
|
||||
"no-host": "Host and/or port not set",
|
||||
"connect-timeout": "connect timeout",
|
||||
"connect-fail": "connect failed",
|
||||
"bad-string": "failed to convert to string"
|
||||
"bad-string": "failed to convert to string",
|
||||
"invalid-host": "Invalid host",
|
||||
"invalid-port": "Invalid port"
|
||||
}
|
||||
},
|
||||
"udp": {
|
||||
@@ -638,7 +672,8 @@
|
||||
"send": "Send a",
|
||||
"toport": "to port",
|
||||
"address": "Address",
|
||||
"decode-base64": "Decode Base64 encoded payload?"
|
||||
"decode-base64": "Decode Base64 encoded payload?",
|
||||
"port": "port"
|
||||
},
|
||||
"placeholder": {
|
||||
"interface": "(optional) local interface or address to bind to",
|
||||
@@ -685,7 +720,8 @@
|
||||
"port-notset": "udp: port not set",
|
||||
"port-invalid": "udp: port number not valid",
|
||||
"alreadyused": "udp: port __port__ already in use",
|
||||
"ifnotfound": "udp: interface __iface__ not found"
|
||||
"ifnotfound": "udp: interface __iface__ not found",
|
||||
"invalid-group": "invalid multicast group"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -749,7 +785,9 @@
|
||||
"invalid-from": "Invalid 'from' property: __error__",
|
||||
"invalid-json": "Invalid 'to' JSON property",
|
||||
"invalid-expr": "Invalid JSONata expression: __error__",
|
||||
"no-override": "Cannot set property of non-object type: __property__"
|
||||
"no-override": "Cannot set property of non-object type: __property__",
|
||||
"invalid-prop": "Invalid property expression: __property__",
|
||||
"invalid-json-data": "Invalid JSON data: __error__"
|
||||
}
|
||||
},
|
||||
"range": {
|
||||
@@ -760,7 +798,11 @@
|
||||
"resultrange": "to the target range",
|
||||
"from": "from",
|
||||
"to": "to",
|
||||
"roundresult": "Round result to the nearest integer?"
|
||||
"roundresult": "Round result to the nearest integer?",
|
||||
"minin": "input from",
|
||||
"maxin": "input to",
|
||||
"minout": "target from",
|
||||
"maxout": "target to"
|
||||
},
|
||||
"placeholder": {
|
||||
"min": "e.g. 0",
|
||||
@@ -985,6 +1027,7 @@
|
||||
"complete": "After a message with the <code>msg.complete</code> property set",
|
||||
"tip": "This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property.",
|
||||
"too-many": "too many pending messages in join node",
|
||||
"message-prop": "message property",
|
||||
"merge": {
|
||||
"topics-label": "Merged Topics",
|
||||
"topics": "topics",
|
||||
@@ -1042,7 +1085,12 @@
|
||||
},
|
||||
"too-many" : "too many pending messages in batch node",
|
||||
"unexpected" : "unexpected mode",
|
||||
"no-parts" : "no parts property in message"
|
||||
"no-parts" : "no parts property in message",
|
||||
"error": {
|
||||
"invalid-count": "Invalid count",
|
||||
"invalid-overlap": "Invalid overlap",
|
||||
"invalid-interval": "Invalid interval"
|
||||
}
|
||||
},
|
||||
"rbe": {
|
||||
"rbe": "filter",
|
||||
@@ -1051,7 +1099,10 @@
|
||||
"init": "Send initial value",
|
||||
"start": "Start value",
|
||||
"name": "Name",
|
||||
"septopics": "Apply mode separately for each "
|
||||
"septopics": "Apply mode separately for each ",
|
||||
"gap": "value change",
|
||||
"property": "property",
|
||||
"topic": "topic"
|
||||
},
|
||||
"placeholder":{
|
||||
"bandgap": "e.g. 10 or 5%",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<dd>If not configured in the node, this optional property sets the HTTP method of the request.
|
||||
Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code>.</dd>
|
||||
<dt class="optional">headers <span class="property-type">object</span></dt>
|
||||
<dd>Sets the HTTP headers of the request.</dd>
|
||||
<dd>Sets the HTTP headers of the request. NOTE: Any headers set in the node configuration will overwrite any matching headers in <code>msg.headers</code> </dd>
|
||||
<dt class="optional">cookies <span class="property-type">object</span></dt>
|
||||
<dd>If set, can be used to send cookies with the request.</dd>
|
||||
<dt class="optional">payload</dt>
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">filename <span class="property-type">string</span></dt>
|
||||
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
|
||||
<dd>The name of the file to be updated can be provided in the node configuration, or as a message property.
|
||||
By default it will use <code>msg.filename</code> but this can be customised in the node.
|
||||
</dd>
|
||||
<dt class="optional">encoding <span class="property-type">string</span></dt>
|
||||
<dd>If encoding is configured to be set by msg, then this optional property can set the encoding.</dt>
|
||||
</dl>
|
||||
@@ -43,7 +45,9 @@
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">filename <span class="property-type">string</span></dt>
|
||||
<dd>if not set in the node configuration, this property sets the filename to read.</dd>
|
||||
<dd>The name of the file to be read can be provided in the node configuration, or as a message property.
|
||||
By default it will use <code>msg.filename</code> but this can be customised in the node.
|
||||
</dd>
|
||||
</dl>
|
||||
<h3>Outputs</h3>
|
||||
<dl class="message-properties">
|
||||
|
||||
@@ -32,9 +32,21 @@
|
||||
|
||||
<script type="text/html" data-help-name="link call">
|
||||
<p><code>link in</code> で始まり、応答を返すフローを呼び出します。</p>
|
||||
<h3>入力</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">target<span class="property-type">文字列</span></dt>
|
||||
<dd><b>リンクの種類</b>で"対象を動的に指定"を選択した場合、<code>msg.target</code>に呼び出したい<code>link in</code>ノードの名前を指定します。</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
<p>本ノードは、任意のタブ内に存在する <code>link in</code> ノードに接続できます。 接続先のフローは、`返却`モードが設定された <code>link out</code> ノードで終了する必要があります。</p>
|
||||
<p>本ノードはメッセージを受信すると、メッセージを接続した <code>link in</code> ノードへ渡します。
|
||||
その後、応答を待った後にメッセージを送信します。</o>
|
||||
<p>もし、設定したタイムアウト(デフォルト30秒)以内に応答がない場合は、<code>catch</code> ノードを用いてエラーをログに記録することもできます。</p>
|
||||
<p><b>リンクの種類</b>で"対象を動的に指定"を選択した場合、<code>link in</code>ノードの名前を<code>msg.target</code>に指定して呼び出すことができます。対象となる<code>link in</code>ノードには、名前を付ける必要があります。
|
||||
<ul>
|
||||
<li>もし、同じ名前を付けた<code>link in</code>ノードが2つある場合、エラーが発生します。</li>
|
||||
<li><code>link call</code>は、サブフローの中の<code>link in</code>ノードを呼び出すことはできません。</li>
|
||||
</ul>
|
||||
</p>
|
||||
本ノードから呼び出すフローは、終端の<code>link out</code>ノードに'返却'モードを設定する必要があります。</p>
|
||||
</script>
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
"errors": {
|
||||
"failed": "inject処理が失敗しました。詳細はログを確認してください。",
|
||||
"toolong": "時間間隔が大き過ぎます",
|
||||
"invalid-expr": "JSONata式が不正: __error__"
|
||||
"invalid-expr": "JSONata式が不正: __error__",
|
||||
"invalid-jsonata": "__prop__: プロパティ式が不正: __error__",
|
||||
"invalid-prop": "__prop__: プロパティ式が不正: __error__",
|
||||
"invalid-json": "__prop__: JSONデータが不正: __error__",
|
||||
"invalid-repeat": "繰り返し数が不正"
|
||||
}
|
||||
},
|
||||
"catch": {
|
||||
@@ -170,6 +174,11 @@
|
||||
"outMode": "モード",
|
||||
"sendToAll": "接続された全てのlinkノードへ送信",
|
||||
"returnToCaller": "link callノードへ返却",
|
||||
"timeout": "タイムアウト",
|
||||
"linkCallType": "リンクの種類",
|
||||
"staticLinkCall": "対象を固定で指定",
|
||||
"dynamicLinkCall": "対象を動的に指定 (msg.target)",
|
||||
"dynamicLinkLabel": "動的",
|
||||
"error": {
|
||||
"missingReturn": "返却するノードの情報が存在しません"
|
||||
}
|
||||
@@ -196,7 +205,9 @@
|
||||
"alpnprotocol": "ALPNで使用"
|
||||
},
|
||||
"error": {
|
||||
"missing-file": "証明書と秘密鍵のファイルが設定されていません"
|
||||
"missing-file": "証明書と秘密鍵のファイルが設定されていません",
|
||||
"invalid-cert": "証明書が指定されていません",
|
||||
"invalid-key": "秘密鍵が指定されていません"
|
||||
}
|
||||
},
|
||||
"exec": {
|
||||
@@ -251,7 +262,9 @@
|
||||
"moduleNameError": "モジュール変数名が不正です: __name__",
|
||||
"moduleNameReserved": "予約された変数名です: __name__",
|
||||
"inputListener": "コード内で'input'イベントのリスナを設定できません",
|
||||
"non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました"
|
||||
"non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました",
|
||||
"invalid-js": "JavaScriptコードのエラー",
|
||||
"missing-module": "モジュール __module__ が存在しません"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
@@ -305,6 +318,9 @@
|
||||
"limit": "limit",
|
||||
"limitTopic": "limit topic",
|
||||
"random": "random",
|
||||
"rate": "流量",
|
||||
"random-first": "ランダム最小値",
|
||||
"random-last": "ランダム最大値",
|
||||
"units": {
|
||||
"second": {
|
||||
"plural": "秒",
|
||||
@@ -325,7 +341,12 @@
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"too-many": "delayノード内で保持しているメッセージが多すぎます"
|
||||
"too-many": "delayノード内で保持しているメッセージが多すぎます",
|
||||
"invalid-timeout": "遅延時間が不正",
|
||||
"invalid-rate": "流量値が不正",
|
||||
"invalid-rate-unit": "流量単位時間が不正",
|
||||
"invalid-random-first": "ランダム最小値が不正",
|
||||
"invalid-random-last": "ランダム最大値が不正"
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
@@ -362,7 +383,9 @@
|
||||
"reset": "初期化条件:",
|
||||
"resetMessage": "msg.resetを設定",
|
||||
"resetPayload": "msg.payloadが次の値",
|
||||
"resetprompt": "任意"
|
||||
"resetprompt": "任意",
|
||||
"duration": "時間間隔",
|
||||
"topic": "トピック"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
@@ -451,6 +474,7 @@
|
||||
"string": "文字列",
|
||||
"base64": "Base64文字列",
|
||||
"auto": "自動判定(文字列もしくはバイナリバッファ)",
|
||||
"auto-detect": "自動判定(JSONオブジェクト、文字列もしくはバイナリバッファ)",
|
||||
"json": "JSONオブジェクト"
|
||||
},
|
||||
"true": "する",
|
||||
@@ -465,7 +489,8 @@
|
||||
"invalid-json-parse": "JSON文字列のパースに失敗しました",
|
||||
"invalid-action-action": "指定された動作が不正です",
|
||||
"invalid-action-alreadyconnected": "接続する前にブローカから切断してください",
|
||||
"invalid-action-badsubscription": "msg.topicが存在しないか不正です"
|
||||
"invalid-action-badsubscription": "msg.topicが存在しないか不正です",
|
||||
"invalid-client-id": "クライアントIDが未指定"
|
||||
}
|
||||
},
|
||||
"httpin": {
|
||||
@@ -521,7 +546,8 @@
|
||||
"invalid-transport": "httpでないトランスポートが要求されました",
|
||||
"timeout-isnan": "タイムアウト値が数値ではないため無視します",
|
||||
"timeout-isnegative": "タイムアウト値が負数のため無視します",
|
||||
"invalid-payload": "不正なペイロード"
|
||||
"invalid-payload": "不正なペイロード",
|
||||
"invalid-url": "URLが不正"
|
||||
},
|
||||
"status": {
|
||||
"requesting": "要求中"
|
||||
@@ -554,7 +580,9 @@
|
||||
"connect-error": "ws接続でエラーが発生しました: ",
|
||||
"send-error": "送信中にエラーが発生しました: ",
|
||||
"missing-conf": "サーバ設定が不足しています",
|
||||
"duplicate-path": "同じパスに対して2つのWebSocketリスナは指定できません: __path__"
|
||||
"duplicate-path": "同じパスに対して2つのWebSocketリスナは指定できません: __path__",
|
||||
"missing-server": "サーバが設定されていません",
|
||||
"missing-client": "クライアントが設定されていません"
|
||||
}
|
||||
},
|
||||
"watch": {
|
||||
@@ -623,7 +651,9 @@
|
||||
"no-host": "ホスト名またはポートが設定されていません",
|
||||
"connect-timeout": "接続がタイムアウトしました",
|
||||
"connect-fail": "接続に失敗しました",
|
||||
"bad-string": "文字列への変換に失敗しました"
|
||||
"bad-string": "文字列への変換に失敗しました",
|
||||
"invalid-host": "ホスト名が不正",
|
||||
"invalid-port": "ポートが不正"
|
||||
}
|
||||
},
|
||||
"udp": {
|
||||
@@ -637,7 +667,8 @@
|
||||
"send": "送信",
|
||||
"toport": "ポート",
|
||||
"address": "アドレス",
|
||||
"decode-base64": "Base64形式のペイロードを復号"
|
||||
"decode-base64": "Base64形式のペイロードを復号",
|
||||
"port": "ポート"
|
||||
},
|
||||
"placeholder": {
|
||||
"interface": "(任意) 使用するローカルインターフェイスもしくはアドレス",
|
||||
@@ -684,7 +715,8 @@
|
||||
"port-notset": "udp: ポートが設定されていません",
|
||||
"port-invalid": "udp: ポート番号が不正です",
|
||||
"alreadyused": "udp: 既に__port__番ポートが使用されています",
|
||||
"ifnotfound": "udp: インターフェイス __iface__ がありません"
|
||||
"ifnotfound": "udp: インターフェイス __iface__ がありません",
|
||||
"invalid-group": "マルチキャストグループが不正"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -748,7 +780,9 @@
|
||||
"invalid-from": "操作対象のプロパティが不正: __error__",
|
||||
"invalid-json": "対象の値のJSONプロパティが不正",
|
||||
"invalid-expr": "JSONata式が不正: __error__",
|
||||
"no-override": "オブジェクト型でないプロパティを設定できません: __property__"
|
||||
"no-override": "オブジェクト型でないプロパティを設定できません: __property__",
|
||||
"invalid-prop": "プロパティ式が不正: __property__",
|
||||
"invalid-json-data": "JSONデータが不正: __error__"
|
||||
}
|
||||
},
|
||||
"range": {
|
||||
@@ -759,7 +793,11 @@
|
||||
"resultrange": "出力値の範囲",
|
||||
"from": "最小値",
|
||||
"to": "最大値",
|
||||
"roundresult": "小数値を四捨五入し整数値へ変換"
|
||||
"roundresult": "小数値を四捨五入し整数値へ変換",
|
||||
"minin": "入力最小値",
|
||||
"maxin": "入力最大値",
|
||||
"minout": "出力最小値",
|
||||
"maxout": "出力最大値"
|
||||
},
|
||||
"placeholder": {
|
||||
"min": "例) 0",
|
||||
@@ -984,7 +1022,8 @@
|
||||
"complete": "<code>msg.complete</code> プロパティが設定されたメッセージ受信後",
|
||||
"tip": "このモードでは、本ノードが <i>split</i> ノードと組となるか、 <code>msg.parts</code> プロパティが設定されたメッセージを受け取ることが前提となります。",
|
||||
"too-many": "joinノード内部で保持しているメッセージが多すぎます",
|
||||
"merge": {
|
||||
"message-prop": "メッセージプロパティ",
|
||||
"merge": {
|
||||
"topics-label": "対象トピック",
|
||||
"topics": "トピック",
|
||||
"topic": "トピック",
|
||||
@@ -1041,7 +1080,12 @@
|
||||
},
|
||||
"too-many": "batchノード内で保持しているメッセージが多すぎます",
|
||||
"unexpected": "想定外のモード",
|
||||
"no-parts": "メッセージにpartsプロパティがありません"
|
||||
"no-parts": "メッセージにpartsプロパティがありません",
|
||||
"error": {
|
||||
"invalid-count": "メッセージ数が不正",
|
||||
"invalid-overlap": "オーバラップが不正",
|
||||
"invalid-interval": "時間間隔が不正"
|
||||
}
|
||||
},
|
||||
"rbe": {
|
||||
"rbe": "filter",
|
||||
@@ -1050,7 +1094,10 @@
|
||||
"init": "初期値を送付",
|
||||
"start": "初期値",
|
||||
"name": "名前",
|
||||
"septopics": "個別に動作を適用"
|
||||
"septopics": "個別に動作を適用",
|
||||
"gap": "変化量",
|
||||
"property": "プロパティ",
|
||||
"topic": "トピック"
|
||||
},
|
||||
"placeholder": {
|
||||
"bandgap": "例:10、5%",
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
"string": "문자열",
|
||||
"base64": "Base64문자열",
|
||||
"auto": "자동판정(문자열혹은 바이너리버퍼)",
|
||||
"auto-detect": "자동판정(JSON오브젝트, 문자열혹은 바이너리버퍼)",
|
||||
"json": "JSON오브젝트"
|
||||
},
|
||||
"true": "한다",
|
||||
|
||||
@@ -385,6 +385,7 @@
|
||||
"string": "строка",
|
||||
"base64": "строка в кодировке Base64",
|
||||
"auto": "автоопределение (строка или буфер)",
|
||||
"auto-detect": "автоопределение (разобрать объект JSON, строка или буфер)",
|
||||
"json": "объект JSON"
|
||||
},
|
||||
"true": "да",
|
||||
|
||||
@@ -382,6 +382,7 @@
|
||||
"string": "字符串",
|
||||
"base64": "Base64编码字符串",
|
||||
"auto": "自动检测 (字符串或buffer)",
|
||||
"auto-detect": "自动检测 (已解析的JSON对象、字符串或buffer)",
|
||||
"json": "解析的JSON对象"
|
||||
},
|
||||
"true": "是",
|
||||
|
||||
@@ -386,6 +386,7 @@
|
||||
"string": "字串",
|
||||
"base64": "Base64編碼字串",
|
||||
"auto": "自動檢測 (字符串或buffer)",
|
||||
"auto-detect": "自动检测 (已解析的JSON对象、字符串或buffer)",
|
||||
"json": "解析的JSON對象"
|
||||
},
|
||||
"true": "是",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "2.2.2",
|
||||
"version": "3.0.0-beta.1",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -15,32 +15,32 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"acorn": "8.7.0",
|
||||
"acorn": "8.7.1",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.10.0",
|
||||
"body-parser": "1.19.1",
|
||||
"ajv": "8.11.0",
|
||||
"body-parser": "1.20.0",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"content-type": "1.0.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cookie": "0.4.2",
|
||||
"cookie": "0.5.0",
|
||||
"cors": "2.8.5",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "2.0.1",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"fs.notify": "0.0.4",
|
||||
"fs-extra": "10.1.0",
|
||||
"got": "11.8.3",
|
||||
"hash-sum": "2.0.0",
|
||||
"hpagent": "0.1.2",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.14.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"media-typer": "1.1.0",
|
||||
"mqtt": "4.3.5",
|
||||
"mqtt": "4.3.7",
|
||||
"multer": "1.4.4",
|
||||
"mustache": "4.2.0",
|
||||
"node-watch": "0.7.3",
|
||||
"on-headers": "1.0.2",
|
||||
"raw-body": "2.4.3",
|
||||
"raw-body": "2.5.1",
|
||||
"tough-cookie": "4.0.0",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.6",
|
||||
|
||||
Reference in New Issue
Block a user