mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Remove known unused files
This commit is contained in:
parent
419a81034c
commit
b1daa8932a
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,3 +27,4 @@ docs
|
||||
.vscode
|
||||
.nyc_output
|
||||
sync.ffs_db
|
||||
.idea
|
@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.
|
@ -1,711 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="inject">
|
||||
<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-row node-input-property-container-row">
|
||||
<ol id="node-input-property-container"></ol>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-once">
|
||||
<label for="node-input-once"> </label>
|
||||
<input type="checkbox" id="node-input-once" style="display:inline-block; width:15px; vertical-align:baseline;">
|
||||
<span data-i18n="inject.onstart"></span>
|
||||
<input type="text" id="node-input-onceDelay" placeholder="0.1" style="width:45px; height:28px;">
|
||||
<span data-i18n="inject.onceDelay"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for=""><i class="fa fa-repeat"></i> <span data-i18n="inject.label.repeat"></span></label>
|
||||
<select id="inject-time-type-select">
|
||||
<option value="none" data-i18n="inject.none"></option>
|
||||
<option value="interval" data-i18n="inject.interval"></option>
|
||||
<option value="interval-time" data-i18n="inject.interval-time"></option>
|
||||
<option value="time" data-i18n="inject.time"></option>
|
||||
</select>
|
||||
<input type="hidden" id="node-input-repeat">
|
||||
<input type="hidden" id="node-input-crontab">
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
|
||||
<span data-i18n="inject.every"></span>
|
||||
<input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
|
||||
<select style="width:100px" id="inject-time-interval-units">
|
||||
<option value="s" data-i18n="inject.seconds"></option>
|
||||
<option value="m" data-i18n="inject.minutes"></option>
|
||||
<option value="h" data-i18n="inject.hours"></option>
|
||||
</select><br/>
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
|
||||
<span data-i18n="inject.every"></span> <select style="width:90px; margin-left:20px;" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="10">10</option>
|
||||
<option value="12">12</option>
|
||||
<option value="15">15</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="0">60</option>
|
||||
</select> <span data-i18n="inject.minutes"></span><br/>
|
||||
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
|
||||
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
|
||||
<div id="inject-time-interval-time-days" class="inject-time-days" style="margin-top:5px">
|
||||
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on">on</div>
|
||||
<div style="display:inline-block;">
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
|
||||
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"></input><br/>
|
||||
<div id="inject-time-time-days" class="inject-time-days">
|
||||
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on"></div>
|
||||
<div style="display:inline-block;">
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.inject-time-row {
|
||||
padding-left: 110px;
|
||||
}
|
||||
.inject-time-row select {
|
||||
margin: 3px 0;
|
||||
}
|
||||
.inject-time-days label {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: baseline;
|
||||
width: 100px;
|
||||
}
|
||||
.inject-time-days input {
|
||||
width: auto !important;
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
.inject-time-times {
|
||||
width: 90px !important;
|
||||
}
|
||||
#inject-time-time {
|
||||
width: 75px;
|
||||
margin-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.inject-time-count {
|
||||
padding-left: 3px !important;
|
||||
width: 80px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
function resizeDialog(size) {
|
||||
size = size || { height: $(".red-ui-tray-content form").height() }
|
||||
var rows = $("#dialog-form>div:not(.node-input-property-container-row):visible");
|
||||
var height = size.height;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-property-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
height += 16;
|
||||
$("#node-input-property-container").editableList('height',height);
|
||||
}
|
||||
/** Retrieve editableList items (refactored for re-use in the form inject button)*/
|
||||
function getProps(el, legacy) {
|
||||
var result = {
|
||||
props: []
|
||||
}
|
||||
el.each(function(i) {
|
||||
var prop = $(this);
|
||||
var p = {
|
||||
p:prop.find(".node-input-prop-property-name").typedInput('value')
|
||||
};
|
||||
if (p.p) {
|
||||
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
|
||||
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
|
||||
if(legacy) {
|
||||
if (p.p === "payload") { // save payload to old "legacy" property
|
||||
result.payloadType = p.vt;
|
||||
result.payload = p.v;
|
||||
delete p.v;
|
||||
delete p.vt;
|
||||
} else if (p.p === "topic" && p.vt === "str") {
|
||||
result.topic = p.v;
|
||||
delete p.v;
|
||||
}
|
||||
}
|
||||
result.props.push(p);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/
|
||||
function doInject(node, customMsg) {
|
||||
var label = node._def.label.call(node,customMsg?customMsg.__user_inject_props__:undefined);
|
||||
if (label.length > 30) {
|
||||
label = label.substring(0, 50) + "...";
|
||||
}
|
||||
label = label.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
$.ajax({
|
||||
url: "inject/" + node.id,
|
||||
type: "POST",
|
||||
data: JSON.stringify(customMsg||{}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (resp) {
|
||||
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.status == 404) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error");
|
||||
} else if (jqXHR.status == 500) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error");
|
||||
} else if (jqXHR.status == 0) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error");
|
||||
} else {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType('inject',{
|
||||
category: 'common',
|
||||
color:"#a6bbcf",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v) {
|
||||
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;
|
||||
}
|
||||
} else if (v[i].vt === "jsonata") {
|
||||
try{jsonata(v[i].v);}catch(e){return false;}
|
||||
} else if ([i].vt === "json") {
|
||||
try{JSON.parse(v[i].v);}catch(e){return false;}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
|
||||
crontab: {value:""},
|
||||
once: {value:false},
|
||||
onceDelay: {value:0.1},
|
||||
topic: {value:""},
|
||||
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
|
||||
payloadType: {value:"date"},
|
||||
},
|
||||
icon: "inject.svg",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
outputLabels: function(index) {
|
||||
var lab = '';
|
||||
|
||||
// if only payload and topic - display payload type
|
||||
// if only one property - show it's type
|
||||
// if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
|
||||
// this.props will not be an array for legacy inject nodes until they are re-deployed
|
||||
//
|
||||
var props = this.props;
|
||||
if (!Array.isArray(props)) {
|
||||
props = [
|
||||
{ p:"payload", v: this.payload, vt: this.payloadType },
|
||||
{ p:"topic", v: this.topic, vt: "str" }
|
||||
]
|
||||
}
|
||||
if (props) {
|
||||
for (var i=0,l=props.length; i<l; i++) {
|
||||
if (i > 0) lab += "\n";
|
||||
if (i === 5) {
|
||||
lab += "... +"+(props.length-5);
|
||||
break;
|
||||
}
|
||||
lab += props[i].p+": ";
|
||||
|
||||
var propType = props[i].p === "payload"? this.payloadType : props[i].vt;
|
||||
if (propType === "json") {
|
||||
try {
|
||||
var parsedProp = JSON.parse(props[i].p === "payload"? this.payload : props[i].v);
|
||||
propType = typeof parsedProp;
|
||||
if (propType === "object" && Array.isArray(parsedProp)) {
|
||||
propType = "Array";
|
||||
}
|
||||
} catch(e) {
|
||||
propType = "invalid";
|
||||
}
|
||||
}
|
||||
lab += this._("inject.label."+propType);
|
||||
}
|
||||
}
|
||||
return lab;
|
||||
},
|
||||
label: function(customProps) {
|
||||
var suffix = "";
|
||||
// if fire once then add small indication
|
||||
if (this.once) {
|
||||
suffix = " ¹";
|
||||
}
|
||||
// but replace with repeat one if set to repeat
|
||||
if ((this.repeat && this.repeat != 0) || this.crontab) {
|
||||
suffix = " ↻";
|
||||
}
|
||||
if (this.name) {
|
||||
return this.name+suffix;
|
||||
}
|
||||
var payload = "";
|
||||
var payloadType = "str";
|
||||
var topic = "";
|
||||
if (customProps) {
|
||||
for (var i=0;i<customProps.length;i++) {
|
||||
if (customProps[i].p === "payload") {
|
||||
payload = customProps[i].v;
|
||||
payloadType = customProps[i].vt;
|
||||
} else if (customProps[i].p === "topic") {
|
||||
topic = customProps[i].v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
payload = this.payload || "";
|
||||
payloadType = this.payloadType || "str";
|
||||
topic = this.topic || "";
|
||||
}
|
||||
if (payloadType === "string" ||
|
||||
payloadType === "str" ||
|
||||
payloadType === "num" ||
|
||||
payloadType === "bool" ||
|
||||
payloadType === "json") {
|
||||
if ((topic !== "") && ((topic.length + payload.length) <= 32)) {
|
||||
return topic + ":" + payload+suffix;
|
||||
} else if (payload.length > 0 && payload.length < 24) {
|
||||
return payload+suffix;
|
||||
} else {
|
||||
return this._("inject.inject")+suffix;
|
||||
}
|
||||
} else if (payloadType === 'date' || payloadType === 'bin' || payloadType === 'env') {
|
||||
if ((topic !== "") && (topic.length <= 16)) {
|
||||
return topic + ":" + this._('inject.label.'+payloadType)+suffix;
|
||||
} else {
|
||||
return this._('inject.label.'+payloadType)+suffix;
|
||||
}
|
||||
} else if (payloadType === 'flow' || payloadType === 'global') {
|
||||
var key = RED.utils.parseContextKey(payload);
|
||||
return payloadType+"."+key.key+suffix;
|
||||
} else {
|
||||
return this._("inject.inject")+suffix;
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var payloadType = node.payloadType;
|
||||
|
||||
if (node.payloadType == null) {
|
||||
if (node.payload == "") {
|
||||
payloadType = "date";
|
||||
} else {
|
||||
payloadType = "str";
|
||||
}
|
||||
} else if (node.payloadType === 'string' || node.payloadType === 'none') {
|
||||
payloadType = "str";
|
||||
}
|
||||
|
||||
$("#inject-time-type-select").on("change", function() {
|
||||
$("#node-input-crontab").val('');
|
||||
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();
|
||||
scrollDiv.scrollTop(scrollDiv.prop('scrollHeight'));
|
||||
resizeDialog();
|
||||
});
|
||||
|
||||
$("#node-input-once").on("change", function() {
|
||||
$("#node-input-onceDelay").attr('disabled', !$("#node-input-once").prop('checked'));
|
||||
})
|
||||
|
||||
$(".inject-time-times").each(function() {
|
||||
for (var i=0; i<24; i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
$(this).append($("<option></option>").val(i).text(l));
|
||||
}
|
||||
});
|
||||
$("<option></option>").val(24).text("00:00").appendTo("#inject-time-interval-time-end");
|
||||
$("#inject-time-interval-time-start").on("change", function() {
|
||||
var start = Number($("#inject-time-interval-time-start").val());
|
||||
var end = Number($("#inject-time-interval-time-end").val());
|
||||
$("#inject-time-interval-time-end option").remove();
|
||||
for (var i=start+1; i<25; i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
if (i==24) {
|
||||
l = "00:00";
|
||||
}
|
||||
var opt = $("<option></option>").val(i).text(l).appendTo("#inject-time-interval-time-end");
|
||||
if (i === end) {
|
||||
opt.attr("selected","selected");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".inject-time-count").spinner({
|
||||
//max:60,
|
||||
min:1
|
||||
});
|
||||
|
||||
var repeattype = "none";
|
||||
if (node.repeat != "" && node.repeat != 0) {
|
||||
repeattype = "interval";
|
||||
var r = "s";
|
||||
var c = node.repeat;
|
||||
if (node.repeat % 60 === 0) { r = "m"; c = c/60; }
|
||||
if (node.repeat % 1440 === 0) { r = "h"; c = c/60; }
|
||||
$("#inject-time-interval-count").val(c);
|
||||
$("#inject-time-interval-units").val(r);
|
||||
$("#inject-time-interval-days").prop("disabled","disabled");
|
||||
} else if (node.crontab) {
|
||||
var cronparts = node.crontab.split(" ");
|
||||
var days = cronparts[4];
|
||||
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
|
||||
repeattype = "time";
|
||||
// Fixed time
|
||||
var time = cronparts[1]+":"+cronparts[0];
|
||||
$("#inject-time-time").val(time);
|
||||
$("#inject-time-type-select").val("s");
|
||||
if (days == "*") {
|
||||
$("#inject-time-time-days input[type=checkbox]").prop("checked",true);
|
||||
} else {
|
||||
$("#inject-time-time-days input[type=checkbox]").removeAttr("checked");
|
||||
days.split(",").forEach(function(v) {
|
||||
$("#inject-time-time-days [value=" + v + "]").prop("checked", true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
repeattype = "interval-time";
|
||||
// interval - time period
|
||||
var minutes = cronparts[0].slice(2);
|
||||
if (minutes === "") { minutes = "0"; }
|
||||
$("#inject-time-interval-time-units").val(minutes);
|
||||
if (days == "*") {
|
||||
$("#inject-time-interval-time-days input[type=checkbox]").prop("checked",true);
|
||||
} else {
|
||||
$("#inject-time-interval-time-days input[type=checkbox]").removeAttr("checked");
|
||||
days.split(",").forEach(function(v) {
|
||||
$("#inject-time-interval-time-days [value=" + v + "]").prop("checked", true);
|
||||
});
|
||||
}
|
||||
var time = cronparts[1];
|
||||
var timeparts = time.split(",");
|
||||
var start;
|
||||
var end;
|
||||
if (timeparts.length == 1) {
|
||||
// 0 or 0-10
|
||||
var hours = timeparts[0].split("-");
|
||||
if (hours.length == 1) {
|
||||
if (hours[0] === "") {
|
||||
start = "0";
|
||||
end = "0";
|
||||
}
|
||||
else {
|
||||
start = hours[0];
|
||||
end = Number(hours[0])+1;
|
||||
}
|
||||
} else {
|
||||
start = hours[0];
|
||||
end = Number(hours[1])+1;
|
||||
}
|
||||
} else {
|
||||
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
|
||||
var startparts = timeparts[0].split("-");
|
||||
start = startparts[0];
|
||||
|
||||
var endparts = timeparts[1].split("-");
|
||||
if (endparts.length == 1) {
|
||||
end = Number(endparts[0])+1;
|
||||
} else {
|
||||
end = Number(endparts[1])+1;
|
||||
}
|
||||
}
|
||||
$("#inject-time-interval-time-end").val(end);
|
||||
$("#inject-time-interval-time-start").val(start);
|
||||
|
||||
}
|
||||
} else {
|
||||
$("#inject-time-type-select").val("none");
|
||||
}
|
||||
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-type-select").val(repeattype);
|
||||
$("#inject-time-row-"+repeattype).show();
|
||||
|
||||
/* */
|
||||
|
||||
var eList = $('#node-input-property-container').css('min-height','120px').css('min-width','450px');
|
||||
|
||||
eList.editableList({
|
||||
buttons: [
|
||||
{
|
||||
id: "node-inject-test-inject-button",
|
||||
label: node._("inject.injectNow"),
|
||||
click: function(e) {
|
||||
var items = eList.editableList('items');
|
||||
var props = getProps(items);
|
||||
var m = {__user_inject_props__: props.props};
|
||||
doInject(node, m);
|
||||
}
|
||||
}
|
||||
],
|
||||
addItem: function(container,i,opt) {
|
||||
var prop = opt;
|
||||
if (!prop.hasOwnProperty('p')) {
|
||||
prop = {p:"",v:"",vt:"str"};
|
||||
}
|
||||
container.css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
});
|
||||
var row = $('<div/>').appendTo(container);
|
||||
|
||||
var propertyName = $('<input/>',{class:"node-input-prop-property-name",type:"text"})
|
||||
.css("width","30%")
|
||||
.appendTo(row)
|
||||
.typedInput({types:['msg']});
|
||||
|
||||
$('<div/>',{style: 'display:inline-block; padding:0px 6px;'})
|
||||
.text('=')
|
||||
.appendTo(row);
|
||||
|
||||
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
|
||||
.css("width","calc(70% - 30px)")
|
||||
.appendTo(row)
|
||||
.typedInput({default:prop.vt || 'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']});
|
||||
|
||||
propertyName.typedInput('value',prop.p);
|
||||
propertyValue.typedInput('value',prop.v);
|
||||
},
|
||||
removable: true,
|
||||
sortable: true
|
||||
});
|
||||
$('#node-inject-test-inject-button').css("float", "right").css("margin-right", "unset");
|
||||
|
||||
if (RED.nodes.subflow(node.z)) {
|
||||
$('#node-inject-test-inject-button').attr("disabled",true);
|
||||
}
|
||||
|
||||
if (!node.props) {
|
||||
var payload = {
|
||||
p:'payload',
|
||||
v: node.payload ? node.payload : '',
|
||||
vt:payloadType ? payloadType : 'date'
|
||||
};
|
||||
var topic = {
|
||||
p:'topic',
|
||||
v: node.topic ? node.topic : '',
|
||||
vt:'str'
|
||||
}
|
||||
node.props = [payload,topic];
|
||||
}
|
||||
|
||||
for (var i=0; i<node.props.length; i++) {
|
||||
var prop = node.props[i];
|
||||
var newProp = { p: prop.p, v: prop.v, vt: prop.vt };
|
||||
if (newProp.v === undefined) {
|
||||
if (prop.p === 'payload') {
|
||||
newProp.v = node.payload ? node.payload : '';
|
||||
newProp.vt = payloadType ? payloadType : 'date';
|
||||
} else if (prop.p === 'topic' && prop.vt === "str") {
|
||||
newProp.v = node.topic ? node.topic : '';
|
||||
}
|
||||
}
|
||||
if (newProp.vt === "string") {
|
||||
// Fix bug in pre 2.1 where an old Inject node might have
|
||||
// a migrated rule with type 'string' not 'str'
|
||||
newProp.vt = "str";
|
||||
}
|
||||
eList.editableList('addItem',newProp);
|
||||
}
|
||||
|
||||
$("#inject-time-type-select").trigger("change");
|
||||
$("#inject-time-interval-time-start").trigger("change");
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var repeat = "";
|
||||
var crontab = "";
|
||||
var type = $("#inject-time-type-select").val();
|
||||
if (type == "none") {
|
||||
// nothing
|
||||
} else if (type == "interval") {
|
||||
var count = $("#inject-time-interval-count").val();
|
||||
var units = $("#inject-time-interval-units").val();
|
||||
if (units == "s") {
|
||||
repeat = count;
|
||||
} else {
|
||||
if (units == "m") {
|
||||
//crontab = "*/"+count+" * * * "+days;
|
||||
repeat = count * 60;
|
||||
} else if (units == "h") {
|
||||
//crontab = "0 */"+count+" * * "+days;
|
||||
repeat = count * 60 * 60;
|
||||
}
|
||||
}
|
||||
} else if (type == "interval-time") {
|
||||
repeat = "";
|
||||
var count = $("#inject-time-interval-time-units").val();
|
||||
var startTime = Number($("#inject-time-interval-time-start").val());
|
||||
var endTime = Number($("#inject-time-interval-time-end").val());
|
||||
var days = $('#inject-time-interval-time-days input[type=checkbox]:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 0) {
|
||||
crontab = "";
|
||||
} else {
|
||||
if (days.length == 7) {
|
||||
days="*";
|
||||
} else {
|
||||
days = days.join(",");
|
||||
}
|
||||
var timerange = "";
|
||||
if (endTime == 0) {
|
||||
timerange = startTime+"-23";
|
||||
} else if (startTime+1 < endTime) {
|
||||
timerange = startTime+"-"+(endTime-1);
|
||||
} else if (startTime+1 == endTime) {
|
||||
timerange = startTime;
|
||||
} else {
|
||||
var startpart = "";
|
||||
var endpart = "";
|
||||
if (startTime == 23) {
|
||||
startpart = "23";
|
||||
} else {
|
||||
startpart = startTime+"-23";
|
||||
}
|
||||
if (endTime == 1) {
|
||||
endpart = "0";
|
||||
} else {
|
||||
endpart = "0-"+(endTime-1);
|
||||
}
|
||||
timerange = startpart+","+endpart;
|
||||
}
|
||||
if (count === "0") {
|
||||
crontab = count+" "+timerange+" * * "+days;
|
||||
} else {
|
||||
crontab = "*/"+count+" "+timerange+" * * "+days;
|
||||
}
|
||||
}
|
||||
} else if (type == "time") {
|
||||
var time = $("#inject-time-time").val();
|
||||
var days = $('#inject-time-time-days input[type=checkbox]:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 0) {
|
||||
crontab = "";
|
||||
} else {
|
||||
if (days.length == 7) {
|
||||
days="*";
|
||||
} else {
|
||||
days = days.join(",");
|
||||
}
|
||||
var parts = time.split(":");
|
||||
if (parts.length === 2) {
|
||||
repeat = "";
|
||||
parts[1] = ("00" + (parseInt(parts[1]) % 60)).substr(-2);
|
||||
parts[0] = ("00" + (parseInt(parts[0]) % 24)).substr(-2);
|
||||
crontab = parts[1]+" "+parts[0]+" * * "+days;
|
||||
}
|
||||
else { crontab = ""; }
|
||||
}
|
||||
}
|
||||
|
||||
$("#node-input-repeat").val(repeat);
|
||||
$("#node-input-crontab").val(crontab);
|
||||
|
||||
/* Gather the properties */
|
||||
var items = $("#node-input-property-container").editableList('items');
|
||||
delete this.payloadType;
|
||||
delete this.payload;
|
||||
this.topic = "";
|
||||
var result = getProps(items, true);
|
||||
this.props = result.props;
|
||||
if(result.payloadType) { this.payloadType = result.payloadType; };
|
||||
if(result.payload) { this.payload = result.payload; };
|
||||
if(result.topic) { this.topic = result.topic; };
|
||||
},
|
||||
button: {
|
||||
enabled: function() {
|
||||
return !this.changed
|
||||
},
|
||||
onclick: function () {
|
||||
if (this.changed) {
|
||||
return RED.notify(RED._("notification.warning", { message: RED._("notification.warnings.undeployedChanges") }), "warning");
|
||||
}
|
||||
doInject(this);
|
||||
}
|
||||
},
|
||||
oneditresize: resizeDialog
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,177 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
const {scheduleTask} = require("cronosjs");
|
||||
|
||||
function InjectNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
/* Handle legacy */
|
||||
if(!Array.isArray(n.props)){
|
||||
n.props = [];
|
||||
n.props.push({
|
||||
p:'payload',
|
||||
v:n.payload,
|
||||
vt:n.payloadType
|
||||
});
|
||||
n.props.push({
|
||||
p:'topic',
|
||||
v:n.topic,
|
||||
vt:'str'
|
||||
});
|
||||
} else {
|
||||
for (var i=0,l=n.props.length; i<l; i++) {
|
||||
if (n.props[i].p === 'payload' && !n.props[i].hasOwnProperty('v')) {
|
||||
n.props[i].v = n.payload;
|
||||
n.props[i].vt = n.payloadType;
|
||||
} else if (n.props[i].p === 'topic' && n.props[i].vt === 'str' && !n.props[i].hasOwnProperty('v')) {
|
||||
n.props[i].v = n.topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.props = n.props;
|
||||
this.repeat = n.repeat;
|
||||
this.crontab = n.crontab;
|
||||
this.once = n.once;
|
||||
this.onceDelay = (n.onceDelay || 0.1) * 1000;
|
||||
this.interval_id = null;
|
||||
this.cronjob = null;
|
||||
var node = this;
|
||||
|
||||
node.props.forEach(function (prop) {
|
||||
if (prop.vt === "jsonata") {
|
||||
try {
|
||||
var val = prop.v ? prop.v : "";
|
||||
prop.exp = RED.util.prepareJSONataExpression(val, node);
|
||||
}
|
||||
catch (err) {
|
||||
node.error(RED._("inject.errors.invalid-expr", {error:err.message}));
|
||||
prop.exp = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (node.repeat > 2147483) {
|
||||
node.error(RED._("inject.errors.toolong", this));
|
||||
delete node.repeat;
|
||||
}
|
||||
|
||||
node.repeaterSetup = function () {
|
||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
||||
this.repeat = this.repeat * 1000;
|
||||
if (RED.settings.verbose) {
|
||||
this.log(RED._("inject.repeat", this));
|
||||
}
|
||||
this.interval_id = setInterval(function() {
|
||||
node.emit("input", {});
|
||||
}, this.repeat);
|
||||
} else if (this.crontab) {
|
||||
if (RED.settings.verbose) {
|
||||
this.log(RED._("inject.crontab", this));
|
||||
}
|
||||
this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
|
||||
}
|
||||
};
|
||||
|
||||
if (this.once) {
|
||||
this.onceTimeout = setTimeout( function() {
|
||||
node.emit("input",{});
|
||||
node.repeaterSetup();
|
||||
}, this.onceDelay);
|
||||
} else {
|
||||
node.repeaterSetup();
|
||||
}
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
var errors = [];
|
||||
var props = this.props;
|
||||
if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
|
||||
props = msg.__user_inject_props__;
|
||||
}
|
||||
delete msg.__user_inject_props__;
|
||||
props.forEach(p => {
|
||||
var property = p.p;
|
||||
var value = p.v ? p.v : '';
|
||||
var valueType = p.vt ? p.vt : 'str';
|
||||
|
||||
if (!property) return;
|
||||
|
||||
if (valueType === "jsonata") {
|
||||
if (p.exp) {
|
||||
try {
|
||||
var val = RED.util.evaluateJSONataExpression(p.exp, msg);
|
||||
RED.util.setMessageProperty(msg, property, val, true);
|
||||
}
|
||||
catch (err) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
|
||||
} catch (err) {
|
||||
errors.push(err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
done(errors.join('; '));
|
||||
} else {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("inject",InjectNode);
|
||||
|
||||
InjectNode.prototype.close = function() {
|
||||
if (this.onceTimeout) {
|
||||
clearTimeout(this.onceTimeout);
|
||||
}
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
|
||||
} else if (this.cronjob != null) {
|
||||
this.cronjob.stop();
|
||||
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
|
||||
delete this.cronjob;
|
||||
}
|
||||
};
|
||||
|
||||
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
if (node != null) {
|
||||
try {
|
||||
if (req.body && req.body.__user_inject_props__) {
|
||||
node.receive(req.body);
|
||||
} else {
|
||||
node.receive();
|
||||
}
|
||||
res.sendStatus(200);
|
||||
} catch(err) {
|
||||
res.sendStatus(500);
|
||||
node.error(RED._("inject.failed",{error:err.toString()}));
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
}
|
@ -1,513 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="debug">
|
||||
<div class="form-row">
|
||||
<label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
|
||||
<input id="node-input-typed-complete" type="text" style="width: 70%">
|
||||
<input id="node-input-complete" type="hidden">
|
||||
<input id="node-input-targetType" type="hidden">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-tosidebar"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
|
||||
<label for="node-input-tosidebar" style="width:70%">
|
||||
<input type="checkbox" id="node-input-tosidebar" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toSidebar"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-console"> </label>
|
||||
<label for="node-input-console" style="width:70%">
|
||||
<input type="checkbox" id="node-input-console" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toConsole"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-tostatus"> </label>
|
||||
<label for="node-input-tostatus" style="width:70%">
|
||||
<input type="checkbox" id="node-input-tostatus" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toStatus"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" id="node-tostatus-line">
|
||||
<label for="node-input-typed-status"></label>
|
||||
<input id="node-input-typed-status" type="text" style="width: 70%">
|
||||
<input id="node-input-statusVal" type="hidden">
|
||||
<input id="node-input-statusType" type="hidden">
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script src="debug/view/debug-utils.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var subWindow = null;
|
||||
|
||||
function activateAjaxCall(node, active, successCallback) {
|
||||
var url;
|
||||
var body;
|
||||
|
||||
if (Array.isArray(node)) {
|
||||
url = "debug/"+(active?"enable":"disable");
|
||||
body = {nodes: node.map(function(n) { return n.id})}
|
||||
node = node[0];
|
||||
} else {
|
||||
url = "debug/"+node.id+"/"+(active?"enable":"disable");
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "POST",
|
||||
data: body,
|
||||
success: successCallback,
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
if (jqXHR.status == 404) {
|
||||
RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.not-deployed")}),"error");
|
||||
} else if (jqXHR.status === 0) {
|
||||
RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.no-response")}),"error");
|
||||
} else {
|
||||
// TODO where is the err.status comming from?
|
||||
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:err.status,message:err.response})}),"error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('debug',{
|
||||
category: 'common',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
active: {value:true},
|
||||
tosidebar: {value:true},
|
||||
console: {value:false},
|
||||
tostatus: {value:false},
|
||||
complete: {value:"false", required:true},
|
||||
targetType: {value:undefined},
|
||||
statusVal: {value:""},
|
||||
statusType: {value:"auto"}
|
||||
},
|
||||
label: function() {
|
||||
var suffix = "";
|
||||
if (this.console === true || this.console === "true") { suffix = " ⇲"; }
|
||||
if (this.targetType === "jsonata") {
|
||||
return (this.name || "JSONata") + suffix;
|
||||
}
|
||||
if (this.complete === true || this.complete === "true") {
|
||||
return (this.name||"msg") + suffix;
|
||||
} else {
|
||||
return (this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete)) + suffix;
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
color:"#87a980",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "debug.svg",
|
||||
align: "right",
|
||||
button: {
|
||||
toggle: "active",
|
||||
visible: function() { return this.tosidebar; },
|
||||
onclick: function() {
|
||||
var label = RED.utils.sanitize(this.name||"debug");
|
||||
var node = this;
|
||||
activateAjaxCall(node, node.active, function(resp, textStatus, xhr) {
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:node,
|
||||
changes:{
|
||||
active:!node.active
|
||||
},
|
||||
dirty:node.dirty,
|
||||
changed:node.changed,
|
||||
callback: function(ev) {
|
||||
activateAjaxCall(ev.node, ev.node.active);
|
||||
}
|
||||
};
|
||||
node.changed = true;
|
||||
node.dirty = true;
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push(historyEvent);
|
||||
RED.view.redraw();
|
||||
if (xhr.status == 200) {
|
||||
RED.notify(node._("debug.notification.activated",{label:label}),{type: "success", timeout: 2000});
|
||||
} else if (xhr.status == 201) {
|
||||
RED.notify(node._("debug.notification.deactivated",{label:label}),{type: "success", timeout: 2000});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onpaletteadd: function() {
|
||||
var options = {
|
||||
messageMouseEnter: function(sourceId) {
|
||||
if (sourceId) {
|
||||
var n = RED.nodes.node(sourceId);
|
||||
if (n) {
|
||||
n.highlighted = true;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
}
|
||||
},
|
||||
messageMouseLeave: function(sourceId) {
|
||||
if (sourceId) {
|
||||
var n = RED.nodes.node(sourceId);
|
||||
if (n) {
|
||||
n.highlighted = false;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
}
|
||||
},
|
||||
messageSourceClick: function(sourceId, aliasId, path) {
|
||||
// Get all of the nodes that could have logged this message
|
||||
var candidateNodes = [RED.nodes.node(sourceId)]
|
||||
if (path) {
|
||||
for (var i=2;i<path.length;i++) {
|
||||
candidateNodes.push(RED.nodes.node(path[i]))
|
||||
}
|
||||
}
|
||||
if (aliasId) {
|
||||
candidateNodes.push(RED.nodes.node(aliasId));
|
||||
}
|
||||
if (candidateNodes.length > 1) {
|
||||
// The node is in a subflow. Check to see if the active
|
||||
// workspace is a subflow in the node's parentage. If
|
||||
// so, reveal the relevant subflow instance node.
|
||||
var ws = RED.workspaces.active();
|
||||
for (var i=0;i<candidateNodes.length;i++) {
|
||||
if (candidateNodes[i].z === ws) {
|
||||
RED.view.reveal(candidateNodes[i].id);
|
||||
return
|
||||
}
|
||||
}
|
||||
// The active workspace is unrelated to the node. So
|
||||
// fall back to revealing the top most node
|
||||
}
|
||||
RED.view.reveal(candidateNodes[0].id);
|
||||
},
|
||||
clear: function() {
|
||||
RED.nodes.eachNode(function(node) {
|
||||
node.highlighted = false;
|
||||
node.dirty = true;
|
||||
});
|
||||
RED.view.redraw();
|
||||
}
|
||||
};
|
||||
|
||||
var uiComponents = RED.debug.init(options);
|
||||
|
||||
RED.sidebar.addTab({
|
||||
id: "debug",
|
||||
label: this._("debug.sidebar.label"),
|
||||
name: this._("debug.sidebar.name"),
|
||||
content: uiComponents.content,
|
||||
toolbar: uiComponents.footer,
|
||||
enableOnEdit: true,
|
||||
pinned: true,
|
||||
iconClass: "fa fa-bug",
|
||||
action: "core:show-debug-tab"
|
||||
});
|
||||
RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });
|
||||
|
||||
var that = this;
|
||||
RED._debug = function(msg) {
|
||||
that.handleDebugMessage("", {
|
||||
name:"debug",
|
||||
msg:msg
|
||||
});
|
||||
};
|
||||
|
||||
this.refreshMessageList = function() {
|
||||
RED.debug.refreshMessageList(RED.workspaces.active());
|
||||
if (subWindow) {
|
||||
try {
|
||||
subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
RED.events.on("workspace:change", this.refreshMessageList);
|
||||
|
||||
this.handleDebugMessage = function(t,o) {
|
||||
// console.log("->",o.id,o.z,o._alias);
|
||||
//
|
||||
// sourceNode should be the top-level node - one that is on a flow.
|
||||
var sourceNode;
|
||||
var pathParts;
|
||||
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
|
||||
|
||||
// If it has one id, that is a top level flow
|
||||
// each subsequent id is the instance id of a subflow node
|
||||
//
|
||||
pathParts = o.path.split("/");
|
||||
if (pathParts.length === 1) {
|
||||
// The source node is on a flow - so can use its id to find
|
||||
sourceNode = RED.nodes.node(o.id);
|
||||
} else if (pathParts.length > 1) {
|
||||
// Highlight the subflow instance node.
|
||||
sourceNode = RED.nodes.node(pathParts[1]);
|
||||
}
|
||||
} else {
|
||||
// This is probably redundant...
|
||||
sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
|
||||
}
|
||||
if (sourceNode) {
|
||||
o._source = {
|
||||
id:sourceNode.id,
|
||||
z:sourceNode.z,
|
||||
name:sourceNode.name,
|
||||
type:sourceNode.type,
|
||||
// _alias identifies the actual logging node. This is
|
||||
// not necessarily the same as sourceNode, which will be
|
||||
// the top-level subflow instance node.
|
||||
// This means the node's name is displayed in the sidebar.
|
||||
_alias:o._alias,
|
||||
path: pathParts
|
||||
};
|
||||
}
|
||||
RED.debug.handleDebugMessage(o);
|
||||
if (subWindow) {
|
||||
try {
|
||||
subWindow.postMessage({event:"message",msg:o},"*");
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
RED.comms.subscribe("debug",this.handleDebugMessage);
|
||||
|
||||
this.clearMessageList = function() {
|
||||
RED.debug.clearMessageList(true);
|
||||
if (subWindow) {
|
||||
try {
|
||||
subWindow.postMessage({event:"projectChange"},"*");
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
RED.events.on("project:change", this.clearMessageList);
|
||||
RED.actions.add("core:clear-debug-messages", function() { RED.debug.clearMessageList(true) });
|
||||
RED.actions.add("core:clear-filtered-debug-messages", function() { RED.debug.clearMessageList(true, true) });
|
||||
|
||||
RED.actions.add("core:activate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(true), true); });
|
||||
RED.actions.add("core:activate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, true),true); });
|
||||
RED.actions.add("core:activate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, false),true); });
|
||||
|
||||
RED.actions.add("core:deactivate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(false), false); });
|
||||
RED.actions.add("core:deactivate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, true),false); });
|
||||
RED.actions.add("core:deactivate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, false),false); });
|
||||
|
||||
function getSelectedDebugNodes(state) {
|
||||
var nodes = [];
|
||||
var selection = RED.view.selection();
|
||||
if (selection.nodes) {
|
||||
selection.nodes.forEach(function(n) {
|
||||
if (RED.nodes.subflow(n.z)) {
|
||||
return;
|
||||
}
|
||||
if (n.type === 'debug' && n.active !== state) {
|
||||
nodes.push(n);
|
||||
} else if (n.type === 'group') {
|
||||
nodes = nodes.concat( RED.group.getNodes(n,true).filter(function(n) {
|
||||
return n.type === 'debug' && n.active !== state
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
|
||||
}
|
||||
function getMatchingDebugNodes(state,globally) {
|
||||
var nodes = [];
|
||||
var filter = {type:"debug"};
|
||||
if (!globally) {
|
||||
filter.z = RED.workspaces.active();
|
||||
}
|
||||
var candidateNodes = RED.nodes.filterNodes(filter);
|
||||
nodes = candidateNodes.filter(function(n) {
|
||||
return n.active !== state && !RED.nodes.subflow(n.z)
|
||||
})
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function setDebugNodeState(nodes,state) {
|
||||
var historyEvents = [];
|
||||
if (nodes.length > 0) {
|
||||
activateAjaxCall(nodes,false, function(resp, textStatus, xhr) {
|
||||
nodes.forEach(function(n) {
|
||||
historyEvents.push({
|
||||
t: "edit",
|
||||
node: n,
|
||||
changed: n.changed,
|
||||
changes: {
|
||||
active: n.active
|
||||
}
|
||||
});
|
||||
n.active = state;
|
||||
n.changed = true;
|
||||
n.dirty = true;
|
||||
})
|
||||
RED.history.push({
|
||||
t: "multi",
|
||||
events: historyEvents,
|
||||
dirty: RED.nodes.dirty(),
|
||||
callback: function() {
|
||||
activateAjaxCall(nodes,nodes[0].active);
|
||||
}
|
||||
});
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$("#red-ui-sidebar-debug-open").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
|
||||
subWindow.onload = function() {
|
||||
subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
|
||||
};
|
||||
});
|
||||
RED.popover.tooltip($("#red-ui-sidebar-debug-open"),RED._('node-red:debug.sidebar.openWindow'));
|
||||
|
||||
|
||||
|
||||
$(window).on('beforeunload',function() {
|
||||
if (subWindow) {
|
||||
try {
|
||||
subWindow.close();
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.handleWindowMessage = function(evt) {
|
||||
var msg = evt.data;
|
||||
if (msg.event === "mouseEnter") {
|
||||
options.messageMouseEnter(msg.id);
|
||||
} else if (msg.event === "mouseLeave") {
|
||||
options.messageMouseLeave(msg.id);
|
||||
} else if (msg.event === "mouseClick") {
|
||||
options.messageSourceClick(msg.id,msg._alias,msg.path);
|
||||
} else if (msg.event === "clear") {
|
||||
options.clear();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message',this.handleWindowMessage);
|
||||
},
|
||||
onpaletteremove: function() {
|
||||
RED.comms.unsubscribe("debug",this.handleDebugMessage);
|
||||
RED.sidebar.removeTab("debug");
|
||||
RED.events.off("workspace:change", this.refreshMessageList);
|
||||
window.removeEventListener("message",this.handleWindowMessage);
|
||||
RED.actions.remove("core:show-debug-tab");
|
||||
RED.actions.remove("core:clear-debug-messages");
|
||||
delete RED._debug;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var autoType = {
|
||||
value: "auto",
|
||||
label: RED._("node-red:debug.autostatus"),
|
||||
hasValue: false
|
||||
};
|
||||
$("#node-input-typed-status").typedInput({
|
||||
default: "auto",
|
||||
types:[autoType, "msg", "jsonata"],
|
||||
typeField: $("#node-input-statusType")
|
||||
});
|
||||
var that = this;
|
||||
var none = {
|
||||
value: "none",
|
||||
label: RED._("node-red:debug.none"),
|
||||
hasValue: false
|
||||
};
|
||||
if (this.tosidebar === undefined) {
|
||||
this.tosidebar = true;
|
||||
$("#node-input-tosidebar").prop('checked', true);
|
||||
}
|
||||
if (this.statusVal === undefined) {
|
||||
this.statusVal = (this.complete === "false") ? "payload" : ((this.complete === "true") ? "payload" : this.complete+"");
|
||||
$("#node-input-typed-status").typedInput('value',this.statusVal || "");
|
||||
}
|
||||
if (this.statusType === undefined) {
|
||||
this.statusType = "auto";
|
||||
$("#node-input-typed-status").typedInput('type',this.statusType || "auto");
|
||||
}
|
||||
if (typeof this.console === "string") {
|
||||
this.console = (this.console == 'true');
|
||||
$("#node-input-console").prop('checked', this.console);
|
||||
$("#node-input-tosidebar").prop('checked', true);
|
||||
}
|
||||
var fullType = {
|
||||
value: "full",
|
||||
label: RED._("node-red:debug.msgobj"),
|
||||
hasValue: false
|
||||
};
|
||||
|
||||
$("#node-input-typed-complete").typedInput({
|
||||
default: "msg",
|
||||
types:['msg', fullType, "jsonata"],
|
||||
typeField: $("#node-input-targetType")
|
||||
});
|
||||
if (this.targetType === "jsonata") {
|
||||
var property = this.complete || "";
|
||||
$("#node-input-typed-complete").typedInput('type','jsonata');
|
||||
$("#node-input-typed-complete").typedInput('value',property);
|
||||
} else if ((this.targetType === "full") || this.complete === "true" || this.complete === true) {
|
||||
// show complete message object
|
||||
$("#node-input-typed-complete").typedInput('type','full');
|
||||
} else {
|
||||
var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
|
||||
$("#node-input-typed-complete").typedInput('type','msg');
|
||||
$("#node-input-typed-complete").typedInput('value',property);
|
||||
}
|
||||
$("#node-input-typed-complete").on('change',function() {
|
||||
if ($("#node-input-typed-complete").typedInput('type') === 'msg' &&
|
||||
$("#node-input-typed-complete").typedInput('value') === ''
|
||||
) {
|
||||
$("#node-input-typed-complete").typedInput('value','payload');
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-tostatus").on('change',function() {
|
||||
if ($(this).is(":checked")) {
|
||||
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');
|
||||
}
|
||||
that.statusType = "auto";
|
||||
that.statusVal = comp;
|
||||
}
|
||||
$("#node-input-typed-status").typedInput('type',that.statusType);
|
||||
$("#node-input-typed-status").typedInput('value',that.statusVal);
|
||||
$("#node-tostatus-line").show();
|
||||
}
|
||||
else {
|
||||
$("#node-tostatus-line").hide();
|
||||
that.statusType = "auto";
|
||||
that.statusVal = "";
|
||||
$("#node-input-typed-status").typedInput('type',that.statusType);
|
||||
$("#node-input-typed-status").typedInput('value',that.statusVal);
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var type = $("#node-input-typed-complete").typedInput('type');
|
||||
if (type === 'full') {
|
||||
$("#node-input-complete").val("true");
|
||||
} else {
|
||||
$("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
|
||||
}
|
||||
$("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,297 +0,0 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var events = require("events");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
var debuglength = RED.settings.debugMaxLength || 1000;
|
||||
var useColors = RED.settings.debugUseColors || false;
|
||||
util.inspect.styles.boolean = "red";
|
||||
|
||||
function DebugNode(n) {
|
||||
var hasEditExpression = (n.targetType === "jsonata");
|
||||
var editExpression = hasEditExpression ? n.complete : null;
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.complete = hasEditExpression ? null : (n.complete||"payload").toString();
|
||||
if (this.complete === "false") { this.complete = "payload"; }
|
||||
this.console = ""+(n.console || false);
|
||||
this.tostatus = n.tostatus || false;
|
||||
this.statusType = n.statusType || "auto";
|
||||
this.statusVal = n.statusVal || this.complete;
|
||||
this.tosidebar = n.tosidebar;
|
||||
if (this.tosidebar === undefined) { this.tosidebar = true; }
|
||||
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
|
||||
if (this.tostatus) {
|
||||
this.status({fill:"grey", shape:"ring"});
|
||||
this.oldState = "{}";
|
||||
}
|
||||
|
||||
var hasStatExpression = (n.statusType === "jsonata");
|
||||
var statExpression = hasStatExpression ? n.statusVal : null;
|
||||
|
||||
var node = this;
|
||||
var preparedEditExpression = null;
|
||||
var preparedStatExpression = null;
|
||||
if (editExpression) {
|
||||
try {
|
||||
preparedEditExpression = RED.util.prepareJSONataExpression(editExpression, this);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("debug.invalid-exp", {error: editExpression}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (statExpression) {
|
||||
try {
|
||||
preparedStatExpression = RED.util.prepareJSONataExpression(statExpression, this);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("debug.invalid-exp", {error: editExpression}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function prepareValue(msg, done) {
|
||||
// Either apply the jsonata expression or...
|
||||
if (preparedEditExpression) {
|
||||
RED.util.evaluateJSONataExpression(preparedEditExpression, msg, (err, value) => {
|
||||
if (err) { done(RED._("debug.invalid-exp", {error: editExpression})); }
|
||||
else { done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:value}); }
|
||||
});
|
||||
} else {
|
||||
// Extract the required message property
|
||||
var property = "payload";
|
||||
var output = msg[property];
|
||||
if (node.complete !== "false" && typeof node.complete !== "undefined") {
|
||||
property = node.complete;
|
||||
try { output = RED.util.getMessageProperty(msg,node.complete); }
|
||||
catch(err) { output = undefined; }
|
||||
}
|
||||
done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, property:property, msg:output});
|
||||
}
|
||||
}
|
||||
|
||||
function prepareStatus(msg, done) {
|
||||
if (node.statusType === "auto") {
|
||||
if (node.complete === "true") {
|
||||
done(null,{msg:msg.payload});
|
||||
}
|
||||
else {
|
||||
prepareValue(msg,function(err,debugMsg) {
|
||||
if (err) { node.error(err); return; }
|
||||
done(null,{msg:debugMsg.msg});
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Either apply the jsonata expression or...
|
||||
if (preparedStatExpression) {
|
||||
RED.util.evaluateJSONataExpression(preparedStatExpression, msg, (err, value) => {
|
||||
if (err) { done(RED._("debug.invalid-exp", {error:editExpression})); }
|
||||
else { done(null,{msg:value}); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Extract the required message property
|
||||
var output;
|
||||
try { output = RED.util.getMessageProperty(msg,node.statusVal); }
|
||||
catch(err) { output = undefined; }
|
||||
done(null,{msg:output});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.on("close", function() {
|
||||
if (this.oldState) {
|
||||
this.status({});
|
||||
}
|
||||
})
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("status") && msg.status.hasOwnProperty("source") && msg.status.source.hasOwnProperty("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' && output.hasOwnProperty("fill") && output.hasOwnProperty("shape") && output.hasOwnProperty("text")) {
|
||||
fill = output.fill;
|
||||
shape = output.shape;
|
||||
st = output.text;
|
||||
}
|
||||
if (node.statusType === "auto") {
|
||||
if (msg.hasOwnProperty("error")) {
|
||||
fill = "red";
|
||||
st = msg.error.message;
|
||||
}
|
||||
if (msg.hasOwnProperty("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 (this.complete === "true") {
|
||||
// debug complete msg object
|
||||
if (this.console === "true") {
|
||||
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
|
||||
}
|
||||
if (this.active && this.tosidebar) {
|
||||
sendDebug({id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:msg});
|
||||
}
|
||||
done();
|
||||
}
|
||||
else {
|
||||
prepareValue(msg,function(err,debugMsg) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
return;
|
||||
}
|
||||
var output = debugMsg.msg;
|
||||
if (node.console === "true") {
|
||||
if (typeof output === "string") {
|
||||
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
|
||||
} else if (typeof output === "object") {
|
||||
node.log("\n"+util.inspect(output, {colors:useColors, depth:10}));
|
||||
} else {
|
||||
node.log(util.inspect(output, {colors:useColors}));
|
||||
}
|
||||
}
|
||||
if (node.active) {
|
||||
if (node.tosidebar == true) {
|
||||
sendDebug(debugMsg);
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RED.nodes.registerType("debug",DebugNode, {
|
||||
settings: {
|
||||
debugUseColors: {
|
||||
value: false,
|
||||
},
|
||||
debugMaxLength: {
|
||||
value: 1000,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function sendDebug(msg) {
|
||||
// don't put blank errors in sidebar (but do add to logs)
|
||||
//if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
|
||||
msg = RED.util.encodeObject(msg,{maxLength:debuglength});
|
||||
RED.comms.publish("debug",msg);
|
||||
}
|
||||
|
||||
DebugNode.logHandler = new events.EventEmitter();
|
||||
DebugNode.logHandler.on("log",function(msg) {
|
||||
if (msg.level === RED.log.WARN || msg.level === RED.log.ERROR) {
|
||||
sendDebug(msg);
|
||||
}
|
||||
});
|
||||
RED.log.addHandler(DebugNode.logHandler);
|
||||
|
||||
function setNodeState(node,state) {
|
||||
if (state) {
|
||||
node.active = true;
|
||||
} else {
|
||||
node.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
RED.httpAdmin.post("/debug/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
|
||||
var state = req.params.state;
|
||||
if (state !== 'enable' && state !== 'disable') {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
var nodes = req.body && req.body.nodes;
|
||||
if (Array.isArray(nodes)) {
|
||||
nodes.forEach(function(id) {
|
||||
var node = RED.nodes.getNode(id);
|
||||
if (node !== null && typeof node !== "undefined" ) {
|
||||
setNodeState(node, state === "enable");
|
||||
}
|
||||
})
|
||||
res.sendStatus(state === "enable" ? 200 : 201);
|
||||
} else {
|
||||
res.sendStatus(400);
|
||||
}
|
||||
})
|
||||
|
||||
RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
|
||||
var state = req.params.state;
|
||||
if (state !== 'enable' && state !== 'disable') {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
if (node !== null && typeof node !== "undefined" ) {
|
||||
setNodeState(node,state === "enable");
|
||||
res.sendStatus(state === "enable" ? 200 : 201);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
let cachedDebugView;
|
||||
RED.httpAdmin.get("/debug/view/view.html", function(req,res) {
|
||||
if (!cachedDebugView) {
|
||||
fs.readFile(path.join(__dirname,"lib","debug","view.html")).then(data => {
|
||||
let customStyles = "";
|
||||
try {
|
||||
let customStyleList = RED.settings.editorTheme.page._.css || [];
|
||||
customStyleList.forEach(style => {
|
||||
customStyles += `<link rel="stylesheet" href="../../${style}">\n`
|
||||
})
|
||||
} catch(err) {}
|
||||
cachedDebugView = data.toString().replace("<!-- INSERT-THEME-CSS -->",customStyles)
|
||||
res.set('Content-Type', 'text/html');
|
||||
res.send(cachedDebugView).end();
|
||||
}).catch(err => {
|
||||
res.sendStatus(404);
|
||||
})
|
||||
} else {
|
||||
res.send(cachedDebugView).end();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// As debug/view/debug-utils.js is loaded via <script> tag, it won't get
|
||||
// the auth header attached. So do not use RED.auth.needsPermission here.
|
||||
RED.httpAdmin.get("/debug/view/*",function(req,res) {
|
||||
var options = {
|
||||
root: path.join(__dirname,"lib","debug"),
|
||||
dotfiles: 'deny'
|
||||
};
|
||||
try {
|
||||
res.sendFile(
|
||||
req.params[0],
|
||||
options,
|
||||
err => {
|
||||
if (err) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch(err) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
};
|
@ -1,154 +0,0 @@
|
||||
<script type="text/html" data-template-name="complete">
|
||||
<div class="form-row node-input-target-row">
|
||||
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
|
||||
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-complete-target-filter"></div>
|
||||
<div id="node-input-complete-target-container-div"></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">
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('complete',{
|
||||
category: 'common',
|
||||
color:"#c0edc0",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
scope: {value:[], type:"*[]"},
|
||||
uncaught: {value:false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "alert.svg",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
return this._("complete.completeNodes",{number:this.scope.length});
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var scope = node.scope || [];
|
||||
|
||||
this._resize = function() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.length;i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-list-row");
|
||||
editorRow.css("height",height+"px");
|
||||
};
|
||||
var search = $("#node-input-complete-target-filter").searchBox({
|
||||
style: "compact",
|
||||
delay: 300,
|
||||
change: function() {
|
||||
var val = $(this).val().trim().toLowerCase();
|
||||
if (val === "") {
|
||||
dirList.treeList("filter", null);
|
||||
search.searchBox("count","");
|
||||
} else {
|
||||
var count = dirList.treeList("filter", function(item) {
|
||||
return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
|
||||
});
|
||||
search.searchBox("count",count+" / "+candidateNodes.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dirList = $("#node-input-complete-target-container-div").css({width: "100%", height: "100%"})
|
||||
.treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
|
||||
item.node.highlighted = true;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}).on("treelistitemmouseout", function(e, item) {
|
||||
item.node.highlighted = false;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
})
|
||||
var candidateNodes = RED.nodes.filterNodes({z:node.z});
|
||||
var allChecked = true;
|
||||
var items = [];
|
||||
var nodeItemMap = {};
|
||||
|
||||
candidateNodes.forEach(function(n) {
|
||||
if (n.id === node.id) {
|
||||
return;
|
||||
}
|
||||
var isChecked = scope.indexOf(n.id) !== -1;
|
||||
|
||||
allChecked = allChecked && isChecked;
|
||||
|
||||
var nodeDef = RED.nodes.getType(n.type);
|
||||
var label;
|
||||
var sublabel;
|
||||
if (nodeDef) {
|
||||
var l = nodeDef.label;
|
||||
label = (typeof l === "function" ? l.call(n) : l)||"";
|
||||
sublabel = n.type;
|
||||
if (sublabel.indexOf("subflow:") === 0) {
|
||||
var subflowId = sublabel.substring(8);
|
||||
var subflow = RED.nodes.subflow(subflowId);
|
||||
sublabel = "subflow : "+subflow.name;
|
||||
}
|
||||
}
|
||||
if (!nodeDef || !label) {
|
||||
label = n.type;
|
||||
}
|
||||
nodeItemMap[n.id] = {
|
||||
node: n,
|
||||
label: label,
|
||||
sublabel: sublabel,
|
||||
selected: isChecked,
|
||||
checkbox: true
|
||||
};
|
||||
items.push(nodeItemMap[n.id]);
|
||||
});
|
||||
dirList.treeList('data',items);
|
||||
|
||||
$("#node-input-complete-target-select").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
|
||||
RED.tray.hide();
|
||||
RED.view.selectNodes({
|
||||
selected: preselected,
|
||||
onselect: function(selection) {
|
||||
RED.tray.show();
|
||||
var newlySelected = {};
|
||||
selection.forEach(function(n) {
|
||||
newlySelected[n.id] = true;
|
||||
if (nodeItemMap[n.id]) {
|
||||
nodeItemMap[n.id].treeList.select(true);
|
||||
}
|
||||
})
|
||||
preselected.forEach(function(id) {
|
||||
if (!newlySelected[id]) {
|
||||
nodeItemMap[id].treeList.select(false);
|
||||
}
|
||||
})
|
||||
},
|
||||
oncancel: function() {
|
||||
RED.tray.show();
|
||||
},
|
||||
filter: function(n) {
|
||||
return n.id !== node.id;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.scope = $("#node-input-complete-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
this._resize();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function CompleteNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.scope = n.scope;
|
||||
this.on("input",function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("complete",CompleteNode);
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="catch">
|
||||
<div class="form-row">
|
||||
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
|
||||
<select id="node-input-scope-select">
|
||||
<option value="all" data-i18n="catch.scope.all"></option>
|
||||
<option value="target" data-i18n="catch.scope.selected"></options>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-uncaught-row">
|
||||
<input type="checkbox" id="node-input-uncaught" style="display: inline-block; width: auto; vertical-align: top; margin-left: 30px; margin-right: 5px;">
|
||||
<label for="node-input-uncaught" style="width: auto" data-i18n="catch.label.uncaught"></label>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row">
|
||||
<button id="node-input-catch-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
|
||||
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-catch-target-filter"></div>
|
||||
<div id="node-input-catch-target-container-div"></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">
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('catch',{
|
||||
category: 'common',
|
||||
color:"#e49191",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
scope: {value:null, type:"*[]"},
|
||||
uncaught: {value:false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "alert.svg",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.scope) {
|
||||
return this._("catch.catchNodes",{number:this.scope.length});
|
||||
}
|
||||
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var scope = node.scope || [];
|
||||
|
||||
this._resize = function() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.length;i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-list-row");
|
||||
editorRow.css("height",height+"px");
|
||||
};
|
||||
var search = $("#node-input-catch-target-filter").searchBox({
|
||||
style: "compact",
|
||||
delay: 300,
|
||||
change: function() {
|
||||
var val = $(this).val().trim().toLowerCase();
|
||||
if (val === "") {
|
||||
dirList.treeList("filter", null);
|
||||
search.searchBox("count","");
|
||||
} else {
|
||||
var count = dirList.treeList("filter", function(item) {
|
||||
return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
|
||||
});
|
||||
search.searchBox("count",count+" / "+candidateNodes.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
var dirList = $("#node-input-catch-target-container-div").css({width: "100%", height: "100%"})
|
||||
.treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
|
||||
item.node.highlighted = true;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}).on("treelistitemmouseout", function(e, item) {
|
||||
item.node.highlighted = false;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
})
|
||||
var candidateNodes = RED.nodes.filterNodes({z:node.z});
|
||||
var allChecked = true;
|
||||
var items = [];
|
||||
var nodeItemMap = {};
|
||||
|
||||
candidateNodes.forEach(function(n) {
|
||||
if (n.id === node.id) {
|
||||
return;
|
||||
}
|
||||
var isChecked = scope.indexOf(n.id) !== -1;
|
||||
|
||||
allChecked = allChecked && isChecked;
|
||||
|
||||
var nodeDef = RED.nodes.getType(n.type);
|
||||
var label;
|
||||
var sublabel;
|
||||
if (nodeDef) {
|
||||
var l = nodeDef.label;
|
||||
label = (typeof l === "function" ? l.call(n) : l)||"";
|
||||
sublabel = n.type;
|
||||
if (sublabel.indexOf("subflow:") === 0) {
|
||||
var subflowId = sublabel.substring(8);
|
||||
var subflow = RED.nodes.subflow(subflowId);
|
||||
sublabel = "subflow : "+subflow.name;
|
||||
}
|
||||
}
|
||||
if (!nodeDef || !label) {
|
||||
label = n.type;
|
||||
}
|
||||
nodeItemMap[n.id] = {
|
||||
node: n,
|
||||
label: label,
|
||||
sublabel: sublabel,
|
||||
selected: isChecked,
|
||||
checkbox: true
|
||||
};
|
||||
items.push(nodeItemMap[n.id]);
|
||||
});
|
||||
dirList.treeList('data',items);
|
||||
|
||||
$("#node-input-catch-target-select").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
|
||||
RED.tray.hide();
|
||||
RED.view.selectNodes({
|
||||
selected: preselected,
|
||||
onselect: function(selection) {
|
||||
RED.tray.show();
|
||||
var newlySelected = {};
|
||||
selection.forEach(function(n) {
|
||||
newlySelected[n.id] = true;
|
||||
if (nodeItemMap[n.id]) {
|
||||
nodeItemMap[n.id].treeList.select(true);
|
||||
}
|
||||
})
|
||||
preselected.forEach(function(id) {
|
||||
if (!newlySelected[id]) {
|
||||
nodeItemMap[id].treeList.select(false);
|
||||
}
|
||||
})
|
||||
},
|
||||
oncancel: function() {
|
||||
RED.tray.show();
|
||||
},
|
||||
filter: function(n) {
|
||||
return n.id !== node.id;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
$("#node-input-scope-select").on("change", function(e) {
|
||||
var scope = $(this).val();
|
||||
if (scope === "target") {
|
||||
$(".node-input-target-row").show();
|
||||
$(".node-input-uncaught-row").hide();
|
||||
} else {
|
||||
$(".node-input-target-row").hide();
|
||||
$(".node-input-uncaught-row").show();
|
||||
}
|
||||
node._resize();
|
||||
});
|
||||
if (this.scope === null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
} else {
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
$("#node-input-scope-select").trigger("change");
|
||||
},
|
||||
oneditsave: function() {
|
||||
var scope = $("#node-input-scope-select").val();
|
||||
if (scope === 'all') {
|
||||
this.scope = null;
|
||||
} else {
|
||||
$("#node-input-uncaught").prop("checked",false);
|
||||
this.scope = $("#node-input-catch-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
||||
}
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
this._resize();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function CatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.scope = n.scope;
|
||||
this.uncaught = n.uncaught;
|
||||
this.on("input",function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("catch",CatchNode);
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="status">
|
||||
<div class="form-row">
|
||||
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
|
||||
<select id="node-input-scope-select">
|
||||
<option value="all" data-i18n="status.scope.all"></option>
|
||||
<option value="target" data-i18n="status.scope.selected"></options>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row">
|
||||
<button id="node-input-status-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
|
||||
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-status-target-filter"></div>
|
||||
<div id="node-input-status-target-container-div"></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">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('status',{
|
||||
category: 'common',
|
||||
color:"#94c1d0",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
scope: {value:null, type:"*[]"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "status.svg",
|
||||
label: function() {
|
||||
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var scope = node.scope || [];
|
||||
this._resize = function() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.length;i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-list-row");
|
||||
editorRow.css("height",height+"px");
|
||||
};
|
||||
var search = $("#node-input-status-target-filter").searchBox({
|
||||
style: "compact",
|
||||
delay: 300,
|
||||
change: function() {
|
||||
var val = $(this).val().trim().toLowerCase();
|
||||
if (val === "") {
|
||||
dirList.treeList("filter", null);
|
||||
search.searchBox("count","");
|
||||
} else {
|
||||
var count = dirList.treeList("filter", function(item) {
|
||||
return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
|
||||
});
|
||||
search.searchBox("count",count+" / "+candidateNodes.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dirList = $("#node-input-status-target-container-div").css({width: "100%", height: "100%"})
|
||||
.treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
|
||||
item.node.highlighted = true;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}).on("treelistitemmouseout", function(e, item) {
|
||||
item.node.highlighted = false;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
})
|
||||
var candidateNodes = RED.nodes.filterNodes({z:node.z});
|
||||
var allChecked = true;
|
||||
var items = [];
|
||||
var nodeItemMap = {};
|
||||
|
||||
candidateNodes.forEach(function(n) {
|
||||
if (n.id === node.id) {
|
||||
return;
|
||||
}
|
||||
var isChecked = scope.indexOf(n.id) !== -1;
|
||||
|
||||
allChecked = allChecked && isChecked;
|
||||
|
||||
var nodeDef = RED.nodes.getType(n.type);
|
||||
var label;
|
||||
var sublabel;
|
||||
if (nodeDef) {
|
||||
var l = nodeDef.label;
|
||||
label = (typeof l === "function" ? l.call(n) : l)||"";
|
||||
sublabel = n.type;
|
||||
if (sublabel.indexOf("subflow:") === 0) {
|
||||
var subflowId = sublabel.substring(8);
|
||||
var subflow = RED.nodes.subflow(subflowId);
|
||||
sublabel = "subflow : "+subflow.name;
|
||||
}
|
||||
}
|
||||
if (!nodeDef || !label) {
|
||||
label = n.type;
|
||||
}
|
||||
nodeItemMap[n.id] = {
|
||||
node: n,
|
||||
label: label,
|
||||
sublabel: sublabel,
|
||||
selected: isChecked,
|
||||
checkbox: true
|
||||
};
|
||||
items.push(nodeItemMap[n.id]);
|
||||
});
|
||||
dirList.treeList('data',items);
|
||||
|
||||
$("#node-input-status-target-select").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
|
||||
RED.tray.hide();
|
||||
RED.view.selectNodes({
|
||||
selected: preselected,
|
||||
onselect: function(selection) {
|
||||
RED.tray.show();
|
||||
var newlySelected = {};
|
||||
selection.forEach(function(n) {
|
||||
newlySelected[n.id] = true;
|
||||
if (nodeItemMap[n.id]) {
|
||||
nodeItemMap[n.id].treeList.select(true);
|
||||
}
|
||||
})
|
||||
preselected.forEach(function(id) {
|
||||
if (!newlySelected[id]) {
|
||||
nodeItemMap[id].treeList.select(false);
|
||||
}
|
||||
})
|
||||
},
|
||||
oncancel: function() {
|
||||
RED.tray.show();
|
||||
},
|
||||
filter: function(n) {
|
||||
return n.id !== node.id;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
$("#node-input-scope-select").on("change", function(e) {
|
||||
var scope = $(this).val();
|
||||
if (scope === "target") {
|
||||
$(".node-input-target-row").show();
|
||||
} else {
|
||||
$(".node-input-target-row").hide();
|
||||
}
|
||||
node._resize();
|
||||
});
|
||||
if (this.scope === null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
} else {
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
$("#node-input-scope-select").trigger("change");
|
||||
},
|
||||
oneditsave: function() {
|
||||
var scope = $("#node-input-scope-select").val();
|
||||
if (scope === 'all') {
|
||||
this.scope = null;
|
||||
} else {
|
||||
this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
||||
}
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
this._resize();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function StatusNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.scope = n.scope;
|
||||
this.on("input", function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("status",StatusNode);
|
||||
}
|
@ -1,340 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="link in">
|
||||
<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 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>
|
||||
</script>
|
||||
<script type="text/html" data-template-name="link out">
|
||||
<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-row">
|
||||
<label for="node-input-mode"><span data-i18n="link.outMode"></span></label>
|
||||
<select id="node-input-mode" style="width: 70%">
|
||||
<option value="link" selected data-i18n="link.sendToAll"></option>
|
||||
<option value="return" data-i18n="link.returnToCaller"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="node-input-link-rows" 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 node-input-link-rows"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="link call">
|
||||
<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-row">
|
||||
<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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
var treeList;
|
||||
|
||||
function onEditPrepare(node,targetType) {
|
||||
if (!node.links) {
|
||||
node.links = [];
|
||||
}
|
||||
node.oldLinks = [];
|
||||
|
||||
var activeSubflow = RED.nodes.subflow(node.z);
|
||||
|
||||
treeList = $("<div>")
|
||||
.css({width: "100%", height: "100%"})
|
||||
.appendTo(".node-input-link-row")
|
||||
.treeList({autoSelect:false})
|
||||
.on('treelistitemmouseover',function(e,item) {
|
||||
if (item.node) {
|
||||
item.node.highlighted = true;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
})
|
||||
.on('treelistitemmouseout',function(e,item) {
|
||||
if (item.node) {
|
||||
item.node.highlighted = false;
|
||||
item.node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
});
|
||||
var candidateNodes = RED.nodes.filterNodes({type:targetType});
|
||||
var candidateNodesCount = 0;
|
||||
|
||||
var search = $("#node-input-link-target-filter").searchBox({
|
||||
style: "compact",
|
||||
delay: 300,
|
||||
change: function() {
|
||||
var val = $(this).val().trim().toLowerCase();
|
||||
if (val === "") {
|
||||
treeList.treeList("filter", null);
|
||||
search.searchBox("count","");
|
||||
} else {
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var flows = [];
|
||||
var flowMap = {};
|
||||
|
||||
if (activeSubflow) {
|
||||
flowMap[activeSubflow.id] = {
|
||||
id: activeSubflow.id,
|
||||
class: 'red-ui-palette-header',
|
||||
label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
|
||||
expanded: true,
|
||||
children: []
|
||||
};
|
||||
flows.push(flowMap[activeSubflow.id])
|
||||
} else {
|
||||
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 ? " *":""),
|
||||
expanded: true,
|
||||
children: []
|
||||
}
|
||||
flows.push(flowMap[ws.id])
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
var isChecked = false;
|
||||
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,
|
||||
selected: isChecked,
|
||||
checkbox: node.type !== "link call",
|
||||
radio: node.type === "link call"
|
||||
})
|
||||
candidateNodesCount++;
|
||||
}
|
||||
});
|
||||
flows = flows.filter(function(f) { return f.children.length > 0 })
|
||||
treeList.treeList('data',flows);
|
||||
setTimeout(function() {
|
||||
treeList.treeList('show',node.z);
|
||||
},100);
|
||||
}
|
||||
|
||||
function resizeNodeList() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-link-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.length;i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-link-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-input-link-row").css("height",height+"px");
|
||||
}
|
||||
|
||||
function onEditSave(node) {
|
||||
var flows = treeList.treeList('data');
|
||||
node.links = [];
|
||||
if (node.type !== "link out" || $("#node-input-mode").val() === 'link') {
|
||||
flows.forEach(function(f) {
|
||||
f.children.forEach(function(n) {
|
||||
if (n.selected) {
|
||||
node.links.push(n.id);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
node.oldLinks.sort();
|
||||
node.links.sort();
|
||||
|
||||
if (node.type === "link call") {
|
||||
return
|
||||
}
|
||||
|
||||
var nodeMap = {};
|
||||
var length = Math.max(node.oldLinks.length,node.links.length);
|
||||
for (var i=0;i<length;i++) {
|
||||
if (i<node.oldLinks.length) {
|
||||
nodeMap[node.oldLinks[i]] = nodeMap[node.oldLinks[i]]||{};
|
||||
nodeMap[node.oldLinks[i]].old = true;
|
||||
}
|
||||
if (i<node.links.length) {
|
||||
nodeMap[node.links[i]] = nodeMap[node.links[i]]||{};
|
||||
nodeMap[node.links[i]].new = true;
|
||||
}
|
||||
}
|
||||
var n;
|
||||
for (var id in nodeMap) {
|
||||
if (nodeMap.hasOwnProperty(id)) {
|
||||
n = RED.nodes.node(id);
|
||||
if (n) {
|
||||
if (nodeMap[id].old && !nodeMap[id].new) {
|
||||
// Removed id
|
||||
i = n.links.indexOf(node.id);
|
||||
if (i > -1) {
|
||||
n.links.splice(i,1);
|
||||
}
|
||||
} else if (!nodeMap[id].old && nodeMap[id].new) {
|
||||
// Added id
|
||||
i = n.links.indexOf(id);
|
||||
if (i === -1) {
|
||||
n.links.push(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onAdd() {
|
||||
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) {
|
||||
n.links.push(this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType('link in',{
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
links: { value: [], type:"link out[]" }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "link-out.svg",
|
||||
outputLabels: function(i) {
|
||||
return this.name||this._("link.linkIn");
|
||||
},
|
||||
showLabel: false,
|
||||
label: function() {
|
||||
return this.name||this._("link.linkIn");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
onEditPrepare(this,"link out");
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
// In case the name has changed, ensure any link call nodes on this
|
||||
// tab are redrawn with the updated name
|
||||
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
|
||||
localCallNodes.forEach(function(node) {
|
||||
node.dirty = true;
|
||||
});
|
||||
},
|
||||
onadd: onAdd,
|
||||
oneditresize: resizeNodeList
|
||||
});
|
||||
|
||||
RED.nodes.registerType('link call',{
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
links: { value: [], type:"link in[]"},
|
||||
timeout: { value: "30", validate:RED.validators.number(true) }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "link-call.svg",
|
||||
inputLabels: function(i) {
|
||||
return this.name||this._("link.linkCall");
|
||||
},
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.links.length > 0) {
|
||||
var targetNode = RED.nodes.node(this.links[0]);
|
||||
return targetNode && (targetNode.name || this._("link.linkCall"));
|
||||
}
|
||||
return this._("inject.none");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
onEditPrepare(this,"link in");
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
},
|
||||
oneditresize: resizeNodeList
|
||||
});
|
||||
|
||||
RED.nodes.registerType('link out',{
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
mode: { value: "link" },// link || return
|
||||
links: { value: [], type:"link in[]"}
|
||||
},
|
||||
align:"right",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: function() {
|
||||
if (this.mode === "return") {
|
||||
return "link-return.svg";
|
||||
} else {
|
||||
return "link-out.svg";
|
||||
}
|
||||
},
|
||||
inputLabels: function(i) {
|
||||
return this.name||(this.mode === "return" ?this._("link.linkOutReturn"):this._("link.linkOut"));
|
||||
},
|
||||
showLabel: false,
|
||||
label: function() {
|
||||
return this.name||(this.mode === "return" ?this._("link.linkOutReturn"):this._("link.linkOut"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
onEditPrepare(this,"link in");
|
||||
$("#node-input-mode").on("change", function() {
|
||||
$(".node-input-link-rows").toggle(this.value === "link")
|
||||
})
|
||||
if (!this.mode) {
|
||||
$("#node-input-mode").val('link').trigger("change");
|
||||
}
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
},
|
||||
onadd: onAdd,
|
||||
oneditresize: resizeNodeList
|
||||
});
|
||||
|
||||
|
||||
|
||||
})();
|
||||
</script>
|
@ -1,132 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
function LinkInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
var event = "node:"+n.id;
|
||||
var handler = function(msg) {
|
||||
msg._event = n.event;
|
||||
node.receive(msg);
|
||||
}
|
||||
RED.events.on(event,handler);
|
||||
this.on("input", function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
this.on("close",function() {
|
||||
RED.events.removeListener(event,handler);
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("link in",LinkInNode);
|
||||
|
||||
function LinkOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
var mode = n.mode || "link";
|
||||
|
||||
var event = "node:"+n.id;
|
||||
this.on("input", function(msg, send, done) {
|
||||
msg._event = event;
|
||||
RED.events.emit(event,msg)
|
||||
|
||||
if (mode === "return") {
|
||||
if (Array.isArray(msg._linkSource) && msg._linkSource.length > 0) {
|
||||
var messageEvent = msg._linkSource.pop();
|
||||
var returnNode = RED.nodes.getNode(messageEvent.node);
|
||||
if (returnNode && returnNode.returnLinkMessage) {
|
||||
returnNode.returnLinkMessage(messageEvent.id, msg);
|
||||
} else {
|
||||
node.warn(RED._("link.error.missingReturn"))
|
||||
}
|
||||
} else {
|
||||
node.warn(RED._("link.error.missingReturn"))
|
||||
}
|
||||
done();
|
||||
} else if (mode === "link") {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("link out",LinkOutNode);
|
||||
|
||||
|
||||
function LinkCallNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
const node = this;
|
||||
const target = n.links[0];
|
||||
const messageEvents = {};
|
||||
let timeout = parseFloat(n.timeout || "30")*1000;
|
||||
if (isNaN(timeout)) {
|
||||
timeout = 30000;
|
||||
}
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
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);
|
||||
var targetNode = RED.nodes.getNode(target);
|
||||
if (targetNode) {
|
||||
targetNode.receive(msg);
|
||||
}
|
||||
});
|
||||
|
||||
this.returnLinkMessage = function(eventId, msg) {
|
||||
if (Array.isArray(msg._linkSource) && msg._linkSource.length === 0) {
|
||||
delete msg._linkSource;
|
||||
}
|
||||
const messageEvent = messageEvents[eventId];
|
||||
if (messageEvent) {
|
||||
clearTimeout(messageEvent.ts);
|
||||
delete messageEvents[eventId];
|
||||
messageEvent.send(msg);
|
||||
messageEvent.done();
|
||||
} else {
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function timeoutMessage(eventId) {
|
||||
const messageEvent = messageEvents[eventId];
|
||||
if (messageEvent) {
|
||||
delete messageEvents[eventId];
|
||||
node.error("timeout",messageEvent.msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
RED.nodes.registerType("link call",LinkCallNode);
|
||||
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="unknown">
|
||||
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('unknown',{
|
||||
category: 'unknown',
|
||||
color:"#fff0f0",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "",
|
||||
label: function() {
|
||||
return "("+this.name+")"||this._("unknown.label.unknown");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return "node_label_unknown";
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function UnknownNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
}
|
||||
RED.nodes.registerType("unknown",UnknownNode);
|
||||
}
|
@ -1,609 +0,0 @@
|
||||
<script type="text/html" data-template-name="function">
|
||||
<style>
|
||||
.func-tabs-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-container {
|
||||
padding: 0px;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-container li {
|
||||
padding:0px;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-item-remove {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
#node-input-libs-container-row .red-ui-editableList-header {
|
||||
display: flex;
|
||||
background: var(--red-ui-tertiary-background);
|
||||
padding-right: 75px;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-header > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.node-libs-entry {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.node-libs-entry .red-ui-typedInput-container {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
.node-libs-entry .red-ui-typedInput-type-select {
|
||||
border-radius: 0 !important;
|
||||
height: 34px;
|
||||
}
|
||||
.node-libs-entry > span > input[type=text] {
|
||||
border-radius: 0;
|
||||
border-top-color: var(--red-ui-form-background);
|
||||
border-bottom-color: var(--red-ui-form-background);
|
||||
border-right-color: var(--red-ui-form-background);
|
||||
}
|
||||
.node-libs-entry > span > input[type=text].input-error {
|
||||
}
|
||||
.node-libs-entry > span {
|
||||
flex-grow: 1;
|
||||
width: 50%;
|
||||
position: relative;
|
||||
}
|
||||
.node-libs-entry span .node-input-libs-var, .node-libs-entry span .red-ui-typedInput-container {
|
||||
width: 100%;
|
||||
}
|
||||
.node-libs-entry > span > span > i {
|
||||
display: none;
|
||||
}
|
||||
.node-libs-entry > span > span.input-error > i {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
</style>
|
||||
<input type="hidden" id="node-input-func">
|
||||
<input type="hidden" id="node-input-noerr">
|
||||
<input type="hidden" id="node-input-finalize">
|
||||
<input type="hidden" id="node-input-initialize">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-row func-tabs-row">
|
||||
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
|
||||
</div>
|
||||
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
||||
|
||||
<div id="func-tab-config" style="display:none">
|
||||
<div class="form-row">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
||||
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-libs-row hide" id="node-input-libs-container-row">
|
||||
<ol id="node-input-libs-container"></ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-init" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-body" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 220px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-finalize" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function() {
|
||||
|
||||
var invalidModuleVNames = [
|
||||
'console',
|
||||
'util',
|
||||
'Buffer',
|
||||
'Date',
|
||||
'RED',
|
||||
'node',
|
||||
'__node__',
|
||||
'context',
|
||||
'flow',
|
||||
'global',
|
||||
'env',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
'setInterval',
|
||||
'clearInterval',
|
||||
'promisify'
|
||||
]
|
||||
|
||||
var knownFunctionNodes = {};
|
||||
RED.events.on("nodes:add", function(n) {
|
||||
if (n.type === "function") {
|
||||
knownFunctionNodes[n.id] = n;
|
||||
}
|
||||
})
|
||||
RED.events.on("nodes:remove", function(n) {
|
||||
if (n.type === "function") {
|
||||
delete knownFunctionNodes[n.id];
|
||||
}
|
||||
})
|
||||
|
||||
var missingModules = [];
|
||||
var missingModuleReasons = {};
|
||||
RED.events.on("runtime-state", function(event) {
|
||||
if (event.error === "missing-modules") {
|
||||
missingModules = event.modules.map(function(m) { missingModuleReasons[m.module] = m.error; return m.module });
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||
RED.editor.validateNode(knownFunctionNodes[id])
|
||||
}
|
||||
}
|
||||
} else if (!event.text) {
|
||||
missingModuleReasons = {};
|
||||
missingModules = [];
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||
RED.editor.validateNode(knownFunctionNodes[id])
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
var installAllowList = ['*'];
|
||||
var installDenyList = [];
|
||||
|
||||
var modulesEnabled = true;
|
||||
if (RED.settings.get('externalModules.modules.allowInstall', true) === false) {
|
||||
modulesEnabled = false;
|
||||
}
|
||||
var settingsAllowList = RED.settings.get("externalModules.modules.allowList")
|
||||
var settingsDenyList = RED.settings.get("externalModules.modules.denyList")
|
||||
if (settingsAllowList || settingsDenyList) {
|
||||
installAllowList = settingsAllowList;
|
||||
installDenyList = settingsDenyList
|
||||
}
|
||||
installAllowList = RED.utils.parseModuleList(installAllowList);
|
||||
installDenyList = RED.utils.parseModuleList(installDenyList);
|
||||
|
||||
|
||||
// object that maps from library name to its descriptor
|
||||
var allLibs = [];
|
||||
|
||||
function moduleName(module) {
|
||||
var match = /^([^@]+)@(.+)/.exec(module);
|
||||
if (match) {
|
||||
return [match[1], match[2]];
|
||||
}
|
||||
return [module, undefined];
|
||||
}
|
||||
|
||||
function getAllUsedModules() {
|
||||
var moduleSet = new Set();
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id)) {
|
||||
if (knownFunctionNodes[id].libs) {
|
||||
for (var i=0, l=knownFunctionNodes[id].libs.length; i<l; i++) {
|
||||
if (RED.utils.checkModuleAllowed(knownFunctionNodes[id].libs[i].module,null,installAllowList,installDenyList)) {
|
||||
moduleSet.add(knownFunctionNodes[id].libs[i].module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var modules = Array.from(moduleSet);
|
||||
modules.sort();
|
||||
return modules;
|
||||
}
|
||||
|
||||
function prepareLibraryConfig(node) {
|
||||
$(".node-input-libs-row").show();
|
||||
var usedModules = getAllUsedModules();
|
||||
var typedModules = usedModules.map(function(l) {
|
||||
return {icon:"fa fa-cube", value:l,label:l,hasValue:false}
|
||||
})
|
||||
typedModules.push({
|
||||
value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
|
||||
})
|
||||
|
||||
var libList = $("#node-input-libs-container").css('min-height','100px').css('min-width','450px').editableList({
|
||||
header: $('<div><div data-i18n="node-red:function.require.moduleName"></div><div data-i18n="node-red:function.require.importAs"></div></div>'),
|
||||
addItem: function(container,i,opt) {
|
||||
var parent = container.parent();
|
||||
var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container);
|
||||
var fmoduleSpan = $("<span>").appendTo(row0);
|
||||
var fmodule = $("<input/>", {
|
||||
class: "node-input-libs-val",
|
||||
placeholder: RED._("node-red:function.require.module"),
|
||||
type: "text"
|
||||
}).css({
|
||||
}).appendTo(fmoduleSpan).typedInput({
|
||||
types: typedModules,
|
||||
default: usedModules.indexOf(opt.module) > -1 ? opt.module : "_custom_"
|
||||
});
|
||||
if (usedModules.indexOf(opt.module) === -1) {
|
||||
fmodule.typedInput('value', opt.module);
|
||||
}
|
||||
var moduleWarning = $('<span style="position: absolute;right:2px;top:7px; display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fmoduleSpan);
|
||||
RED.popover.tooltip(moduleWarning.find("i"),function() {
|
||||
var val = fmodule.typedInput("type");
|
||||
if (val === "_custom_") {
|
||||
val = fmodule.val();
|
||||
}
|
||||
var errors = [];
|
||||
|
||||
if (!RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList)) {
|
||||
return RED._("node-red:function.error.moduleNotAllowed",{module:val});
|
||||
} else {
|
||||
return RED._("node-red:function.error.moduleLoadError",{module:val,error:missingModuleReasons[val]});
|
||||
}
|
||||
})
|
||||
|
||||
var fvarSpan = $("<span>").appendTo(row0);
|
||||
|
||||
var fvar = $("<input/>", {
|
||||
class: "node-input-libs-var red-ui-font-code",
|
||||
placeholder: RED._("node-red:function.require.var"),
|
||||
type: "text"
|
||||
}).css({
|
||||
}).appendTo(fvarSpan).val(opt.var);
|
||||
var vnameWarning = $('<span style="position: absolute; right:2px;top:7px;display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fvarSpan);
|
||||
RED.popover.tooltip(vnameWarning.find("i"),function() {
|
||||
var val = fvar.val();
|
||||
if (invalidModuleVNames.indexOf(val) !== -1) {
|
||||
return RED._("node-red:function.error.moduleNameReserved",{name:val})
|
||||
} else {
|
||||
return RED._("node-red:function.error.moduleNameError",{name:val})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
fvar.on("change keyup paste", function (e) {
|
||||
var v = $(this).val().trim();
|
||||
if (v === "" || / /.test(v) || invalidModuleVNames.indexOf(v) !== -1) {
|
||||
fvar.addClass("input-error");
|
||||
vnameWarning.addClass("input-error");
|
||||
} else {
|
||||
fvar.removeClass("input-error");
|
||||
vnameWarning.removeClass("input-error");
|
||||
}
|
||||
});
|
||||
|
||||
fmodule.on("change keyup paste", function (e) {
|
||||
var val = $(this).typedInput("type");
|
||||
if (val === "_custom_") {
|
||||
val = $(this).val();
|
||||
}
|
||||
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
|
||||
fvar.val(varName);
|
||||
fvar.trigger("change");
|
||||
|
||||
if (RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList) && (missingModules.indexOf(val) === -1)) {
|
||||
fmodule.removeClass("input-error");
|
||||
moduleWarning.removeClass("input-error");
|
||||
} else {
|
||||
fmodule.addClass("input-error");
|
||||
moduleWarning.addClass("input-error");
|
||||
}
|
||||
});
|
||||
if (RED.utils.checkModuleAllowed(opt.module,null,installAllowList,installDenyList) && (missingModules.indexOf(opt.module) === -1)) {
|
||||
fmodule.removeClass("input-error");
|
||||
moduleWarning.removeClass("input-error");
|
||||
} else {
|
||||
fmodule.addClass("input-error");
|
||||
moduleWarning.addClass("input-error");
|
||||
}
|
||||
if (opt.var) {
|
||||
fvar.trigger("change");
|
||||
}
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
|
||||
var libs = node.libs || [];
|
||||
for (var i=0,l=libs.length;i<l; i++) {
|
||||
libList.editableList('addItem',libs[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getLibsList() {
|
||||
var _libs = [];
|
||||
if (RED.settings.functionExternalModules !== false) {
|
||||
var libs = $("#node-input-libs-container").editableList("items");
|
||||
libs.each(function(i) {
|
||||
var item = $(this);
|
||||
var v = item.find(".node-input-libs-var").val();
|
||||
var n = item.find(".node-input-libs-val").typedInput("type");
|
||||
if (n === "_custom_") {
|
||||
n = item.find(".node-input-libs-val").val();
|
||||
}
|
||||
if ((!v || (v === "")) ||
|
||||
(!n || (n === ""))) {
|
||||
return;
|
||||
}
|
||||
_libs.push({
|
||||
var: v,
|
||||
module: n
|
||||
});
|
||||
});
|
||||
}
|
||||
return _libs;
|
||||
}
|
||||
|
||||
RED.nodes.registerType('function',{
|
||||
color:"#fdd0a2",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||
initialize: {value:""},
|
||||
finalize: {value:""},
|
||||
libs: {value: [], validate: function(v) {
|
||||
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
|
||||
}
|
||||
if (m.var === "" || / /.test(m.var)) {
|
||||
return false;
|
||||
}
|
||||
if (missingModules.indexOf(m.module) > -1) {
|
||||
return false;
|
||||
}
|
||||
if (invalidModuleVNames.indexOf(m.var) !== -1){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "function.svg",
|
||||
label: function() {
|
||||
return this.name||this._("function.function");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
|
||||
var tabs = RED.tabs.create({
|
||||
id: "func-tabs",
|
||||
onchange: function(tab) {
|
||||
$("#func-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
let editor = $("#" + tab.id).find('.monaco-editor').first();
|
||||
if(editor.length) {
|
||||
if(that.editor.nodered && that.editor.type == "monaco") {
|
||||
that.editor.nodered.refreshModuleLibs(getLibsList());
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
}
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-config",
|
||||
iconClass: "fa fa-cog",
|
||||
label: that._("function.label.setup")
|
||||
});
|
||||
|
||||
tabs.addTab({
|
||||
id: "func-tab-init",
|
||||
label: that._("function.label.initialize")
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-body",
|
||||
label: that._("function.label.function")
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-finalize",
|
||||
label: that._("function.label.finalize")
|
||||
});
|
||||
|
||||
tabs.activateTab("func-tab-body");
|
||||
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:0,
|
||||
change: function(event, ui) {
|
||||
var value = this.value;
|
||||
if (!value.match(/^\d+$/)) { value = 1; }
|
||||
else if (value < this.min) { value = this.min; }
|
||||
if (value !== this.value) { $(this).spinner("value", value); }
|
||||
}
|
||||
});
|
||||
|
||||
var buildEditor = function(id, value, defaultValue, extraLibs) {
|
||||
var editor = RED.editor.createEditor({
|
||||
id: id,
|
||||
mode: 'ace/mode/nrjavascript',
|
||||
value: value || defaultValue || "",
|
||||
globals: {
|
||||
msg:true,
|
||||
context:true,
|
||||
RED: true,
|
||||
util: true,
|
||||
flow: true,
|
||||
global: true,
|
||||
console: true,
|
||||
Buffer: true,
|
||||
setTimeout: true,
|
||||
clearTimeout: true,
|
||||
setInterval: true,
|
||||
clearInterval: true
|
||||
},
|
||||
extraLibs: extraLibs
|
||||
});
|
||||
if (defaultValue && value === "") {
|
||||
editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
|
||||
}
|
||||
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"))
|
||||
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:this.editor, // the field name the main text body goes to
|
||||
mode:"ace/mode/nrjavascript",
|
||||
fields:[
|
||||
'name', 'outputs',
|
||||
{
|
||||
name: 'initialize',
|
||||
get: function() {
|
||||
return that.initEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.initEditor.setValue(v||RED._("node-red:function.text.initialize"), -1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finalize',
|
||||
get: function() {
|
||||
return that.finalizeEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.finalizeEditor.setValue(v||RED._("node-red:function.text.finalize"), -1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
get: function() {
|
||||
return that.infoEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.infoEditor.setValue(v||"", -1);
|
||||
}
|
||||
}
|
||||
],
|
||||
ext:"js"
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
|
||||
var expandButtonClickHandler = function(editor) {
|
||||
return function(e) {
|
||||
e.preventDefault();
|
||||
var value = editor.getValue();
|
||||
RED.editor.editJavaScript({
|
||||
value: value,
|
||||
width: "Infinity",
|
||||
cursor: editor.getCursorPosition(),
|
||||
mode: "ace/mode/nrjavascript",
|
||||
complete: function(v,cursor) {
|
||||
editor.setValue(v, -1);
|
||||
editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
editor.focus();
|
||||
},300);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
$("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor));
|
||||
$("#node-function-expand-js").on("click", expandButtonClickHandler(this.editor));
|
||||
$("#node-finalize-expand-js").on("click", expandButtonClickHandler(this.finalizeEditor));
|
||||
|
||||
RED.popover.tooltip($("#node-init-expand-js"), RED._("node-red:common.label.expand"));
|
||||
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
||||
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
||||
|
||||
if (RED.settings.functionExternalModules !== false) {
|
||||
prepareLibraryConfig(that);
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var node = this;
|
||||
var noerr = 0;
|
||||
$("#node-input-noerr").val(0);
|
||||
|
||||
var disposeEditor = function(editorName,targetName,defaultValue) {
|
||||
var editor = node[editorName];
|
||||
var annot = editor.getSession().getAnnotations();
|
||||
for (var k=0; k < annot.length; k++) {
|
||||
if (annot[k].type === "error") {
|
||||
noerr += annot.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var val = editor.getValue();
|
||||
if (defaultValue) {
|
||||
if (val.trim() == defaultValue.trim()) {
|
||||
val = "";
|
||||
}
|
||||
}
|
||||
editor.destroy();
|
||||
delete node[editorName];
|
||||
$("#"+targetName).val(val);
|
||||
}
|
||||
disposeEditor("editor","node-input-func");
|
||||
disposeEditor("initEditor","node-input-initialize", RED._("node-red:function.text.initialize"));
|
||||
disposeEditor("finalizeEditor","node-input-finalize", RED._("node-red:function.text.finalize"));
|
||||
|
||||
$("#node-input-noerr").val(noerr);
|
||||
this.noerr = noerr;
|
||||
node.libs = getLibsList();
|
||||
},
|
||||
oneditcancel: function() {
|
||||
var node = this;
|
||||
|
||||
node.editor.destroy();
|
||||
delete node.editor;
|
||||
|
||||
node.initEditor.destroy();
|
||||
delete node.initEditor;
|
||||
|
||||
node.finalizeEditor.destroy();
|
||||
delete node.finalizeEditor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#dialog-form .node-text-editor").css("height",height+"px");
|
||||
|
||||
var height = size.height;
|
||||
$("#node-input-init-editor").css("height", (height - 83)+"px");
|
||||
$("#node-input-func-editor").css("height", (height - 83)+"px");
|
||||
$("#node-input-finalize-editor").css("height", (height - 83)+"px");
|
||||
|
||||
this.initEditor.resize();
|
||||
this.editor.resize();
|
||||
this.finalizeEditor.resize();
|
||||
|
||||
$("#node-input-libs-container").css("height", (height - 192)+"px");
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,507 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var util = require("util");
|
||||
var vm = require("vm");
|
||||
var acorn = require("acorn");
|
||||
var acornWalk = require("acorn-walk");
|
||||
|
||||
function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
|
||||
if (msgs == null) {
|
||||
return;
|
||||
} else if (!util.isArray(msgs)) {
|
||||
msgs = [msgs];
|
||||
}
|
||||
var msgCount = 0;
|
||||
for (var m=0; m<msgs.length; m++) {
|
||||
if (msgs[m]) {
|
||||
if (!util.isArray(msgs[m])) {
|
||||
msgs[m] = [msgs[m]];
|
||||
}
|
||||
for (var n=0; n < msgs[m].length; n++) {
|
||||
var msg = msgs[m][n];
|
||||
if (msg !== null && msg !== undefined) {
|
||||
if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
|
||||
if (msgCount === 0 && cloneFirstMessage !== false) {
|
||||
msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
|
||||
msg = msgs[m][n];
|
||||
}
|
||||
msg._msgid = _msgid;
|
||||
msgCount++;
|
||||
} else {
|
||||
var type = typeof msg;
|
||||
if (type === 'object') {
|
||||
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
|
||||
}
|
||||
node.error(RED._("function.error.non-message-returned",{ type: type }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msgCount>0) {
|
||||
send(msgs);
|
||||
}
|
||||
}
|
||||
|
||||
function createVMOpt(node, kind) {
|
||||
var opt = {
|
||||
filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces
|
||||
displayErrors: true
|
||||
// Using the following options causes node 4/6 to not include the line number
|
||||
// in the stack output. So don't use them.
|
||||
// lineOffset: -11, // line number offset to be used for stack traces
|
||||
// columnOffset: 0, // column number offset to be used for stack traces
|
||||
};
|
||||
return opt;
|
||||
}
|
||||
|
||||
function updateErrorInfo(err) {
|
||||
if (err.stack) {
|
||||
var stack = err.stack.toString();
|
||||
var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack);
|
||||
if (m) {
|
||||
var line = parseInt(m[3]) -1;
|
||||
var kind = "body:";
|
||||
if (/setup/.exec(m[1])) {
|
||||
kind = "setup:";
|
||||
}
|
||||
if (/cleanup/.exec(m[1])) {
|
||||
kind = "cleanup:";
|
||||
}
|
||||
err.message += " ("+kind+"line "+line+")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function FunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
node.name = n.name;
|
||||
node.func = n.func;
|
||||
node.outputs = n.outputs;
|
||||
node.ini = n.initialize ? n.initialize.trim() : "";
|
||||
node.fin = n.finalize ? n.finalize.trim() : "";
|
||||
node.libs = n.libs || [];
|
||||
|
||||
if (RED.settings.functionExternalModules === false && node.libs.length > 0) {
|
||||
throw new Error(RED._("function.error.externalModuleNotAllowed"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
var functionText = "var results = null;"+
|
||||
"results = (async function(msg,__send__,__done__){ "+
|
||||
"var __msgid__ = msg._msgid;"+
|
||||
"var node = {"+
|
||||
"id:__node__.id,"+
|
||||
"name:__node__.name,"+
|
||||
"outputCount:__node__.outputCount,"+
|
||||
"log:__node__.log,"+
|
||||
"error:__node__.error,"+
|
||||
"warn:__node__.warn,"+
|
||||
"debug:__node__.debug,"+
|
||||
"trace:__node__.trace,"+
|
||||
"on:__node__.on,"+
|
||||
"status:__node__.status,"+
|
||||
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
|
||||
"done:__done__"+
|
||||
"};\n"+
|
||||
node.func+"\n"+
|
||||
"})(msg,__send__,__done__);";
|
||||
|
||||
var handleNodeDoneCall = true;
|
||||
|
||||
// Check to see if the Function appears to call `node.done()`. If so,
|
||||
// we will assume it is well written and does actually call node.done().
|
||||
// Otherwise, we will call node.done() after the function returns regardless.
|
||||
if (/node\.done\s*\(\s*\)/.test(functionText)) {
|
||||
// We have spotted the code contains `node.done`. It could be in a comment
|
||||
// so need to do the extra work to parse the AST and examine it properly.
|
||||
acornWalk.simple(acorn.parse(functionText,{ecmaVersion: "latest"} ), {
|
||||
CallExpression(astNode) {
|
||||
if (astNode.callee && astNode.callee.object) {
|
||||
if (astNode.callee.object.name === "node" && astNode.callee.property.name === "done") {
|
||||
handleNodeDoneCall = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var finScript = null;
|
||||
var finOpt = null;
|
||||
node.topic = n.topic;
|
||||
node.outstandingTimers = [];
|
||||
node.outstandingIntervals = [];
|
||||
node.clearStatus = false;
|
||||
|
||||
var sandbox = {
|
||||
console:console,
|
||||
util:util,
|
||||
Buffer:Buffer,
|
||||
Date: Date,
|
||||
RED: {
|
||||
util: RED.util
|
||||
},
|
||||
__node__: {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
outputCount: node.outputs,
|
||||
log: function() {
|
||||
node.log.apply(node, arguments);
|
||||
},
|
||||
error: function() {
|
||||
node.error.apply(node, arguments);
|
||||
},
|
||||
warn: function() {
|
||||
node.warn.apply(node, arguments);
|
||||
},
|
||||
debug: function() {
|
||||
node.debug.apply(node, arguments);
|
||||
},
|
||||
trace: function() {
|
||||
node.trace.apply(node, arguments);
|
||||
},
|
||||
send: function(send, id, msgs, cloneMsg) {
|
||||
sendResults(node, send, id, msgs, cloneMsg);
|
||||
},
|
||||
on: function() {
|
||||
if (arguments[0] === "input") {
|
||||
throw new Error(RED._("function.error.inputListener"));
|
||||
}
|
||||
node.on.apply(node, arguments);
|
||||
},
|
||||
status: function() {
|
||||
node.clearStatus = true;
|
||||
node.status.apply(node, arguments);
|
||||
}
|
||||
},
|
||||
context: {
|
||||
set: function() {
|
||||
node.context().set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().keys.apply(node,arguments);
|
||||
},
|
||||
get global() {
|
||||
return node.context().global;
|
||||
},
|
||||
get flow() {
|
||||
return node.context().flow;
|
||||
}
|
||||
},
|
||||
flow: {
|
||||
set: function() {
|
||||
node.context().flow.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().flow.get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().flow.keys.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
global: {
|
||||
set: function() {
|
||||
node.context().global.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().global.get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().global.keys.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
env: {
|
||||
get: function(envVar) {
|
||||
var flow = node._flow;
|
||||
return flow.getSetting(envVar);
|
||||
}
|
||||
},
|
||||
setTimeout: function () {
|
||||
var func = arguments[0];
|
||||
var timerId;
|
||||
arguments[0] = function() {
|
||||
sandbox.clearTimeout(timerId);
|
||||
try {
|
||||
func.apply(node,arguments);
|
||||
} catch(err) {
|
||||
node.error(err,{});
|
||||
}
|
||||
};
|
||||
timerId = setTimeout.apply(node,arguments);
|
||||
node.outstandingTimers.push(timerId);
|
||||
return timerId;
|
||||
},
|
||||
clearTimeout: function(id) {
|
||||
clearTimeout(id);
|
||||
var index = node.outstandingTimers.indexOf(id);
|
||||
if (index > -1) {
|
||||
node.outstandingTimers.splice(index,1);
|
||||
}
|
||||
},
|
||||
setInterval: function() {
|
||||
var func = arguments[0];
|
||||
var timerId;
|
||||
arguments[0] = function() {
|
||||
try {
|
||||
func.apply(node,arguments);
|
||||
} catch(err) {
|
||||
node.error(err,{});
|
||||
}
|
||||
};
|
||||
timerId = setInterval.apply(node,arguments);
|
||||
node.outstandingIntervals.push(timerId);
|
||||
return timerId;
|
||||
},
|
||||
clearInterval: function(id) {
|
||||
clearInterval(id);
|
||||
var index = node.outstandingIntervals.indexOf(id);
|
||||
if (index > -1) {
|
||||
node.outstandingIntervals.splice(index,1);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (util.hasOwnProperty('promisify')) {
|
||||
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
sandbox.setTimeout(function(){ resolve(value); }, after);
|
||||
});
|
||||
};
|
||||
sandbox.promisify = util.promisify;
|
||||
}
|
||||
const moduleLoadPromises = [];
|
||||
|
||||
if (node.hasOwnProperty("libs")) {
|
||||
let moduleErrors = false;
|
||||
var modules = node.libs;
|
||||
modules.forEach(module => {
|
||||
var vname = module.hasOwnProperty("var") ? module.var : null;
|
||||
if (vname && (vname !== "")) {
|
||||
if (sandbox.hasOwnProperty(vname) || vname === 'node') {
|
||||
node.error(RED._("function.error.moduleNameError",{name:vname}))
|
||||
moduleErrors = true;
|
||||
return;
|
||||
}
|
||||
sandbox[vname] = null;
|
||||
var spec = module.module;
|
||||
if (spec && (spec !== "")) {
|
||||
moduleLoadPromises.push(RED.import(module.module).then(lib => {
|
||||
sandbox[vname] = lib.default;
|
||||
}).catch(err => {
|
||||
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
|
||||
throw err;
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
if (moduleErrors) {
|
||||
throw new Error(RED._("function.error.externalModuleLoadError"));
|
||||
}
|
||||
}
|
||||
const RESOLVING = 0;
|
||||
const RESOLVED = 1;
|
||||
const ERROR = 2;
|
||||
var state = RESOLVING;
|
||||
var messages = [];
|
||||
var processMessage = (() => {});
|
||||
|
||||
node.on("input", function(msg,send,done) {
|
||||
if(state === RESOLVING) {
|
||||
messages.push({msg:msg, send:send, done:done});
|
||||
}
|
||||
else if(state === RESOLVED) {
|
||||
processMessage(msg, send, done);
|
||||
}
|
||||
});
|
||||
Promise.all(moduleLoadPromises).then(() => {
|
||||
var context = vm.createContext(sandbox);
|
||||
try {
|
||||
var iniScript = null;
|
||||
var iniOpt = null;
|
||||
if (node.ini && (node.ini !== "")) {
|
||||
var iniText = `
|
||||
(async function(__send__) {
|
||||
var node = {
|
||||
id:__node__.id,
|
||||
name:__node__.name,
|
||||
outputCount:__node__.outputCount,
|
||||
log:__node__.log,
|
||||
error:__node__.error,
|
||||
warn:__node__.warn,
|
||||
debug:__node__.debug,
|
||||
trace:__node__.trace,
|
||||
status:__node__.status,
|
||||
send: function(msgs, cloneMsg) {
|
||||
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
|
||||
}
|
||||
};
|
||||
`+ node.ini +`
|
||||
})(__initSend__);`;
|
||||
iniOpt = createVMOpt(node, " setup");
|
||||
iniScript = new vm.Script(iniText, iniOpt);
|
||||
}
|
||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||
if (node.fin && (node.fin !== "")) {
|
||||
var finText = `(function () {
|
||||
var node = {
|
||||
id:__node__.id,
|
||||
name:__node__.name,
|
||||
outputCount:__node__.outputCount,
|
||||
log:__node__.log,
|
||||
error:__node__.error,
|
||||
warn:__node__.warn,
|
||||
debug:__node__.debug,
|
||||
trace:__node__.trace,
|
||||
status:__node__.status,
|
||||
send: function(msgs, cloneMsg) {
|
||||
__node__.error("Cannot send from close function");
|
||||
}
|
||||
};
|
||||
`+node.fin +`
|
||||
})();`;
|
||||
finOpt = createVMOpt(node, " cleanup");
|
||||
finScript = new vm.Script(finText, finOpt);
|
||||
}
|
||||
var promise = Promise.resolve();
|
||||
if (iniScript) {
|
||||
context.__initSend__ = function(msgs) { node.send(msgs); };
|
||||
promise = iniScript.runInContext(context, iniOpt);
|
||||
}
|
||||
|
||||
processMessage = function (msg, send, done) {
|
||||
var start = process.hrtime();
|
||||
context.msg = msg;
|
||||
context.__send__ = send;
|
||||
context.__done__ = done;
|
||||
|
||||
node.script.runInContext(context);
|
||||
context.results.then(function(results) {
|
||||
sendResults(node,send,msg._msgid,results,false);
|
||||
if (handleNodeDoneCall) {
|
||||
done();
|
||||
}
|
||||
|
||||
var duration = process.hrtime(start);
|
||||
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
||||
node.metric("duration", msg, converted);
|
||||
if (process.env.NODE_RED_FUNCTION_TIME) {
|
||||
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
||||
}
|
||||
}).catch(err => {
|
||||
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
||||
//remove unwanted part
|
||||
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
|
||||
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
|
||||
var stack = err.stack.split(/\r?\n/);
|
||||
|
||||
//store the error in msg to be used in flows
|
||||
msg.error = err;
|
||||
|
||||
var line = 0;
|
||||
var errorMessage;
|
||||
if (stack.length > 0) {
|
||||
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
||||
line++;
|
||||
}
|
||||
|
||||
if (line < stack.length) {
|
||||
errorMessage = stack[line];
|
||||
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
|
||||
if (m) {
|
||||
var lineno = Number(m[1])-1;
|
||||
var cha = m[2];
|
||||
errorMessage += " (line "+lineno+", col "+cha+")";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!errorMessage) {
|
||||
errorMessage = err.toString();
|
||||
}
|
||||
done(errorMessage);
|
||||
}
|
||||
else if (typeof err === "string") {
|
||||
done(err);
|
||||
}
|
||||
else {
|
||||
done(JSON.stringify(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
if (finScript) {
|
||||
try {
|
||||
finScript.runInContext(context, finOpt);
|
||||
}
|
||||
catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
}
|
||||
while (node.outstandingTimers.length > 0) {
|
||||
clearTimeout(node.outstandingTimers.pop());
|
||||
}
|
||||
while (node.outstandingIntervals.length > 0) {
|
||||
clearInterval(node.outstandingIntervals.pop());
|
||||
}
|
||||
if (node.clearStatus) {
|
||||
node.status({});
|
||||
}
|
||||
});
|
||||
|
||||
promise.then(function (v) {
|
||||
var msgs = messages;
|
||||
messages = [];
|
||||
while (msgs.length > 0) {
|
||||
msgs.forEach(function (s) {
|
||||
processMessage(s.msg, s.send, s.done);
|
||||
});
|
||||
msgs = messages;
|
||||
messages = [];
|
||||
}
|
||||
state = RESOLVED;
|
||||
}).catch((error) => {
|
||||
messages = [];
|
||||
state = ERROR;
|
||||
node.error(error);
|
||||
});
|
||||
|
||||
}
|
||||
catch(err) {
|
||||
// eg SyntaxError - which v8 doesn't include line number information
|
||||
// so we can't do better than this
|
||||
updateErrorInfo(err);
|
||||
node.error(err);
|
||||
}
|
||||
}).catch(err => {
|
||||
node.error(RED._("function.error.externalModuleLoadError"));
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("function",FunctionNode, {
|
||||
dynamicModuleList: "libs",
|
||||
settings: {
|
||||
functionExternalModules: { value: true, exportable: true }
|
||||
}
|
||||
});
|
||||
RED.library.register("functions");
|
||||
};
|
@ -1,440 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="switch">
|
||||
<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" style="width: calc(100% - 105px)" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="switch.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width: calc(100% - 105px)"/>
|
||||
<input type="hidden" id="node-input-outputs"/>
|
||||
</div>
|
||||
<div class="form-row node-input-rule-container-row">
|
||||
<ol id="node-input-rule-container"></ol>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
|
||||
<option value="true" data-i18n="switch.checkall"></option>
|
||||
<option value="false" data-i18n="switch.stopfirst"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var operators = [
|
||||
{v:"eq",t:"==",kind:'V'},
|
||||
{v:"neq",t:"!=",kind:'V'},
|
||||
{v:"lt",t:"<",kind:'V'},
|
||||
{v:"lte",t:"<=",kind:'V'},
|
||||
{v:"gt",t:">",kind:'V'},
|
||||
{v:"gte",t:">=",kind:'V'},
|
||||
{v:"hask",t:"switch.rules.hask",kind:'V'},
|
||||
{v:"btwn",t:"switch.rules.btwn",kind:'V'},
|
||||
{v:"cont",t:"switch.rules.cont",kind:'V'},
|
||||
{v:"regex",t:"switch.rules.regex",kind:'V'},
|
||||
{v:"true",t:"switch.rules.true",kind:'V'},
|
||||
{v:"false",t:"switch.rules.false",kind:'V'},
|
||||
{v:"null",t:"switch.rules.null",kind:'V'},
|
||||
{v:"nnull",t:"switch.rules.nnull",kind:'V'},
|
||||
{v:"istype",t:"switch.rules.istype",kind:'V'},
|
||||
{v:"empty",t:"switch.rules.empty",kind:'V'},
|
||||
{v:"nempty",t:"switch.rules.nempty",kind:'V'},
|
||||
{v:"head",t:"switch.rules.head",kind:'S'},
|
||||
{v:"index",t:"switch.rules.index",kind:'S'},
|
||||
{v:"tail",t:"switch.rules.tail",kind:'S'},
|
||||
{v:"jsonata_exp",t:"switch.rules.exp",kind:'O'},
|
||||
{v:"else",t:"switch.rules.else",kind:'O'}
|
||||
];
|
||||
|
||||
var previousValueType = {value:"prev",label:RED._("node-red:switch.previous"),hasValue:false};
|
||||
function clipValueLength(v) {
|
||||
if (v.length > 15) {
|
||||
return v.substring(0,15)+"...";
|
||||
}
|
||||
return v;
|
||||
}
|
||||
function prop2name(key) {
|
||||
var result = RED.utils.parseContextKey(key);
|
||||
return result.key;
|
||||
}
|
||||
function getValueLabel(t,v) {
|
||||
if (t === 'str') {
|
||||
return '"'+clipValueLength(v)+'"';
|
||||
} else if (t === 'msg') {
|
||||
return t+"."+clipValueLength(v);
|
||||
} else if (t === 'flow' || t === 'global') {
|
||||
return t+"."+clipValueLength(prop2name(v));
|
||||
}
|
||||
return clipValueLength(v);
|
||||
}
|
||||
|
||||
function exportRule(rule) {
|
||||
var type = rule.find("select").val();
|
||||
var r = {t:type};
|
||||
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else")) {
|
||||
if ((type === "btwn") || (type === "index")) {
|
||||
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
|
||||
r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
|
||||
r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
|
||||
} else if ((type === "head") || (type === "tail")) {
|
||||
r.v = rule.find(".node-input-rule-num-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-num-value").typedInput('type');
|
||||
} else if (type === "istype") {
|
||||
r.v = rule.find(".node-input-rule-type-value").typedInput('type');
|
||||
r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
|
||||
} else if (type === "jsonata_exp") {
|
||||
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
|
||||
} else {
|
||||
r.v = rule.find(".node-input-rule-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-value").typedInput('type');
|
||||
}
|
||||
if (type === "regex") {
|
||||
r.case = rule.find(".node-input-rule-case").prop("checked");
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function createValueField(row, defaultType){
|
||||
return $('<input/>',{class:"node-input-rule-value",type:"text",style:"width: 100%;"}).appendTo(row)
|
||||
.typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
|
||||
}
|
||||
|
||||
function createNumValueField(row, defaultType){
|
||||
return $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"width: 100%;"}).appendTo(row)
|
||||
.typedInput({default:defaultType||'num',types:['flow','global','num','jsonata','env']});
|
||||
}
|
||||
|
||||
function createExpValueField(row){
|
||||
return $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"width: 100%;"}).appendTo(row)
|
||||
.typedInput({default:'jsonata',types:['jsonata']});
|
||||
}
|
||||
|
||||
function createBtwnValueField(row, defaultType){
|
||||
return $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"width: 100%;"}).appendTo(row)
|
||||
.typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
|
||||
}
|
||||
|
||||
function createBtwnValue2Field(row3, andLabel, defaultType){
|
||||
$('<div/>',{class:"node-input-rule-btwn-label", style:"width: 120px; text-align: right;"}).text(" "+andLabel+" ").appendTo(row3);
|
||||
var row3InputCell = $('<div/>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row3);
|
||||
return $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 100%"}).appendTo(row3InputCell)
|
||||
.typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
|
||||
}
|
||||
|
||||
function createTypeValueField(row, defaultType){
|
||||
return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:defaultType || 'string',types:[
|
||||
{value:"string",label:RED._("common.type.string"),hasValue:false,icon:"red/images/typedInput/az.png"},
|
||||
{value:"number",label:RED._("common.type.number"),hasValue:false,icon:"red/images/typedInput/09.png"},
|
||||
{value:"boolean",label:RED._("common.type.boolean"),hasValue:false,icon:"red/images/typedInput/bool.png"},
|
||||
{value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
|
||||
{value:"buffer",label:RED._("common.type.buffer"),hasValue:false,icon:"red/images/typedInput/bin.png"},
|
||||
{value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"},
|
||||
{value:"json",label:RED._("common.type.jsonString"),hasValue:false,icon:"red/images/typedInput/json.png"},
|
||||
{value:"undefined",label:RED._("common.type.undefined"),hasValue:false},
|
||||
{value:"null",label:RED._("common.type.null"),hasValue:false}
|
||||
]});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('switch', {
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload", required:true, validate: RED.validators.typedInput("propertyType")},
|
||||
propertyType: { value:"msg" },
|
||||
rules: {value:[{t:"eq", v:"", vt:"str"}]},
|
||||
checkall: {value:"true", required:true},
|
||||
repair: {value:false},
|
||||
outputs: {value:1}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
outputLabels: function(index) {
|
||||
var rule = this.rules[index];
|
||||
var label = "";
|
||||
if (rule) {
|
||||
for (var i=0;i<operators.length;i++) {
|
||||
if (operators[i].v === rule.t) {
|
||||
label = /^switch/.test(operators[i].t)?this._(operators[i].t):operators[i].t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((rule.t === 'btwn') || (rule.t === 'index')) {
|
||||
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
|
||||
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'empty' && rule.t !== 'nempty' && rule.t !== 'else' ) {
|
||||
label += " "+getValueLabel(rule.vt,rule.v);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
},
|
||||
icon: "switch.svg",
|
||||
label: function() {
|
||||
return this.name||this._("switch.switch");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
|
||||
|
||||
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata','env']});
|
||||
var outputCount = $("#node-input-outputs").val("{}");
|
||||
|
||||
var andLabel = this._("switch.and");
|
||||
var caseLabel = this._("switch.ignorecase");
|
||||
|
||||
$("#node-input-rule-container").css('min-height','150px').css('min-width','450px').editableList({
|
||||
addItem: function(container,i,opt) {
|
||||
var focusValueField = false;
|
||||
if (!opt.hasOwnProperty('r')) {
|
||||
opt.r = {};
|
||||
if (i > 0) {
|
||||
var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1);
|
||||
var exportedRule = exportRule(lastRule.element);
|
||||
opt.r.vt = exportedRule.vt;
|
||||
opt.r.v = "";
|
||||
// We could copy the value over as well and preselect it (see the 'activeElement' code below)
|
||||
// But not sure that feels right. Is copying over the last value 'expected' behaviour?
|
||||
// It would make sense for an explicit 'copy' action, but not sure where the copy button would
|
||||
// go for each rule without being wasted space for most users.
|
||||
// opt.r.v = exportedRule.v;
|
||||
focusValueField = true;
|
||||
}
|
||||
}
|
||||
|
||||
opt.element = container;
|
||||
var rule = opt.r;
|
||||
if (!rule.hasOwnProperty('t')) {
|
||||
rule.t = 'eq';
|
||||
}
|
||||
if (!opt.hasOwnProperty('i')) {
|
||||
opt._i = Math.floor((0x99999-0x10000)*Math.random()).toString();
|
||||
}
|
||||
container.css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
display: "flex",
|
||||
"align-items":"center"
|
||||
});
|
||||
var inputRows = $('<div></div>',{style:"flex-grow:1"}).appendTo(container);
|
||||
var row = $('<div></div>',{style:"display: flex;"}).appendTo(inputRows);
|
||||
var row2 = $('<div/>',{style:"display: flex; padding-top: 5px; padding-left: 175px;"}).appendTo(inputRows);
|
||||
var row3 = $('<div/>',{style:"display: flex; padding-top: 5px; align-items: center"}).appendTo(inputRows);
|
||||
|
||||
var selectField = $('<select/>',{style:"width:120px; text-align: center;"}).appendTo(row);
|
||||
var group0 = $('<optgroup/>', { label: "value rules" }).appendTo(selectField);
|
||||
for (var d in operators) {
|
||||
if(operators[d].kind === 'V') {
|
||||
group0.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
|
||||
}
|
||||
}
|
||||
var group1 = $('<optgroup/>', { label: "sequence rules" }).appendTo(selectField);
|
||||
for (var d in operators) {
|
||||
if(operators[d].kind === 'S') {
|
||||
group1.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
|
||||
}
|
||||
}
|
||||
for (var d in operators) {
|
||||
if(operators[d].kind === 'O') {
|
||||
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
|
||||
}
|
||||
}
|
||||
|
||||
var rowInputCell = $('<div>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row);
|
||||
|
||||
|
||||
var valueField = null;
|
||||
var numValueField = null;
|
||||
var expValueField = null;
|
||||
var btwnAndLabel = null;
|
||||
var btwnValueField = null;
|
||||
var btwnValue2Field = null;
|
||||
var typeValueField = null;
|
||||
|
||||
var finalspan = $('<span/>',{style:"margin-left: 5px;"}).appendTo(container);
|
||||
finalspan.append(' → <span class="node-input-rule-index">'+(i+1)+'</span> ');
|
||||
|
||||
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
|
||||
$('<label/>',{for:"node-input-rule-case-"+i,style:"margin-left: 3px;"}).text(caseLabel).appendTo(row2);
|
||||
|
||||
selectField.on("change", function() {
|
||||
var fieldToFocus;
|
||||
var type = selectField.val();
|
||||
if (valueField) { valueField.typedInput('hide'); }
|
||||
if (expValueField) { expValueField.typedInput('hide'); }
|
||||
if (numValueField) { numValueField.typedInput('hide'); }
|
||||
if (typeValueField) { typeValueField.typedInput('hide'); }
|
||||
if (btwnValueField) { btwnValueField.typedInput('hide'); }
|
||||
if (btwnValue2Field) { btwnValue2Field.typedInput('hide'); }
|
||||
|
||||
if ((type === "btwn") || (type === "index")) {
|
||||
if (!btwnValueField){
|
||||
btwnValueField = createBtwnValueField(rowInputCell);
|
||||
}
|
||||
btwnValueField.typedInput('show');
|
||||
fieldToFocus = btwnValueField;
|
||||
} else if ((type === "head") || (type === "tail")) {
|
||||
if (!numValueField){
|
||||
numValueField = createNumValueField(rowInputCell);
|
||||
}
|
||||
numValueField.typedInput('show');
|
||||
fieldToFocus = numValueField;
|
||||
} else if (type === "jsonata_exp") {
|
||||
if (!expValueField){
|
||||
expValueField = createExpValueField(rowInputCell);
|
||||
}
|
||||
expValueField.typedInput('show');
|
||||
fieldToFocus = expValueField;
|
||||
|
||||
} else if (type === "istype") {
|
||||
if (!typeValueField){
|
||||
typeValueField = createTypeValueField(rowInputCell);
|
||||
}
|
||||
typeValueField.typedInput('show');
|
||||
fieldToFocus = typeValueField;
|
||||
} else if (! (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else" )) {
|
||||
if (!valueField){
|
||||
valueField = createValueField(rowInputCell);
|
||||
}
|
||||
valueField.typedInput('show');
|
||||
fieldToFocus = valueField;
|
||||
}
|
||||
if (type === "regex") {
|
||||
row2.show();
|
||||
row3.hide();
|
||||
} else if ((type === "btwn") || (type === "index")) {
|
||||
row2.hide();
|
||||
row3.show();
|
||||
if (!btwnValue2Field){
|
||||
btwnValue2Field = createBtwnValue2Field(row3, andLabel);
|
||||
}
|
||||
btwnValue2Field.typedInput('show');
|
||||
} else {
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
}
|
||||
var selectedLabel = selectField.find("option:selected").text();
|
||||
if (selectedLabel.length <= 5) {
|
||||
selectField.outerWidth(60);
|
||||
} else if (selectedLabel.length < 12) {
|
||||
selectField.outerWidth(120);
|
||||
} else {
|
||||
selectField.width("auto")
|
||||
}
|
||||
if (fieldToFocus) {
|
||||
fieldToFocus.typedInput("focus");
|
||||
}
|
||||
// Preselect the contents of the element
|
||||
// if (focusValueField && document.activeElement) {
|
||||
// document.activeElement.selectionStart = 0;
|
||||
// document.activeElement.selectionEnd = document.activeElement.value.length;
|
||||
// }
|
||||
});
|
||||
selectField.val(rule.t);
|
||||
|
||||
if ((rule.t == "btwn") || (rule.t == "index")) {
|
||||
btwnValueField = createBtwnValueField(rowInputCell,rule.vt||'num');
|
||||
btwnValueField.typedInput('value',rule.v);
|
||||
btwnValue2Field = createBtwnValue2Field(row3, andLabel,rule.v2t||'num');
|
||||
btwnValue2Field.typedInput('value',rule.v2);
|
||||
} else if ((rule.t === "head") || (rule.t === "tail")) {
|
||||
numValueField = createNumValueField(rowInputCell,rule.vt||'num');
|
||||
numValueField.typedInput('value',rule.v);
|
||||
} else if (rule.t === "istype") {
|
||||
typeValueField = createTypeValueField(rowInputCell,rule.vt);
|
||||
typeValueField.typedInput('value',rule.vt);
|
||||
} else if (rule.t === "jsonata_exp") {
|
||||
expValueField = createExpValueField(rowInputCell,rule.vt||'jsonata');
|
||||
expValueField.typedInput('value',rule.v);
|
||||
} else if (typeof rule.v != "undefined") {
|
||||
valueField = createValueField(rowInputCell,rule.vt||'str');
|
||||
valueField.typedInput('value',rule.v);
|
||||
}
|
||||
caseSensitive.prop('checked',!!rule.case);
|
||||
selectField.change();
|
||||
|
||||
var currentOutputs = JSON.parse(outputCount.val()||"{}");
|
||||
currentOutputs[opt.hasOwnProperty('i')?opt.i:opt._i] = i;
|
||||
outputCount.val(JSON.stringify(currentOutputs));
|
||||
},
|
||||
removeItem: function(opt) {
|
||||
var currentOutputs = JSON.parse(outputCount.val()||"{}");
|
||||
if (opt.hasOwnProperty('i')) {
|
||||
currentOutputs[opt.i] = -1;
|
||||
} else {
|
||||
delete currentOutputs[opt._i];
|
||||
}
|
||||
var rules = $("#node-input-rule-container").editableList('items');
|
||||
rules.each(function(i) {
|
||||
$(this).find(".node-input-rule-index").html(i+1);
|
||||
var data = $(this).data('data');
|
||||
currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
|
||||
});
|
||||
outputCount.val(JSON.stringify(currentOutputs));
|
||||
},
|
||||
sortItems: function(rules) {
|
||||
var currentOutputs = JSON.parse(outputCount.val()||"{}");
|
||||
var rules = $("#node-input-rule-container").editableList('items');
|
||||
rules.each(function(i) {
|
||||
$(this).find(".node-input-rule-index").html(i+1);
|
||||
var data = $(this).data('data');
|
||||
currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
|
||||
});
|
||||
outputCount.val(JSON.stringify(currentOutputs));
|
||||
},
|
||||
sortable: true,
|
||||
removable: true
|
||||
});
|
||||
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
var rule = this.rules[i];
|
||||
$("#node-input-rule-container").editableList('addItem',{r:rule,i:i});
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").editableList('items');
|
||||
var node = this;
|
||||
node.rules = [];
|
||||
rules.each(function(i) {
|
||||
node.rules.push(exportRule($(this)));
|
||||
});
|
||||
this.propertyType = $("#node-input-property").typedInput('type');
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = size.height;
|
||||
for (var i=0;i<rows.length;i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
height += 16;
|
||||
$("#node-input-rule-container").editableList('height',height);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,521 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var operators = {
|
||||
'eq': function(a, b) { return a == b; },
|
||||
'neq': function(a, b) { return a != b; },
|
||||
'lt': function(a, b) { return a < b; },
|
||||
'lte': function(a, b) { return a <= b; },
|
||||
'gt': function(a, b) { return a > b; },
|
||||
'gte': function(a, b) { return a >= b; },
|
||||
'btwn': function(a, b, c) { return (a >= b && a <= c) || (a <= b && a >= c); },
|
||||
'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
|
||||
'regex': function(a, b, c, d) { return (a + "").match(new RegExp(b,d?'i':'')); },
|
||||
'true': function(a) { return a === true; },
|
||||
'false': function(a) { return a === false; },
|
||||
'null': function(a) { return (typeof a == "undefined" || a === null); },
|
||||
'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
|
||||
'empty': function(a) {
|
||||
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
|
||||
return a.length === 0;
|
||||
} else if (typeof a === 'object' && a !== null) {
|
||||
return Object.keys(a).length === 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'nempty': function(a) {
|
||||
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
|
||||
return a.length !== 0;
|
||||
} else if (typeof a === 'object' && a !== null) {
|
||||
return Object.keys(a).length !== 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'istype': function(a, b) {
|
||||
if (b === "array") { return Array.isArray(a); }
|
||||
else if (b === "buffer") { return Buffer.isBuffer(a); }
|
||||
else if (b === "json") {
|
||||
try { JSON.parse(a); return true; } // or maybe ??? a !== null; }
|
||||
catch(e) { return false;}
|
||||
}
|
||||
else if (b === "null") { return a === null; }
|
||||
else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
|
||||
},
|
||||
'head': function(a, b, c, d, parts) {
|
||||
var count = Number(b);
|
||||
return (parts.index < count);
|
||||
},
|
||||
'tail': function(a, b, c, d, parts) {
|
||||
var count = Number(b);
|
||||
return (parts.count -count <= parts.index);
|
||||
},
|
||||
'index': function(a, b, c, d, parts) {
|
||||
var min = Number(b);
|
||||
var max = Number(c);
|
||||
var index = parts.index;
|
||||
return ((min <= index) && (index <= max));
|
||||
},
|
||||
'hask': function(a, b) {
|
||||
return a !== undefined && a !== null && (typeof b !== "object" ) && a.hasOwnProperty(b+"");
|
||||
},
|
||||
'jsonata_exp': function(a, b) { return (b === true); },
|
||||
'else': function(a) { return a === true; }
|
||||
};
|
||||
|
||||
var _maxKeptCount;
|
||||
|
||||
function getMaxKeptCount() {
|
||||
if (_maxKeptCount === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_maxKeptCount = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_maxKeptCount = 0;
|
||||
}
|
||||
}
|
||||
return _maxKeptCount;
|
||||
}
|
||||
|
||||
function getProperty(node,msg,done) {
|
||||
if (node.propertyType === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
|
||||
if (err) {
|
||||
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
done(undefined,value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
done(undefined,undefined);
|
||||
} else {
|
||||
done(undefined,value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getV1(node,msg,rule,hasParts,done) {
|
||||
if (rule.vt === 'prev') {
|
||||
return done(undefined,node.previousValue);
|
||||
} else if (rule.vt === 'jsonata') {
|
||||
var exp = rule.v;
|
||||
if (rule.t === 'jsonata_exp') {
|
||||
if (hasParts) {
|
||||
exp.assign("I", msg.parts.index);
|
||||
exp.assign("N", msg.parts.count);
|
||||
}
|
||||
}
|
||||
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
|
||||
if (err) {
|
||||
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
done(undefined, value);
|
||||
}
|
||||
});
|
||||
} else if (rule.vt === 'json') {
|
||||
done(undefined,"json"); // TODO: ?! invalid case
|
||||
} else if (rule.vt === 'null') {
|
||||
done(undefined,"null");
|
||||
} else {
|
||||
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
|
||||
if (err) {
|
||||
done(undefined, undefined);
|
||||
} else {
|
||||
done(undefined, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getV2(node,msg,rule,done) {
|
||||
var v2 = rule.v2;
|
||||
if (rule.v2t === 'prev') {
|
||||
return done(undefined,node.previousValue);
|
||||
} else if (rule.v2t === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
|
||||
if (err) {
|
||||
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
done(undefined,value);
|
||||
}
|
||||
});
|
||||
} else if (typeof v2 !== 'undefined') {
|
||||
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
|
||||
if (err) {
|
||||
done(undefined,undefined);
|
||||
} else {
|
||||
done(undefined,value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
done(undefined,v2);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRule(node, msg, property, state, done) {
|
||||
var rule = node.rules[state.currentRule];
|
||||
var v1,v2;
|
||||
|
||||
getV1(node,msg,rule,state.hasParts, (err,value) => {
|
||||
if (err) {
|
||||
// This only happens if v1 is an invalid JSONata expr
|
||||
// But that will have already been logged and the node marked
|
||||
// invalid as part of the constructor
|
||||
return done(err);
|
||||
}
|
||||
v1 = value;
|
||||
getV2(node,msg,rule, (err,value) => {
|
||||
if (err) {
|
||||
// This only happens if v1 is an invalid JSONata expr
|
||||
// But that will have already been logged and the node marked
|
||||
// invalid as part of the constructor
|
||||
return done(err);
|
||||
}
|
||||
v2 = value;
|
||||
if (rule.t == "else") {
|
||||
property = state.elseflag;
|
||||
state.elseflag = true;
|
||||
}
|
||||
try {
|
||||
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
|
||||
state.onward.push(msg);
|
||||
state.elseflag = false;
|
||||
if (node.checkall == "false") {
|
||||
return done(undefined,false);
|
||||
}
|
||||
} else {
|
||||
state.onward.push(null);
|
||||
}
|
||||
done(undefined, state.currentRule < node.rules.length - 1);
|
||||
} catch(err) {
|
||||
// An error occurred evaluating the rule - for example, an
|
||||
// invalid RegExp value.
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyRules(node, msg, property,state,done) {
|
||||
if (!state) {
|
||||
if (node.rules.length === 0) {
|
||||
done(undefined, []);
|
||||
return;
|
||||
}
|
||||
state = {
|
||||
currentRule: 0,
|
||||
elseflag: true,
|
||||
onward: [],
|
||||
hasParts: msg.hasOwnProperty("parts") &&
|
||||
msg.parts.hasOwnProperty("id") &&
|
||||
msg.parts.hasOwnProperty("index")
|
||||
}
|
||||
}
|
||||
applyRule(node,msg,property,state,(err,hasMore) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (hasMore) {
|
||||
state.currentRule++;
|
||||
applyRules(node,msg,property,state,done);
|
||||
} else {
|
||||
node.previousValue = property;
|
||||
done(undefined,state.onward);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function SwitchNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.rules = n.rules || [];
|
||||
this.property = n.property;
|
||||
this.propertyType = n.propertyType || "msg";
|
||||
|
||||
if (this.propertyType === 'jsonata') {
|
||||
try {
|
||||
this.property = RED.util.prepareJSONataExpression(this.property,this);
|
||||
} catch(err) {
|
||||
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.checkall = n.checkall || "true";
|
||||
this.previousValue = null;
|
||||
var node = this;
|
||||
var valid = true;
|
||||
var repair = n.repair;
|
||||
var needsCount = repair;
|
||||
|
||||
for (var i=0; i<this.rules.length; i+=1) {
|
||||
var rule = this.rules[i];
|
||||
needsCount = needsCount || ((rule.t === "tail"));
|
||||
if (!rule.vt) {
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.vt = 'num';
|
||||
} else {
|
||||
rule.vt = 'str';
|
||||
}
|
||||
}
|
||||
if (rule.vt === 'num') {
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.v = Number(rule.v);
|
||||
}
|
||||
} else if (rule.vt === "jsonata") {
|
||||
try {
|
||||
rule.v = RED.util.prepareJSONataExpression(rule.v,node);
|
||||
} catch(err) {
|
||||
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if (typeof rule.v2 !== 'undefined') {
|
||||
if (!rule.v2t) {
|
||||
if (!isNaN(Number(rule.v2))) {
|
||||
rule.v2t = 'num';
|
||||
} else {
|
||||
rule.v2t = 'str';
|
||||
}
|
||||
}
|
||||
if (rule.v2t === 'num') {
|
||||
rule.v2 = Number(rule.v2);
|
||||
} else if (rule.v2t === 'jsonata') {
|
||||
try {
|
||||
rule.v2 = RED.util.prepareJSONataExpression(rule.v2,node);
|
||||
} catch(err) {
|
||||
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingCount = 0;
|
||||
var pendingId = 0;
|
||||
var pendingIn = {};
|
||||
var pendingOut = {};
|
||||
var received = {};
|
||||
|
||||
function addMessageToGroup(id, msg, parts) {
|
||||
if (!(id in pendingIn)) {
|
||||
pendingIn[id] = {
|
||||
count: undefined,
|
||||
msgs: [],
|
||||
seq_no: pendingId++
|
||||
};
|
||||
}
|
||||
var group = pendingIn[id];
|
||||
group.msgs.push(msg);
|
||||
pendingCount++;
|
||||
var max_msgs = getMaxKeptCount();
|
||||
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||
clearPending();
|
||||
node.error(RED._("switch.errors.too-many"), msg);
|
||||
}
|
||||
if (parts.hasOwnProperty("count")) {
|
||||
group.count = parts.count;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
function drainMessageGroup(msgs,count,done) {
|
||||
var msg = msgs.shift();
|
||||
msg.parts.count = count;
|
||||
processMessage(msg,false, err => {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
if (msgs.length === 0) {
|
||||
done()
|
||||
} else {
|
||||
drainMessageGroup(msgs,count,done);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function addMessageToPending(msg,done) {
|
||||
var parts = msg.parts;
|
||||
// We've already checked the msg.parts has the require bits
|
||||
var group = addMessageToGroup(parts.id, msg, parts);
|
||||
var msgs = group.msgs;
|
||||
var count = group.count;
|
||||
var msgsCount = msgs.length;
|
||||
if (count === msgsCount) {
|
||||
// We have a complete group - send the individual parts
|
||||
drainMessageGroup(msgs,count,err => {
|
||||
pendingCount -= msgsCount;
|
||||
delete pendingIn[parts.id];
|
||||
done();
|
||||
})
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
function sendGroup(onwards, port_count) {
|
||||
var counts = new Array(port_count).fill(0);
|
||||
for (var i = 0; i < onwards.length; i++) {
|
||||
var onward = onwards[i];
|
||||
for (var j = 0; j < port_count; j++) {
|
||||
counts[j] += (onward[j] !== null) ? 1 : 0
|
||||
}
|
||||
}
|
||||
var ids = new Array(port_count);
|
||||
for (var j = 0; j < port_count; j++) {
|
||||
ids[j] = RED.util.generateId();
|
||||
}
|
||||
var ports = new Array(port_count);
|
||||
var indexes = new Array(port_count).fill(0);
|
||||
for (var i = 0; i < onwards.length; i++) {
|
||||
var onward = onwards[i];
|
||||
for (var j = 0; j < port_count; j++) {
|
||||
var msg = onward[j];
|
||||
if (msg) {
|
||||
var new_msg = RED.util.cloneMessage(msg);
|
||||
var parts = new_msg.parts;
|
||||
parts.id = ids[j];
|
||||
parts.index = indexes[j];
|
||||
parts.count = counts[j];
|
||||
ports[j] = new_msg;
|
||||
indexes[j]++;
|
||||
}
|
||||
else {
|
||||
ports[j] = null;
|
||||
}
|
||||
}
|
||||
node.send(ports);
|
||||
}
|
||||
}
|
||||
|
||||
function sendGroupMessages(onward, msg) {
|
||||
var parts = msg.parts;
|
||||
var gid = parts.id;
|
||||
received[gid] = ((gid in received) ? received[gid] : 0) +1;
|
||||
var send_ok = (received[gid] === parts.count);
|
||||
|
||||
if (!(gid in pendingOut)) {
|
||||
pendingOut[gid] = {
|
||||
onwards: []
|
||||
};
|
||||
}
|
||||
var group = pendingOut[gid];
|
||||
var onwards = group.onwards;
|
||||
onwards.push(onward);
|
||||
pendingCount++;
|
||||
if (send_ok) {
|
||||
sendGroup(onwards, onward.length, msg);
|
||||
pendingCount -= onward.length;
|
||||
delete pendingOut[gid];
|
||||
delete received[gid];
|
||||
}
|
||||
var max_msgs = getMaxKeptCount();
|
||||
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||
clearPending();
|
||||
node.error(RED._("switch.errors.too-many"), msg);
|
||||
}
|
||||
}
|
||||
|
||||
function processMessage(msg, checkParts, done) {
|
||||
var hasParts = msg.hasOwnProperty("parts") &&
|
||||
msg.parts.hasOwnProperty("id") &&
|
||||
msg.parts.hasOwnProperty("index");
|
||||
|
||||
if (needsCount && checkParts && hasParts) {
|
||||
addMessageToPending(msg,done);
|
||||
} else {
|
||||
getProperty(node,msg,(err,property) => {
|
||||
if (err) {
|
||||
node.warn(err);
|
||||
done();
|
||||
} else {
|
||||
applyRules(node,msg,property,undefined,(err,onward) => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
} else {
|
||||
if (!repair || !hasParts) {
|
||||
node.send(onward);
|
||||
} else {
|
||||
sendGroupMessages(onward, msg);
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() {
|
||||
pendingCount = 0;
|
||||
pendingId = 0;
|
||||
pendingIn = {};
|
||||
pendingOut = {};
|
||||
received = {};
|
||||
}
|
||||
|
||||
var pendingMessages = [];
|
||||
var handlingMessage = false;
|
||||
var processMessageQueue = function(msg) {
|
||||
if (msg) {
|
||||
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msg);
|
||||
if (handlingMessage) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
handlingMessage = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsg = pendingMessages.shift();
|
||||
handlingMessage = true;
|
||||
processMessage(nextMsg,true,err => {
|
||||
if (err) {
|
||||
node.error(err,nextMsg);
|
||||
}
|
||||
processMessageQueue()
|
||||
});
|
||||
}
|
||||
|
||||
this.on('input', function(msg) {
|
||||
processMessageQueue(msg);
|
||||
});
|
||||
|
||||
this.on('close', function() {
|
||||
clearPending();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("switch", SwitchNode);
|
||||
}
|
@ -1,367 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="change">
|
||||
<style>
|
||||
ol#node-input-rule-container .red-ui-typedInput-container {
|
||||
flex:1;
|
||||
}
|
||||
</style>
|
||||
<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" style="width: calc(100% - 105px)" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-rule-container-row">
|
||||
<ol id="node-input-rule-container"></ol>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
function validateProperty(v,vt) {
|
||||
if (/msg|flow|global/.test(vt)) {
|
||||
if (!RED.utils.validatePropertyExpression(v)) {
|
||||
return false;
|
||||
}
|
||||
} else if (vt === "jsonata") {
|
||||
try{jsonata(v);}catch(e){return false;}
|
||||
} else if (vt === "json") {
|
||||
try{JSON.parse(v);}catch(e){return false;}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
} else if (r.t === 'change') {
|
||||
if (!validateProperty(r.p,r.pt) || !validateProperty(r.from,r.fromt) || !validateProperty(r.to,r.tot)) {
|
||||
return false;
|
||||
}
|
||||
} else if (r.t === 'move') {
|
||||
if (!validateProperty(r.p,r.pt)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
// legacy
|
||||
action: {value:""},
|
||||
property: {value:""},
|
||||
from: {value:""},
|
||||
to: {value:""},
|
||||
reg: {value:false}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "swap.svg",
|
||||
label: function() {
|
||||
function prop2name(type, key) {
|
||||
var result = RED.utils.parseContextKey(key);
|
||||
return type +"." +result.key;
|
||||
}
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (!this.rules) {
|
||||
if (this.action === "replace") {
|
||||
return this._("change.label.set",{property:"msg."+this.property});
|
||||
} else if (this.action === "change") {
|
||||
return this._("change.label.change",{property:"msg."+this.property});
|
||||
} else if (this.action === "move") {
|
||||
return this._("change.label.move",{property:"msg."+this.property});
|
||||
} else {
|
||||
return this._("change.label.delete",{property:"msg."+this.property});
|
||||
}
|
||||
} else {
|
||||
if (this.rules.length == 1) {
|
||||
if (this.rules[0].t === "set") {
|
||||
return this._("change.label.set",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else if (this.rules[0].t === "change") {
|
||||
return this._("change.label.change",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else if (this.rules[0].t === "move") {
|
||||
return this._("change.label.move",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else {
|
||||
return this._("change.label.delete",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
}
|
||||
} else {
|
||||
return this._("change.label.changeCount",{count:this.rules.length});
|
||||
}
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var set = this._("change.action.set");
|
||||
var change = this._("change.action.change");
|
||||
var del = this._("change.action.delete");
|
||||
var move = this._("change.action.move");
|
||||
var to = this._("change.action.to");
|
||||
var toValueLabel = this._("change.action.toValue",to);
|
||||
var search = this._("change.action.search");
|
||||
var replace = this._("change.action.replace");
|
||||
var regex = this._("change.label.regex");
|
||||
var deepCopyLabel = this._("change.label.deepCopy");
|
||||
|
||||
function createPropertyValue(row2_1, row2_2, defaultType) {
|
||||
var propValInput = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
|
||||
.appendTo(row2_1)
|
||||
.typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
|
||||
|
||||
var dcLabel = $('<label style="padding-left: 130px;"></label>').appendTo(row2_2);
|
||||
var deepCopy = $('<input type="checkbox" class="node-input-rule-property-deepCopy" style="width: auto; margin: 0 6px 0 0">').appendTo(dcLabel)
|
||||
$('<span>').text(deepCopyLabel).appendTo(dcLabel)
|
||||
|
||||
propValInput.on("change", function(evt,type,val) {
|
||||
row2_2.toggle(type === "msg" || type === "flow" || type === "global" || type === "env");
|
||||
})
|
||||
return [propValInput, deepCopy];
|
||||
}
|
||||
function createFromValue(row3_1, defaultType) {
|
||||
return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
|
||||
.appendTo(row3_1)
|
||||
.typedInput({default:defaultType||'str',types:['msg','flow','global','str','re','num','bool','env']});
|
||||
}
|
||||
function createToValue(row3_2, defaultType) {
|
||||
return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
|
||||
.appendTo(row3_2)
|
||||
.typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
|
||||
}
|
||||
function createMoveValue(row4, defaultType) {
|
||||
return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
|
||||
.appendTo(row4)
|
||||
.typedInput({default:defaultType||'msg',types:['msg','flow','global']});
|
||||
}
|
||||
|
||||
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
|
||||
addItem: function(container,i,opt) {
|
||||
var rule = opt;
|
||||
if (!rule.hasOwnProperty('t')) {
|
||||
rule = {t:"set",p:"payload",to:"",tot:"str"};
|
||||
}
|
||||
if (rule.t === "change" && rule.re) {
|
||||
rule.fromt = 're';
|
||||
delete rule.re;
|
||||
}
|
||||
if (rule.t === "set" && !rule.tot) {
|
||||
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
|
||||
rule.to = rule.to.substring(4);
|
||||
rule.tot = "msg";
|
||||
} else {
|
||||
rule.tot = "str";
|
||||
}
|
||||
}
|
||||
if (rule.t === "move" && !rule.tot) {
|
||||
rule.tot = "msg";
|
||||
}
|
||||
container.css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
});
|
||||
let fragment = document.createDocumentFragment();
|
||||
var row1 = $('<div/>',{style:"display:flex; align-items: baseline"}).appendTo(fragment);
|
||||
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
|
||||
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
|
||||
var row4 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(fragment);
|
||||
|
||||
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1);
|
||||
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del},{v:"move",l:move}];
|
||||
for (var i=0; i<4; i++) {
|
||||
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
|
||||
}
|
||||
|
||||
var propertyName = $('<input/>',{class:"node-input-rule-property-name",type:"text"})
|
||||
.appendTo(row1)
|
||||
.typedInput({types:['msg','flow','global']});
|
||||
|
||||
var row2_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row2);
|
||||
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
|
||||
.text(toValueLabel)
|
||||
.appendTo(row2_1);
|
||||
|
||||
var row2_2 = $('<div/>', {style:"margin-top: 4px;"}).appendTo(row2);
|
||||
|
||||
var row3_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row3);
|
||||
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
|
||||
.text(search)
|
||||
.appendTo(row3_1);
|
||||
|
||||
var row3_2 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(row3);
|
||||
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
|
||||
.text(replace)
|
||||
.appendTo(row3_2);
|
||||
|
||||
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
|
||||
.text(to)
|
||||
.appendTo(row4);
|
||||
|
||||
let propertyValue = null;
|
||||
let fromValue = null;
|
||||
let toValue = null;
|
||||
let moveValue = null;
|
||||
|
||||
selectField.on("change", function() {
|
||||
var type = $(this).val();
|
||||
if (propertyValue) {
|
||||
propertyValue.typedInput('hide');
|
||||
}
|
||||
if (fromValue) {
|
||||
fromValue.typedInput('hide');
|
||||
}
|
||||
if (toValue) {
|
||||
toValue.typedInput('hide');
|
||||
}
|
||||
if (moveValue) {
|
||||
moveValue.typedInput('hide');
|
||||
}
|
||||
|
||||
if (type == "set") {
|
||||
if(!propertyValue) {
|
||||
var parts = createPropertyValue(row2_1, row2_2);
|
||||
propertyValue = parts[0];
|
||||
deepCopy = parts[1];
|
||||
}
|
||||
propertyValue.typedInput('show');
|
||||
row2.show();
|
||||
row3.hide();
|
||||
row4.hide();
|
||||
} else if (type == "change") {
|
||||
if(!fromValue) {
|
||||
fromValue = createFromValue(row3_1);
|
||||
}
|
||||
fromValue.typedInput('show');
|
||||
if(!toValue) {
|
||||
toValue = createToValue(row3_2);
|
||||
}
|
||||
toValue.typedInput('show');
|
||||
row2.hide();
|
||||
row3.show();
|
||||
row4.hide();
|
||||
} else if (type == "delete") {
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
row4.hide();
|
||||
} else if (type == "move") {
|
||||
if(!moveValue) {
|
||||
moveValue = createMoveValue(row4);
|
||||
}
|
||||
moveValue.typedInput('show');
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
row4.show();
|
||||
}
|
||||
});
|
||||
|
||||
selectField.val(rule.t);
|
||||
propertyName.typedInput('value',rule.p);
|
||||
propertyName.typedInput('type',rule.pt);
|
||||
if (rule.t == "set") {
|
||||
var parts = createPropertyValue(row2_1, row2_2, rule.tot);
|
||||
propertyValue = parts[0];
|
||||
deepCopy = parts[1];
|
||||
propertyValue.typedInput('value',rule.to);
|
||||
deepCopy.prop("checked", !!rule.dc);
|
||||
}
|
||||
if (rule.t == "move") {
|
||||
moveValue = createMoveValue(row4,rule.tot);
|
||||
moveValue.typedInput('value',rule.to);
|
||||
}
|
||||
if (rule.t == "change") {
|
||||
fromValue = createFromValue(row3_1, rule.fromt);
|
||||
fromValue.typedInput('value',rule.from);
|
||||
|
||||
toValue = createToValue(row3_2,rule.tot);
|
||||
toValue.typedInput('value',rule.to);
|
||||
}
|
||||
selectField.change();
|
||||
container[0].appendChild(fragment);
|
||||
},
|
||||
removable: true,
|
||||
sortable: true
|
||||
});
|
||||
|
||||
if (!this.rules) {
|
||||
var rule = {
|
||||
t:(this.action=="replace"?"set":this.action),
|
||||
p:this.property,
|
||||
pt:"msg"
|
||||
};
|
||||
|
||||
if ((rule.t === "set")||(rule.t === "move")) {
|
||||
rule.to = this.to;
|
||||
} else if (rule.t === "change") {
|
||||
rule.from = this.from;
|
||||
rule.to = this.to;
|
||||
rule.re = this.reg;
|
||||
}
|
||||
|
||||
delete this.to;
|
||||
delete this.from;
|
||||
delete this.reg;
|
||||
delete this.action;
|
||||
delete this.property;
|
||||
|
||||
this.rules = [rule];
|
||||
}
|
||||
|
||||
for (var i=0; i<this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
$("#node-input-rule-container").editableList('addItem',rule);
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").editableList('items');
|
||||
var node = this;
|
||||
node.rules= [];
|
||||
rules.each(function(i) {
|
||||
var rule = $(this);
|
||||
var type = rule.find(".node-input-rule-type").val();
|
||||
var r = {
|
||||
t:type,
|
||||
p:rule.find(".node-input-rule-property-name").typedInput('value'),
|
||||
pt:rule.find(".node-input-rule-property-name").typedInput('type')
|
||||
};
|
||||
if (type === "set") {
|
||||
r.to = rule.find(".node-input-rule-property-value").typedInput('value');
|
||||
r.tot = rule.find(".node-input-rule-property-value").typedInput('type');
|
||||
if (rule.find(".node-input-rule-property-deepCopy").prop("checked")) {
|
||||
r.dc = true;
|
||||
}
|
||||
} else if (type === "move") {
|
||||
r.to = rule.find(".node-input-rule-property-move-value").typedInput('value');
|
||||
r.tot = rule.find(".node-input-rule-property-move-value").typedInput('type');
|
||||
} else if (type === "change") {
|
||||
r.from = rule.find(".node-input-rule-property-search-value").typedInput('value');
|
||||
r.fromt = rule.find(".node-input-rule-property-search-value").typedInput('type');
|
||||
r.to = rule.find(".node-input-rule-property-replace-value").typedInput('value');
|
||||
r.tot = rule.find(".node-input-rule-property-replace-value").typedInput('type');
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = size.height;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
height += 16;
|
||||
$("#node-input-rule-container").editableList('height',height);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,357 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function ChangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
var node = this;
|
||||
|
||||
this.rules = n.rules;
|
||||
var rule;
|
||||
if (!this.rules) {
|
||||
rule = {
|
||||
t:(n.action=="replace"?"set":n.action),
|
||||
p:n.property||""
|
||||
}
|
||||
|
||||
if ((rule.t === "set")||(rule.t === "move")) {
|
||||
rule.to = n.to||"";
|
||||
} else if (rule.t === "change") {
|
||||
rule.from = n.from||"";
|
||||
rule.to = n.to||"";
|
||||
rule.re = (n.reg===null||n.reg);
|
||||
}
|
||||
this.rules = [rule];
|
||||
}
|
||||
|
||||
var valid = true;
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
rule = this.rules[i];
|
||||
// Migrate to type-aware rules
|
||||
if (!rule.pt) {
|
||||
rule.pt = "msg";
|
||||
}
|
||||
if (rule.t === "change" && rule.re) {
|
||||
rule.fromt = 're';
|
||||
delete rule.re;
|
||||
}
|
||||
if (rule.t === "set" && !rule.tot) {
|
||||
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
|
||||
rule.to = rule.to.substring(4);
|
||||
rule.tot = "msg";
|
||||
}
|
||||
}
|
||||
if (!rule.tot) {
|
||||
rule.tot = "str";
|
||||
}
|
||||
if (!rule.fromt) {
|
||||
rule.fromt = "str";
|
||||
}
|
||||
if (rule.t === "change" && rule.fromt !== 'msg' && rule.fromt !== 'flow' && rule.fromt !== 'global') {
|
||||
rule.fromRE = rule.from;
|
||||
if (rule.fromt !== 're') {
|
||||
rule.fromRE = rule.fromRE.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
try {
|
||||
rule.fromRE = new RegExp(rule.fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
}
|
||||
}
|
||||
if (rule.tot === 'num') {
|
||||
rule.to = Number(rule.to);
|
||||
} else if (rule.tot === 'json' || rule.tot === 'bin') {
|
||||
try {
|
||||
// check this is parsable JSON
|
||||
JSON.parse(rule.to);
|
||||
} catch(e2) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-json"));
|
||||
}
|
||||
} else if (rule.tot === 'bool') {
|
||||
rule.to = /^true$/i.test(rule.to);
|
||||
} else if (rule.tot === 'jsonata') {
|
||||
try {
|
||||
rule.to = RED.util.prepareJSONataExpression(rule.to,this);
|
||||
} catch(e) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-expr",{error:e.message}));
|
||||
}
|
||||
} else if (rule.tot === 'env') {
|
||||
rule.to = RED.util.evaluateNodeProperty(rule.to,'env',node);
|
||||
}
|
||||
}
|
||||
|
||||
function getToValue(msg,rule,done) {
|
||||
var value = rule.to;
|
||||
if (rule.tot === 'json') {
|
||||
value = JSON.parse(rule.to);
|
||||
} else if (rule.tot === 'bin') {
|
||||
value = Buffer.from(JSON.parse(rule.to))
|
||||
}
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if ((rule.tot === 'flow') || (rule.tot === 'global')) {
|
||||
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
|
||||
if (err) {
|
||||
done(undefined,undefined);
|
||||
} else {
|
||||
done(undefined,value);
|
||||
}
|
||||
});
|
||||
return
|
||||
} else if (rule.tot === 'date') {
|
||||
value = Date.now();
|
||||
} else if (rule.tot === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
|
||||
if (err) {
|
||||
done(RED._("change.errors.invalid-expr",{error:err.message}))
|
||||
} else {
|
||||
done(undefined, value);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
done(undefined,value);
|
||||
}
|
||||
|
||||
function getFromValueType(fromValue, done) {
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
||||
fromType = 'num';
|
||||
} else if (typeof fromValue === 'boolean') {
|
||||
fromType = 'bool'
|
||||
} else if (fromValue instanceof RegExp) {
|
||||
fromType = 're';
|
||||
fromRE = fromValue;
|
||||
} else if (typeof fromValue === 'string') {
|
||||
fromType = 'str';
|
||||
fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
try {
|
||||
fromRE = new RegExp(fromRE, "g");
|
||||
} catch (e) {
|
||||
done(new Error(RED._("change.errors.invalid-from",{error:e.message})));
|
||||
}
|
||||
} else {
|
||||
done(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
|
||||
}
|
||||
done(undefined,{
|
||||
fromType,
|
||||
fromValue,
|
||||
fromRE
|
||||
});
|
||||
}
|
||||
function getFromValue(msg,rule, done) {
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (rule.t === 'change') {
|
||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
if (rule.fromt === "msg") {
|
||||
return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done);
|
||||
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
var contextKey = RED.util.parseContextStore(rule.from);
|
||||
if (/\[msg\./.test(context.key)) {
|
||||
// The key has a nest msg. reference to evaluate first
|
||||
context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true);
|
||||
}
|
||||
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
|
||||
if (err) {
|
||||
done(err)
|
||||
} else {
|
||||
getFromValueType(fromValue,done);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
fromType = rule.fromt;
|
||||
fromValue = rule.from;
|
||||
fromRE = rule.fromRE;
|
||||
}
|
||||
}
|
||||
done(undefined, {
|
||||
fromType,
|
||||
fromValue,
|
||||
fromRE
|
||||
});
|
||||
}
|
||||
function applyRule(msg,rule,done) {
|
||||
var property = rule.p;
|
||||
var current;
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
|
||||
try {
|
||||
getToValue(msg,rule,(err,value) => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
return done(undefined,null);
|
||||
} else {
|
||||
if (rule.dc) {
|
||||
value = RED.util.cloneMessage(value);
|
||||
}
|
||||
getFromValue(msg,rule,(err,fromParts) => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
return done(undefined,null);
|
||||
} else {
|
||||
fromValue = fromParts.fromValue;
|
||||
fromType = fromParts.fromType;
|
||||
fromRE = fromParts.fromRE;
|
||||
if (rule.pt === 'msg') {
|
||||
try {
|
||||
if (rule.t === 'delete') {
|
||||
RED.util.setMessageProperty(msg,property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
if (!RED.util.setMessageProperty(msg,property,value)) {
|
||||
node.warn(RED._("change.errors.no-override",{property:property}));
|
||||
}
|
||||
} else if (rule.t === 'change') {
|
||||
current = RED.util.getMessageProperty(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {}
|
||||
return done(undefined,msg);
|
||||
} else if (rule.pt === 'flow' || rule.pt === 'global') {
|
||||
var contextKey = RED.util.parseContextStore(property);
|
||||
if (/\[msg/.test(contextKey.key)) {
|
||||
// The key has a nest msg. reference to evaluate first
|
||||
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
|
||||
}
|
||||
var target = node.context()[rule.pt];
|
||||
var callback = err => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
return done(undefined,null);
|
||||
} else {
|
||||
done(undefined,msg);
|
||||
}
|
||||
}
|
||||
if (rule.t === 'delete') {
|
||||
target.set(contextKey.key,undefined,contextKey.store,callback);
|
||||
} else if (rule.t === 'set') {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
} else if (rule.t === 'change') {
|
||||
target.get(contextKey.key,contextKey.store,(err,current) => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
return done(undefined,null);
|
||||
}
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
target.set(contextKey.key,current,contextKey.store,callback);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
// This is an okay error
|
||||
done(undefined,msg);
|
||||
}
|
||||
}
|
||||
function completeApplyingRules(msg,currentRule,done) {
|
||||
if (!msg) {
|
||||
return done();
|
||||
} else if (currentRule === node.rules.length - 1) {
|
||||
return done(undefined, msg);
|
||||
} else {
|
||||
applyRules(msg, currentRule+1,done);
|
||||
}
|
||||
}
|
||||
function applyRules(msg, currentRule, done) {
|
||||
if (currentRule >= node.rules.length) {
|
||||
return done(undefined,msg);
|
||||
}
|
||||
var r = node.rules[currentRule];
|
||||
if (r.t === "move") {
|
||||
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
|
||||
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
|
||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
|
||||
completeApplyingRules(msg,currentRule,done);
|
||||
})
|
||||
});
|
||||
} else { // 2 step move if we are moving from a child
|
||||
applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt},(err,msg)=> {
|
||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt},(err,msg)=> {
|
||||
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt},(err,msg)=> {
|
||||
applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt},(err,msg)=> {
|
||||
completeApplyingRules(msg,currentRule,done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
applyRule(msg,r,(err,msg)=> { completeApplyingRules(msg,currentRule,done); });
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
this.on('input', function(msg, send, done) {
|
||||
applyRules(msg, 0, (err,msg) => {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else if (msg) {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("change", ChangeNode);
|
||||
};
|
@ -1,70 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="range">
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
|
||||
<select id="node-input-action" style="width:70%;">
|
||||
<option value="scale" data-i18n="range.scale.payload"></option>
|
||||
<option value="clamp" data-i18n="range.scale.limit"></option>
|
||||
<option value="roll" data-i18n="range.scale.wrap"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
|
||||
<div class="form-row"><label></label>
|
||||
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
|
||||
<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
|
||||
</div>
|
||||
<div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
|
||||
<div class="form-row"><label></label>
|
||||
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
|
||||
<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row"><label></label>
|
||||
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
|
||||
</div>
|
||||
<br/>
|
||||
<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="node-tip"><span data-i18n="range.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('range', {
|
||||
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()},
|
||||
action: {value:"scale"},
|
||||
round: {value:false},
|
||||
property: {value:"payload",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "range.svg",
|
||||
label: function() {
|
||||
if (this.minout !== "" && this.maxout !== "") { return this.name||this.minout + " - " + this.maxout; }
|
||||
else { return this.name||this._("range.range"); }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.property === undefined) {
|
||||
$("#node-input-property").val("payload");
|
||||
}
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function RangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.action = n.action;
|
||||
this.round = n.round || false;
|
||||
this.minin = Number(n.minin);
|
||||
this.maxin = Number(n.maxin);
|
||||
this.minout = Number(n.minout);
|
||||
this.maxout = Number(n.maxout);
|
||||
this.property = n.property||"payload";
|
||||
var node = this;
|
||||
|
||||
this.on('input', function (msg, send, done) {
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
var n = Number(value);
|
||||
if (!isNaN(n)) {
|
||||
if (node.action == "clamp") {
|
||||
if (n < node.minin) { n = node.minin; }
|
||||
if (n > node.maxin) { n = node.maxin; }
|
||||
}
|
||||
if (node.action == "roll") {
|
||||
var divisor = node.maxin - node.minin;
|
||||
n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;
|
||||
}
|
||||
value = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
|
||||
if (node.round) { value = Math.round(value); }
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
send(msg);
|
||||
}
|
||||
else { node.log(RED._("range.errors.notnumber")+": "+value); }
|
||||
}
|
||||
else { send(msg); } // If no payload - just pass it on.
|
||||
done();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("range", RangeNode);
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="template">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-ellipsis-h"></i> <span data-i18n="template.label.property"></span></label>
|
||||
<input type="text" id="node-input-field" placeholder="payload" style="width:250px;">
|
||||
<input type="hidden" id="node-input-fieldType">
|
||||
</div>
|
||||
<div class="form-row" style="position: relative; margin-bottom: 0px;">
|
||||
<label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
|
||||
<input type="hidden" id="node-input-template" autofocus="autofocus">
|
||||
<div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
|
||||
<span data-i18n="template.label.format"></span>:
|
||||
<select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
|
||||
<option value="handlebars">mustache</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="javascript">Javascript</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="python">Python</option>
|
||||
<option value="sql">SQL</option>
|
||||
<option value="yaml">YAML</option>
|
||||
<option value="text" data-i18n="template.label.none"></option>
|
||||
</select>
|
||||
<button id="node-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-template-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-syntax"><i class="fa fa-code"></i> <span data-i18n="template.label.syntax"></span></label>
|
||||
<select id="node-input-syntax" style="width:180px;">
|
||||
<option value="mustache" data-i18n="template.label.mustache"></option>
|
||||
<option value="plain" data-i18n="template.label.plain"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-output"><i class="fa fa-long-arrow-right"></i> <span data-i18n="template.label.output"></span></label>
|
||||
<select id="node-input-output" style="width:180px;">
|
||||
<option value="str" data-i18n="template.label.plain"></option>
|
||||
<option value="json" data-i18n="template.label.json"></option>
|
||||
<option value="yaml" data-i18n="template.label.yaml"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('template',{
|
||||
color:"rgb(243, 181, 103)",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
field: {value:"payload", validate:RED.validators.typedInput("fieldType")},
|
||||
fieldType: {value:"msg"},
|
||||
format: {value:"handlebars"},
|
||||
syntax: {value:"mustache"},
|
||||
template: {value:"This is the payload: {{payload}} !"},
|
||||
output: {value:"str"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "template.svg",
|
||||
label: function() {
|
||||
return this.name||this._("template.template");;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
if (!this.field) {
|
||||
this.field = 'payload';
|
||||
$("#node-input-field").val("payload");
|
||||
}
|
||||
if (!this.fieldType) {
|
||||
this.fieldType = 'msg';
|
||||
}
|
||||
if (!this.syntax) {
|
||||
this.syntax = 'mustache';
|
||||
$("#node-input-syntax").val(this.syntax);
|
||||
}
|
||||
$("#node-input-field").typedInput({
|
||||
default: 'msg',
|
||||
types: ['msg','flow','global'],
|
||||
typeField: $("#node-input-fieldType")
|
||||
});
|
||||
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-template-editor',
|
||||
mode: 'ace/mode/html',
|
||||
value: $("#node-input-template").val()
|
||||
});
|
||||
RED.library.create({
|
||||
url:"templates", // where to get the data from
|
||||
type:"template", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','format','output','syntax'],
|
||||
ext: "txt"
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
$("#node-input-format").on("change", function() {
|
||||
var mod = "ace/mode/"+$("#node-input-format").val();
|
||||
that.editor.getSession().setMode({
|
||||
path: mod,
|
||||
v: Date.now()
|
||||
});
|
||||
});
|
||||
RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand"));
|
||||
$("#node-template-expand-editor").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var value = that.editor.getValue();
|
||||
RED.editor.editText({
|
||||
mode: $("#node-input-format").val(),
|
||||
value: value,
|
||||
width: "Infinity",
|
||||
cursor: that.editor.getCursorPosition(),
|
||||
complete: function(v,cursor) {
|
||||
that.editor.setValue(v, -1);
|
||||
that.editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
that.editor.focus();
|
||||
},300);
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-template").val(this.editor.getValue());
|
||||
this.editor.destroy();
|
||||
delete this.editor;
|
||||
},
|
||||
oneditcancel: function() {
|
||||
this.editor.destroy();
|
||||
delete this.editor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
this.editor.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,200 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var mustache = require("mustache");
|
||||
var yaml = require("js-yaml");
|
||||
|
||||
function extractTokens(tokens,set) {
|
||||
set = set || new Set();
|
||||
tokens.forEach(function(token) {
|
||||
if (token[0] !== 'text') {
|
||||
set.add(token[1]);
|
||||
if (token.length > 4) {
|
||||
extractTokens(token[4],set);
|
||||
}
|
||||
}
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
function parseContext(key) {
|
||||
var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
|
||||
if (match) {
|
||||
var parts = {};
|
||||
parts.type = match[1];
|
||||
parts.store = (match[3] === '') ? "default" : match[3];
|
||||
parts.field = match[4];
|
||||
return parts;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Mustache Context capable to collect message property and node
|
||||
* flow and global context
|
||||
*/
|
||||
|
||||
function NodeContext(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
|
||||
this.msgContext = new mustache.Context(msg,parent);
|
||||
this.nodeContext = nodeContext;
|
||||
this.escapeStrings = escapeStrings;
|
||||
this.cachedContextTokens = cachedContextTokens;
|
||||
}
|
||||
|
||||
NodeContext.prototype = new mustache.Context();
|
||||
|
||||
NodeContext.prototype.lookup = function (name) {
|
||||
// try message first:
|
||||
try {
|
||||
var value = this.msgContext.lookup(name);
|
||||
if (value !== undefined) {
|
||||
if (this.escapeStrings && typeof value === "string") {
|
||||
value = value.replace(/\\/g, "\\\\");
|
||||
value = value.replace(/\n/g, "\\n");
|
||||
value = value.replace(/\t/g, "\\t");
|
||||
value = value.replace(/\r/g, "\\r");
|
||||
value = value.replace(/\f/g, "\\f");
|
||||
value = value.replace(/[\b]/g, "\\b");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// try flow/global context:
|
||||
var context = parseContext(name);
|
||||
if (context) {
|
||||
var type = context.type;
|
||||
var store = context.store;
|
||||
var field = context.field;
|
||||
var target = this.nodeContext[type];
|
||||
if (target) {
|
||||
return this.cachedContextTokens[name];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
catch(err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
NodeContext.prototype.push = function push (view) {
|
||||
return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.cachedContextTokens);
|
||||
};
|
||||
|
||||
function TemplateNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.field = n.field || "payload";
|
||||
this.template = n.template;
|
||||
this.syntax = n.syntax || "mustache";
|
||||
this.fieldType = n.fieldType || "msg";
|
||||
this.outputFormat = n.output || "str";
|
||||
|
||||
var node = this;
|
||||
|
||||
function output(msg,value,send,done) {
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "json") {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "yaml") {
|
||||
value = yaml.load(value);
|
||||
}
|
||||
|
||||
if (node.fieldType === 'msg') {
|
||||
RED.util.setMessageProperty(msg, node.field, value);
|
||||
send(msg);
|
||||
done();
|
||||
} else if ((node.fieldType === 'flow') ||
|
||||
(node.fieldType === 'global')) {
|
||||
var context = RED.util.parseContextStore(node.field);
|
||||
var target = node.context()[node.fieldType];
|
||||
target.set(context.key, value, context.store, function (err) {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
node.on("input", function(msg, send, done) {
|
||||
|
||||
try {
|
||||
/***
|
||||
* Allow template contents to be defined externally
|
||||
* through inbound msg.template IFF node.template empty
|
||||
*/
|
||||
var template = node.template;
|
||||
if (msg.hasOwnProperty("template")) {
|
||||
if (template == "" || template === null) {
|
||||
template = msg.template;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.syntax === "mustache") {
|
||||
var is_json = (node.outputFormat === "json");
|
||||
var promises = [];
|
||||
var tokens = extractTokens(mustache.parse(template));
|
||||
var resolvedTokens = {};
|
||||
tokens.forEach(function(name) {
|
||||
var context = parseContext(name);
|
||||
if (context) {
|
||||
var type = context.type;
|
||||
var store = context.store;
|
||||
var field = context.field;
|
||||
var target = node.context()[type];
|
||||
if (target) {
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
target.get(field, store, (err, val) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolvedTokens[name] = val;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
promises.push(promise);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(promises).then(function() {
|
||||
var value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, resolvedTokens));
|
||||
output(msg, value, send, done);
|
||||
}).catch(function (err) {
|
||||
done(err.message);
|
||||
});
|
||||
} else {
|
||||
output(msg, template, send, done);
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
done(err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("template",TemplateNode);
|
||||
RED.library.register("templates");
|
||||
}
|
@ -1,283 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="delay">
|
||||
<div class="form-row">
|
||||
<label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
|
||||
<select id="node-input-delay-action" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="delay-details">
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<select id="node-input-delay-type" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delayfixed"></option>
|
||||
<option value="random" data-i18n="delay.randomdelay"></option>
|
||||
<option value="delayv" data-i18n="delay.delayvarmsg"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="delay-details-for">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
|
||||
<input type="text" id="node-input-timeout" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="random-details" class="form-row">
|
||||
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:50px !important">
|
||||
<span data-i18n="delay.and"></span>
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rate-details">
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<select id="node-input-rate-type" style="width:270px !important">
|
||||
<option value="all" data-i18n="delay.limitall"></option>
|
||||
<option value="topic" data-i18n="delay.limittopic"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="text-align:end; width:40px !important">
|
||||
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
|
||||
<input type="text" id="node-input-nbRateUnits" placeholder="1" style="text-align:end; width:40px !important">
|
||||
<select id="node-input-rateUnits" style="width:90px !important">
|
||||
<option value="second" data-i18n="delay.label.units.second.singular"></option>
|
||||
<option value="minute" data-i18n="delay.label.units.minute.singular"></option>
|
||||
<option value="hour" data-i18n="delay.label.units.hour.singular"></option>
|
||||
<option value="day" data-i18n="delay.label.units.day.singular"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="rate-override" style="display: flex; align-items: center">
|
||||
<label></label><input style="width:30px; margin:0" type="checkbox" id="node-input-allowrate"><label style="margin:0;width: auto;" for="node-input-allowrate" data-i18n="delay.allowrate"></label>
|
||||
</div>
|
||||
<div class="form-row" id="rate-details-drop">
|
||||
<input type="hidden" id="node-input-outputs" value="1">
|
||||
<label></label>
|
||||
<select id="node-input-drop-select" style="width: 70%">
|
||||
<option id="node-input-drop-select-queue" value="queue" data-i18n="delay.queuemsg"></option>
|
||||
<option value="drop" data-i18n="delay.dropmsg"></option>
|
||||
<option value="emit" data-i18n="delay.sendmsg"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="rate-details-per-topic">
|
||||
<label></label>
|
||||
<select id="node-input-rate-topic-type" style="width:270px !important">
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="timed" data-i18n="delay.timedqueue"></option>
|
||||
</select>
|
||||
</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">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('delay',{
|
||||
category: 'function',
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pauseType: {value:"delay", required:true},
|
||||
timeout: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
nbRateUnits: {value:"1", required:false, validate:RED.validators.regex(/\d+|/)},
|
||||
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); }},
|
||||
randomUnits: {value: "seconds"},
|
||||
drop: {value:false},
|
||||
allowrate: {value:false},
|
||||
outputs: { value: 1},
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "timer.svg",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.pauseType == "delayv") {
|
||||
return this._("delay.label.variable");
|
||||
} else if (this.pauseType == "delay") {
|
||||
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
|
||||
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
|
||||
return this._("delay.label.delay")+" "+this.timeout+units;
|
||||
} else if (this.pauseType == "random") {
|
||||
return this._("delay.label.random");
|
||||
} else {
|
||||
var rate = this.rate+" msg/"+(this.rateUnits ? (this.nbRateUnits > 1 ? this.nbRateUnits : '') + this.rateUnits.charAt(0) : "s");
|
||||
if (this.pauseType == "rate") {
|
||||
return this._("delay.label.limit")+" "+rate;
|
||||
} else if (this.pauseType == "timed") {
|
||||
return this._("delay.label.limitTopic")+" "+rate;
|
||||
} else {
|
||||
return this._("delay.label.limitTopic")+" "+rate;
|
||||
}
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
$( "#node-input-timeout" ).spinner({min:1});
|
||||
$( "#node-input-rate" ).spinner({min:1});
|
||||
$( "#node-input-nbRateUnits" ).spinner({min:1});
|
||||
|
||||
$( "#node-input-randomFirst" ).spinner({min:0});
|
||||
$( "#node-input-randomLast" ).spinner({min:1});
|
||||
|
||||
$('.ui-spinner-button').on("click", function() {
|
||||
$(this).siblings('input').trigger("change");
|
||||
});
|
||||
|
||||
$( "#node-input-nbRateUnits" ).on('change keyup', function() {
|
||||
var $this = $(this);
|
||||
var val = parseInt($this.val());
|
||||
var type = "singular";
|
||||
if (val > 1) {
|
||||
type = "plural";
|
||||
}
|
||||
if ($this.attr("data-type") == type) {
|
||||
return;
|
||||
}
|
||||
$this.attr("data-type", type);
|
||||
$("#node-input-rateUnits option").each(function () {
|
||||
var $option = $(this);
|
||||
var key = "delay.label.units." + $option.val() + "." + type;
|
||||
$option.attr('data-i18n', 'node-red:' + key);
|
||||
$option.html(node._(key));
|
||||
});
|
||||
});
|
||||
|
||||
if (this.pauseType == "delay") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('delay');
|
||||
} else if (this.pauseType == "delayv") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('delayv');
|
||||
} else if (this.pauseType == "random") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('random');
|
||||
} else if (this.pauseType == "rate") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('all');
|
||||
} else if (this.pauseType == "queue") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('topic');
|
||||
$("#node-input-rate-topic-type").val('queue');
|
||||
} else if (this.pauseType == "timed") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('topic');
|
||||
$("#node-input-rate-topic-type").val('timed');
|
||||
}
|
||||
|
||||
if (!this.timeoutUnits) {
|
||||
$("#node-input-timeoutUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
if (!this.randomUnits) {
|
||||
$("#node-input-randomUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
$("#node-input-delay-action").on("change",function() {
|
||||
if (this.value === "delay") {
|
||||
$("#delay-details").show();
|
||||
$("#rate-details").hide();
|
||||
} else if (this.value === "rate") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
$("#node-input-delay-type").on("change", function() {
|
||||
if (this.value === "delay") {
|
||||
$("#delay-details-for").show();
|
||||
$("#random-details").hide();
|
||||
} else if (this.value === "delayv") {
|
||||
$("#delay-details-for").show();
|
||||
$("#random-details").hide();
|
||||
} else if (this.value === "random") {
|
||||
$("#delay-details-for").hide();
|
||||
$("#random-details").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
if (this.outputs === 2) {
|
||||
$("#node-input-drop-select").val("emit");
|
||||
} else if (this.drop) {
|
||||
$("#node-input-drop-select").val("drop");
|
||||
} else {
|
||||
$("#node-input-drop-select").val("queue");
|
||||
}
|
||||
|
||||
$("#node-input-rate-type").on("change", function() {
|
||||
if (this.value === "all") {
|
||||
$("#rate-details-per-topic").hide();
|
||||
$("#node-input-drop-select-queue").attr('disabled', false);
|
||||
} else if (this.value === "topic") {
|
||||
if ($("#node-input-drop-select").val() === "queue") {
|
||||
$("#node-input-drop-select").val("drop");
|
||||
}
|
||||
$("#node-input-drop-select-queue").attr('disabled', true);
|
||||
$("#rate-details-per-topic").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
},
|
||||
oneditsave: function() {
|
||||
var action = $("#node-input-delay-action").val();
|
||||
if (action === "delay") {
|
||||
this.pauseType = $("#node-input-delay-type").val();
|
||||
$("#node-input-outputs").val(1);
|
||||
} else if (action === "rate") {
|
||||
action = $("#node-input-rate-type").val();
|
||||
if (action === "all") {
|
||||
this.pauseType = "rate";
|
||||
} else {
|
||||
this.pauseType = $("#node-input-rate-topic-type").val();
|
||||
}
|
||||
var dropType = $("#node-input-drop-select").val();
|
||||
this.drop = dropType !== "queue";
|
||||
$("#node-input-outputs").val(dropType === "emit"?2:1);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,424 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
//Simple node to introduce a pause into a flow
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var MILLIS_TO_NANOS = 1000000;
|
||||
var SECONDS_TO_NANOS = 1000000000;
|
||||
var _maxKeptMsgsCount;
|
||||
|
||||
function maxKeptMsgsCount(node) {
|
||||
if (_maxKeptMsgsCount === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_maxKeptMsgsCount = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_maxKeptMsgsCount = 0;
|
||||
}
|
||||
}
|
||||
return _maxKeptMsgsCount;
|
||||
}
|
||||
|
||||
function DelayNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.pauseType = n.pauseType;
|
||||
this.timeoutUnits = n.timeoutUnits;
|
||||
this.randomUnits = n.randomUnits;
|
||||
this.rateUnits = n.rateUnits;
|
||||
|
||||
if (n.timeoutUnits === "milliseconds") {
|
||||
this.timeout = n.timeout;
|
||||
} else if (n.timeoutUnits === "minutes") {
|
||||
this.timeout = n.timeout * (60 * 1000);
|
||||
} else if (n.timeoutUnits === "hours") {
|
||||
this.timeout = n.timeout * (60 * 60 * 1000);
|
||||
} else if (n.timeoutUnits === "days") {
|
||||
this.timeout = n.timeout * (24 * 60 * 60 * 1000);
|
||||
} else { // Default to seconds
|
||||
this.timeout = n.timeout * 1000;
|
||||
}
|
||||
|
||||
if (n.rateUnits === "minute") {
|
||||
this.rate = (60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "hour") {
|
||||
this.rate = (60 * 60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "day") {
|
||||
this.rate = (24 * 60 * 60 * 1000)/n.rate;
|
||||
} else { // Default to seconds
|
||||
this.rate = 1000/n.rate;
|
||||
}
|
||||
|
||||
this.rate *= (n.nbRateUnits > 0 ? n.nbRateUnits : 1);
|
||||
|
||||
if (n.randomUnits === "milliseconds") {
|
||||
this.randomFirst = n.randomFirst * 1;
|
||||
this.randomLast = n.randomLast * 1;
|
||||
} else if (n.randomUnits === "minutes") {
|
||||
this.randomFirst = n.randomFirst * (60 * 1000);
|
||||
this.randomLast = n.randomLast * (60 * 1000);
|
||||
} else if (n.randomUnits === "hours") {
|
||||
this.randomFirst = n.randomFirst * (60 * 60 * 1000);
|
||||
this.randomLast = n.randomLast * (60 * 60 * 1000);
|
||||
} else if (n.randomUnits === "days") {
|
||||
this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
|
||||
this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
|
||||
} else { // Default to seconds
|
||||
this.randomFirst = n.randomFirst * 1000;
|
||||
this.randomLast = n.randomLast * 1000;
|
||||
}
|
||||
|
||||
this.diff = this.randomLast - this.randomFirst;
|
||||
this.name = n.name;
|
||||
this.idList = [];
|
||||
this.buffer = [];
|
||||
this.intervalID = -1;
|
||||
this.randomID = -1;
|
||||
this.lastSent = null;
|
||||
this.drop = n.drop;
|
||||
this.droppedMsgs = 0;
|
||||
this.allowrate = n.allowrate|| false;
|
||||
this.fixedrate = this.rate;
|
||||
this.outputs = n.outputs;
|
||||
var node = this;
|
||||
|
||||
function ourTimeout(handler, delay, clearHandler) {
|
||||
var toutID = setTimeout(handler, delay);
|
||||
return {
|
||||
clear: function() { clearTimeout(toutID); clearHandler(); },
|
||||
trigger: function() { clearTimeout(toutID); return handler(); }
|
||||
};
|
||||
}
|
||||
|
||||
var sendMsgFromBuffer = function() {
|
||||
if (node.buffer.length === 0) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
}
|
||||
if (node.buffer.length > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
if (Object.keys(msgInfo.msg).length > 1) {
|
||||
msgInfo.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
}
|
||||
node.reportDepth();
|
||||
}
|
||||
|
||||
var clearDelayList = function(s) {
|
||||
var len = node.idList.length;
|
||||
for (var i=0; i<len; i++ ) { node.idList[i].clear(); }
|
||||
node.idList = [];
|
||||
if (s) { node.status({fill:"blue",shape:"ring",text:0}); }
|
||||
else { node.status({}); }
|
||||
}
|
||||
|
||||
var flushDelayList = function(n) {
|
||||
var len = node.idList.length;
|
||||
if (typeof(n) == 'number') { len = Math.min(Math.floor(n),len); }
|
||||
for (var i=0; i<len; i++ ) { node.idList[0].trigger(); }
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
|
||||
node.reportDepth = function() {
|
||||
if (!node.busy) {
|
||||
node.busy = setTimeout(function() {
|
||||
// if (node.buffer.length > 0) { node.status({text:node.buffer.length}); }
|
||||
// else { node.status({}); }
|
||||
node.status({fill:"blue",shape:"dot",text:node.buffer.length});
|
||||
node.busy = null;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
var loggerId = setInterval(function () {
|
||||
if (node.droppedMsgs !== 0) {
|
||||
node.debug("node.droppedMsgs = " + node.droppedMsgs);
|
||||
node.droppedMsgs = 0;
|
||||
}
|
||||
}, 15 * 1000);
|
||||
node.on("close", function() { clearInterval(loggerId); });
|
||||
|
||||
// The delay type modes
|
||||
if (node.pauseType === "delay") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
if (node.timeout > 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
send(msg);
|
||||
done();
|
||||
}, node.timeout, () => done());
|
||||
if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
|
||||
else { node.idList.push(id); }
|
||||
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
|
||||
else if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
|
||||
else if (node.timeout > 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
});
|
||||
node.on("close", function() { clearDelayList(); });
|
||||
}
|
||||
else if (node.pauseType === "delayv") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
var delayvar = Number(node.timeout);
|
||||
if (msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
|
||||
delayvar = parseFloat(msg.delay);
|
||||
}
|
||||
if (delayvar < 0) { delayvar = 0; }
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
if (node.idList.length === 0) { node.status({}); }
|
||||
send(msg);
|
||||
if (delayvar >= 0) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
done();
|
||||
}, delayvar, () => done());
|
||||
node.idList.push(id);
|
||||
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
|
||||
if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
|
||||
if (delayvar >= 0) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
});
|
||||
node.on("close", function() { clearDelayList(); });
|
||||
}
|
||||
else if (node.pauseType === "random") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
var wait = node.randomFirst + (node.diff * Math.random());
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
send(msg);
|
||||
if (node.timeout >= 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
done();
|
||||
}, wait, () => done());
|
||||
if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
|
||||
else { node.idList.push(id); }
|
||||
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
|
||||
if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
|
||||
if (node.timeout >= 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
});
|
||||
node.on("close", function() { clearDelayList(); });
|
||||
}
|
||||
|
||||
// The rate limit/queue type modes
|
||||
else if (node.pauseType === "rate") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (node.intervalID !== -1 ) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
}
|
||||
delete node.lastSent;
|
||||
node.buffer = [];
|
||||
node.rate = node.fixedrate;
|
||||
node.status({fill:"blue",shape:"ring",text:0});
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.drop) {
|
||||
var m = RED.util.cloneMessage(msg);
|
||||
delete m.flush;
|
||||
delete m.lifo;
|
||||
if (Object.keys(m).length > 1) {
|
||||
if (node.intervalID !== -1) {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
|
||||
node.rate = msg.rate;
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||
}
|
||||
var max_msgs = maxKeptMsgsCount(node);
|
||||
if ((max_msgs > 0) && (node.buffer.length >= max_msgs)) {
|
||||
node.buffer = [];
|
||||
node.error(RED._("delay.errors.too-many"), msg);
|
||||
} else if (msg.toFront === true) {
|
||||
node.buffer.unshift({msg: m, send: send, done: done});
|
||||
node.reportDepth();
|
||||
} else {
|
||||
node.buffer.push({msg: m, send: send, done: done});
|
||||
node.reportDepth();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
|
||||
node.rate = msg.rate;
|
||||
}
|
||||
send(m);
|
||||
node.reportDepth();
|
||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||
done();
|
||||
}
|
||||
}
|
||||
if (msg.hasOwnProperty("flush")) {
|
||||
var len = node.buffer.length;
|
||||
if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush),len); }
|
||||
while (len > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
if (Object.keys(msgInfo.msg).length > 1) {
|
||||
node.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
len = len - 1;
|
||||
}
|
||||
if (node.buffer.length === 0) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
}
|
||||
node.status({fill:"blue",shape:"dot",text:node.buffer.length});
|
||||
done();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (maxKeptMsgsCount(node) > 0) {
|
||||
if (node.intervalID === -1) {
|
||||
node.send(msg);
|
||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||
} else {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
|
||||
node.rate = msg.rate;
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||
}
|
||||
if (node.buffer.length < _maxKeptMsgsCount) {
|
||||
var m = RED.util.cloneMessage(msg);
|
||||
node.buffer.push({msg: m, send: send, done: done});
|
||||
} else {
|
||||
node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
|
||||
node.droppedMsgs++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
|
||||
node.rate = msg.rate;
|
||||
}
|
||||
var timeSinceLast;
|
||||
if (node.lastSent) {
|
||||
timeSinceLast = process.hrtime(node.lastSent);
|
||||
}
|
||||
if (!node.lastSent) { // ensuring that we always send the first message
|
||||
node.lastSent = process.hrtime();
|
||||
send(msg);
|
||||
}
|
||||
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
|
||||
node.lastSent = process.hrtime();
|
||||
send(msg);
|
||||
} else if (node.outputs === 2) {
|
||||
send([null,msg])
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
node.on("close", function() {
|
||||
clearInterval(node.intervalID);
|
||||
clearTimeout(node.busy);
|
||||
node.buffer.forEach((msgInfo) => msgInfo.done());
|
||||
node.buffer = [];
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
|
||||
// The topic based fair queue and last arrived on all topics queue
|
||||
else if ((node.pauseType === "queue") || (node.pauseType === "timed")) {
|
||||
node.intervalID = setInterval(function() {
|
||||
if (node.pauseType === "queue") {
|
||||
if (node.buffer.length > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
msgInfo.send(msgInfo.msg); // send the first on the queue
|
||||
msgInfo.done();
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (node.buffer.length > 0) { // send the whole queue
|
||||
const msgInfo = node.buffer.shift();
|
||||
msgInfo.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
}
|
||||
node.reportDepth();
|
||||
},node.rate);
|
||||
|
||||
var hit;
|
||||
node.on("input", function(msg, send, done) {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
|
||||
node.rate = msg.rate;
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||
}
|
||||
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
|
||||
hit = false;
|
||||
for (var b in node.buffer) { // check if already in queue
|
||||
if (msg.topic === node.buffer[b].msg.topic) {
|
||||
if (node.outputs === 2) { send([null,node.buffer[b].msg]) }
|
||||
node.buffer[b].done();
|
||||
node.buffer[b] = {msg, send, done}; // if so - replace existing entry
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit) {
|
||||
node.buffer.push({msg, send, done}); // if not add to end of queue
|
||||
node.reportDepth();
|
||||
}
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
while (node.buffer.length > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
msgInfo.done();
|
||||
}
|
||||
node.buffer = [];
|
||||
node.rate = node.fixedrate;
|
||||
node.status({text:"reset"});
|
||||
done();
|
||||
}
|
||||
if (msg.hasOwnProperty("flush")) {
|
||||
var len = node.buffer.length;
|
||||
if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
|
||||
while (len > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
delete msgInfo.msg.flush;
|
||||
if (Object.keys(msgInfo.msg).length > 2) {
|
||||
node.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
len = len - 1;
|
||||
}
|
||||
node.status({});
|
||||
done();
|
||||
}
|
||||
});
|
||||
node.on("close", function() {
|
||||
clearInterval(node.intervalID);
|
||||
while (node.buffer.length > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
msgInfo.done();
|
||||
}
|
||||
node.buffer = [];
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("delay",DelayNode);
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="trigger">
|
||||
<div class="form-row">
|
||||
<label data-i18n="trigger.send" for="node-input-op1"></label>
|
||||
<input type="hidden" id="node-input-op1type">
|
||||
<input style="width:70%" type="text" id="node-input-op1" placeholder="1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label data-i18n="trigger.then"></label>
|
||||
<select id="node-then-type" style="width:70%;">
|
||||
<option value="block" data-i18n="trigger.wait-reset"></option>
|
||||
<option value="wait" data-i18n="trigger.wait-for"></option>
|
||||
<option value="loop" data-i18n="trigger.wait-loop"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-type-duration">
|
||||
<label></label>
|
||||
<input type="text" id="node-input-duration" style="text-align:end; width:70px !important">
|
||||
<select id="node-input-units" style="width:140px !important">
|
||||
<option value="ms" data-i18n="trigger.duration.ms"></option>
|
||||
<option value="s" data-i18n="trigger.duration.s"></option>
|
||||
<option value="min" data-i18n="trigger.duration.m"></option>
|
||||
<option value="hr" data-i18n="trigger.duration.h"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-type-wait">
|
||||
<label></label>
|
||||
<input type="checkbox" id="node-input-extend" style="margin-left:0px; vertical-align:top; width:auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
|
||||
</div>
|
||||
<div class="form-row node-type-override">
|
||||
<label></label>
|
||||
<input type="checkbox" id="node-input-overrideDelay" style="margin-left:0px; vertical-align:top; width:auto !important;"> <label style="width:auto !important;" for="node-input-overrideDelay" data-i18n="trigger.override"></label>
|
||||
</div>
|
||||
<div class="form-row node-type-wait">
|
||||
<label data-i18n="trigger.then-send"></label>
|
||||
<input type="hidden" id="node-input-op2type">
|
||||
<input style="width:70%" type="text" id="node-input-op2" placeholder="0">
|
||||
</div>
|
||||
<div class="form-row" id="node-second-output">
|
||||
<label></label>
|
||||
<input type="checkbox" id="node-input-second" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-second" data-i18n="trigger.second"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label data-i18n="trigger.label.reset" style="width:auto"></label>
|
||||
<div style="display:inline-block; width:70%;vertical-align:top">
|
||||
<ul>
|
||||
<li data-i18n="trigger.label.resetMessage"></li>
|
||||
<li><span data-i18n="trigger.label.resetPayload"></span> <input type="text" id="node-input-reset" style="width:150px" data-i18n="[placeholder]trigger.label.resetprompt"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label data-i18n="trigger.for" for="node-input-bytopic"></label>
|
||||
<select id="node-input-bytopic" style="width:120px;">
|
||||
<option value="all" data-i18n="trigger.alltopics"></option>
|
||||
<option value="topic" data-i18n="trigger.bytopics"></option>
|
||||
</select>
|
||||
<span class="form-row" id="node-stream-topic">
|
||||
<input type="text" id="node-input-topic" style="width:46%;"/>
|
||||
</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>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></input>
|
||||
<input type="hidden" id="node-input-outputs" value="1">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('trigger',{
|
||||
category: 'function',
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
op1: {value:"1", validate: RED.validators.typedInput("op1type")},
|
||||
op2: {value:"0", validate: RED.validators.typedInput("op2type")},
|
||||
op1type: {value:"val"},
|
||||
op2type: {value:"val"},
|
||||
duration: {value:"250",required:true,validate:RED.validators.number()},
|
||||
extend: {value:"false"},
|
||||
overrideDelay: {value:"false"},
|
||||
units: {value:"ms"},
|
||||
reset: {value:""},
|
||||
bytopic: {value:"all"},
|
||||
topic: {value:"topic",required:true},
|
||||
outputs: {value:1}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "trigger.svg",
|
||||
label: function() {
|
||||
if (this.duration > 0) {
|
||||
return this.name|| this._("trigger.label.trigger")+" "+this.duration+this.units;
|
||||
}
|
||||
if (this.duration < 0) {
|
||||
return this.name|| this._("trigger.label.trigger-loop")+" "+(this.duration * -1)+this.units;
|
||||
}
|
||||
else {
|
||||
return this.name|| this._("trigger.label.trigger-block");
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
if (this.topic === undefined) { $("#node-input-topic").val("topic"); }
|
||||
$("#node-input-topic").typedInput({default:'msg',types:['msg']});
|
||||
$("#node-input-bytopic").on("change", function() {
|
||||
if ($("#node-input-bytopic").val() === "all") {
|
||||
$("#node-stream-topic").hide();
|
||||
} else {
|
||||
$("#node-stream-topic").show();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.outputs == 2) { $("#node-input-second").prop('checked', true) }
|
||||
else { $("#node-input-second").prop('checked', false) }
|
||||
|
||||
$("#node-input-second").change(function() {
|
||||
if ($("#node-input-second").is(":checked")) {
|
||||
$("#node-input-outputs").val(2);
|
||||
}
|
||||
else {
|
||||
$("#node-input-outputs").val(1);
|
||||
}
|
||||
});
|
||||
$("#node-then-type").on("change", function() {
|
||||
if ($(this).val() == "block") {
|
||||
$(".node-type-wait").hide();
|
||||
$(".node-type-override").hide();
|
||||
$(".node-type-duration").hide();
|
||||
$("#node-second-output").hide();
|
||||
$("#node-input-second").prop('checked', false);
|
||||
$("#node-input-outputs").val(1);
|
||||
}
|
||||
else if ($(this).val() == "loop") {
|
||||
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
|
||||
$(".node-type-wait").hide();
|
||||
$(".node-type-override").show();
|
||||
$(".node-type-duration").show();
|
||||
$("#node-second-output").hide();
|
||||
$("#node-input-second").prop('checked', false);
|
||||
$("#node-input-outputs").val(1);
|
||||
} else {
|
||||
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
|
||||
$(".node-type-wait").show();
|
||||
$(".node-type-override").show();
|
||||
$(".node-type-duration").show();
|
||||
$("#node-second-output").show();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.op1type === 'val') {
|
||||
$("#node-input-op1type").val('str');
|
||||
}
|
||||
if (this.op2type === 'val') {
|
||||
$("#node-input-op2type").val('str');
|
||||
}
|
||||
|
||||
var optionNothing = {value:"nul",label:this._("trigger.output.nothing"),hasValue:false};
|
||||
var optionPayload = {value:"pay",label:this._("trigger.output.existing"),hasValue:false};
|
||||
var optionOriginalPayload = {value:"pay",label:this._("trigger.output.original"),hasValue:false};
|
||||
var optionLatestPayload = {value:"payl",label:this._("trigger.output.latest"),hasValue:false};
|
||||
|
||||
$("#node-input-op1").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-op1type"),
|
||||
types:['flow','global','str','num','bool','json','bin','date','env',
|
||||
optionPayload,
|
||||
optionNothing
|
||||
]
|
||||
});
|
||||
$("#node-input-op2").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-op2type"),
|
||||
types:['flow','global','str','num','bool','json','bin','date','env',
|
||||
optionOriginalPayload,
|
||||
optionLatestPayload,
|
||||
optionNothing
|
||||
]
|
||||
});
|
||||
|
||||
if (this.bytopic === undefined) {
|
||||
$("#node-input-bytopic").val("all");
|
||||
}
|
||||
|
||||
if (this.duration == "0") {
|
||||
$("#node-then-type").val("block");
|
||||
}
|
||||
else if ((this.duration * 1) < 0) {
|
||||
$("#node-then-type").val("loop");
|
||||
$("#node-input-duration").val(this.duration*-1);
|
||||
} else {
|
||||
$("#node-then-type").val("wait");
|
||||
}
|
||||
$("#node-then-type").trigger("change");
|
||||
|
||||
if (this.extend === "true" || this.extend === true) {
|
||||
$("#node-input-extend").prop("checked",true);
|
||||
} else {
|
||||
$("#node-input-extend").prop("checked",false);
|
||||
}
|
||||
if (this.overrideDelay === "true" || this.overrideDelay === true) {
|
||||
$("#node-input-overrideDelay").prop("checked",true);
|
||||
} else {
|
||||
$("#node-input-overrideDelay").prop("checked",false);
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if ($("#node-then-type").val() == "block") {
|
||||
$("#node-input-duration").val("0");
|
||||
}
|
||||
if ($("#node-then-type").val() == "loop") {
|
||||
$("#node-input-duration").val($("#node-input-duration").val() * -1);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,298 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var mustache = require("mustache");
|
||||
function TriggerNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.bytopic = n.bytopic || "all";
|
||||
this.op1 = n.op1 || "1";
|
||||
this.op2 = n.op2 || "0";
|
||||
this.op1type = n.op1type || "str";
|
||||
this.op2type = n.op2type || "str";
|
||||
this.second = (n.outputs == 2) ? true : false;
|
||||
this.topic = n.topic || "topic";
|
||||
|
||||
if (this.op1type === 'val') {
|
||||
if (this.op1 === 'true' || this.op1 === 'false') {
|
||||
this.op1type = 'bool'
|
||||
} else if (this.op1 === 'null') {
|
||||
this.op1type = 'null';
|
||||
this.op1 = null;
|
||||
} else {
|
||||
this.op1type = 'str';
|
||||
}
|
||||
}
|
||||
if (this.op2type === 'val') {
|
||||
if (this.op2 === 'true' || this.op2 === 'false') {
|
||||
this.op2type = 'bool'
|
||||
} else if (this.op2 === 'null') {
|
||||
this.op2type = 'null';
|
||||
this.op2 = null;
|
||||
} else {
|
||||
this.op2type = 'str';
|
||||
}
|
||||
}
|
||||
this.extend = n.extend || "false";
|
||||
this.overrideDelay = n.overrideDelay || false;
|
||||
this.units = n.units || "ms";
|
||||
this.reset = n.reset || '';
|
||||
this.duration = parseFloat(n.duration);
|
||||
if (isNaN(this.duration)) {
|
||||
this.duration = 250;
|
||||
}
|
||||
if (this.duration < 0) {
|
||||
this.loop = true;
|
||||
this.duration = this.duration * -1;
|
||||
this.extend = false;
|
||||
}
|
||||
|
||||
if (this.units == "s") { this.duration = this.duration * 1000; }
|
||||
if (this.units == "min") { this.duration = this.duration * 1000 * 60; }
|
||||
if (this.units == "hr") { this.duration = this.duration * 1000 *60 * 60; }
|
||||
|
||||
this.op1Templated = (this.op1type === 'str' && this.op1.indexOf("{{") != -1);
|
||||
this.op2Templated = (this.op2type === 'str' && this.op2.indexOf("{{") != -1);
|
||||
if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); }
|
||||
if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); }
|
||||
//if (this.op1 == "null") { this.op1 = null; }
|
||||
//if (this.op2 == "null") { this.op2 = null; }
|
||||
//try { this.op1 = JSON.parse(this.op1); }
|
||||
//catch(e) { this.op1 = this.op1; }
|
||||
//try { this.op2 = JSON.parse(this.op2); }
|
||||
//catch(e) { this.op2 = this.op2; }
|
||||
|
||||
var node = this;
|
||||
node.topics = {};
|
||||
|
||||
var npay = {};
|
||||
var pendingMessages = [];
|
||||
var activeMessagePromise = null;
|
||||
var processMessageQueue = function(msgInfo) {
|
||||
if (msgInfo) {
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msgInfo);
|
||||
if (activeMessagePromise !== null) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
activeMessagePromise = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsgInfo = pendingMessages.shift();
|
||||
activeMessagePromise = processMessage(nextMsgInfo)
|
||||
.then(processMessageQueue)
|
||||
.catch((err) => {
|
||||
nextMsgInfo.done(err);
|
||||
return processMessageQueue();
|
||||
});
|
||||
}
|
||||
|
||||
this.on('input', function(msg, send, done) {
|
||||
processMessageQueue({msg, send, done});
|
||||
});
|
||||
|
||||
var stat = function() {
|
||||
var l = Object.keys(node.topics).length;
|
||||
if (l === 0) { return {} }
|
||||
else if (l === 1) { return {fill:"blue",shape:"dot"} }
|
||||
else return {fill:"blue",shape:"dot",text:l};
|
||||
}
|
||||
|
||||
var processMessage = function(msgInfo) {
|
||||
let msg = msgInfo.msg;
|
||||
var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
|
||||
var promise;
|
||||
var delayDuration = node.duration;
|
||||
if (node.overrideDelay && msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
|
||||
delayDuration = parseFloat(msg.delay);
|
||||
}
|
||||
if (node.bytopic === "all") { topic = "_none"; }
|
||||
node.topics[topic] = node.topics[topic] || {};
|
||||
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
|
||||
if (node.loop === true) { clearInterval(node.topics[topic].tout); }
|
||||
else { clearTimeout(node.topics[topic].tout); }
|
||||
delete node.topics[topic];
|
||||
node.status(stat());
|
||||
}
|
||||
else {
|
||||
if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
|
||||
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
|
||||
promise = Promise.resolve();
|
||||
if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
|
||||
else if (node.op2type !== "nul") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
promise = Promise.resolve();
|
||||
if (node.op1type === "pay") { }
|
||||
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
||||
else if (node.op1type !== "nul") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
msg.payload = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return promise.then(() => {
|
||||
if (delayDuration === 0) { node.topics[topic].tout = 0; }
|
||||
else if (node.loop === true) {
|
||||
/* istanbul ignore else */
|
||||
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
|
||||
/* istanbul ignore else */
|
||||
if (node.op1type !== "nul") {
|
||||
var msg2 = RED.util.cloneMessage(msg);
|
||||
node.topics[topic].tout = setInterval(function() {
|
||||
if (node.op1type === "date") { msg2.payload = Date.now(); }
|
||||
msgInfo.send(RED.util.cloneMessage(msg2));
|
||||
}, delayDuration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!node.topics[topic].tout) {
|
||||
node.topics[topic].tout = setTimeout(function() {
|
||||
var msg2 = null;
|
||||
if (node.op2type !== "nul") {
|
||||
var promise = Promise.resolve();
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
if (node.op2type === "flow" || node.op2type === "global") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
promise.then(() => {
|
||||
if (node.op2type === "payl") {
|
||||
if (node.second === true) { msgInfo.send([null,npay[topic]]); }
|
||||
else { msgInfo.send(npay[topic]); }
|
||||
delete npay[topic];
|
||||
}
|
||||
else {
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
if (node.op2type === "date") { msg2.payload = Date.now(); }
|
||||
if (node.second === true) { msgInfo.send([null,msg2]); }
|
||||
else { msgInfo.send(msg2); }
|
||||
}
|
||||
delete node.topics[topic];
|
||||
node.status(stat());
|
||||
}).catch(err => {
|
||||
node.error(err);
|
||||
});
|
||||
} else {
|
||||
delete node.topics[topic];
|
||||
node.status(stat());
|
||||
}
|
||||
|
||||
}, delayDuration);
|
||||
}
|
||||
}
|
||||
msgInfo.done();
|
||||
node.status(stat());
|
||||
if (node.op1type !== "nul") { msgInfo.send(RED.util.cloneMessage(msg)); }
|
||||
});
|
||||
});
|
||||
}
|
||||
else if ((node.extend === "true" || node.extend === true) && (delayDuration > 0)) {
|
||||
/* istanbul ignore else */
|
||||
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||
/* istanbul ignore else */
|
||||
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
|
||||
node.topics[topic].tout = setTimeout(function() {
|
||||
var msg2 = null;
|
||||
var promise = Promise.resolve();
|
||||
|
||||
if (node.op2type !== "nul") {
|
||||
if (node.op2type === "flow" || node.op2type === "global") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
promise.then(() => {
|
||||
if (node.op2type !== "nul") {
|
||||
if (node.topics[topic] !== undefined) {
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
}
|
||||
}
|
||||
delete node.topics[topic];
|
||||
node.status(stat());
|
||||
if (node.second === true) { msgInfo.send([null,msg2]); }
|
||||
else { msgInfo.send(msg2); }
|
||||
}).catch(err => {
|
||||
node.error(err);
|
||||
});
|
||||
}, delayDuration);
|
||||
}
|
||||
// else {
|
||||
// if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||
// }
|
||||
}
|
||||
msgInfo.done();
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.on("close", function() {
|
||||
for (var t in node.topics) {
|
||||
/* istanbul ignore else */
|
||||
if (node.topics[t]) {
|
||||
if (node.loop === true) { clearInterval(node.topics[t].tout); }
|
||||
else { clearTimeout(node.topics[t].tout); }
|
||||
delete node.topics[t];
|
||||
}
|
||||
}
|
||||
node.status(stat());
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("trigger",TriggerNode);
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="exec">
|
||||
<div class="form-row">
|
||||
<label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
|
||||
<input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-plus"></i> <span data-i18n="exec.label.append"></span></label>
|
||||
<input type="checkbox" id="node-input-addpay-cb" style="display:inline-block; width:auto;">
|
||||
<input type="text" id="node-input-addpay" style="margin-left: 5px; width:160px;">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-append"> </label>
|
||||
<input type="text" id="node-input-append" data-i18n="[placeholder]exec.placeholder.extraparams">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="exec.label.return"></span></label>
|
||||
<select type="text" id="node-input-useSpawn" style="width:70%">
|
||||
<option value="false" data-i18n="exec.opt.exec"></option>
|
||||
<option value="true" data-i18n="exec.opt.spawn"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-timer"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
|
||||
<input type="text" id="node-input-timer" style="width:65px;" data-i18n="[placeholder]exec.label.timeoutplace">
|
||||
<span data-i18n="exec.label.seconds"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-winHide" style="width: auto !important; padding-right:10px"><i class="fa fa-windows"></i> <span data-i18n="exec.label.winHide"></span></label>
|
||||
<input type="checkbox" id="node-input-winHide" style="margin-top: 0; display:inline-block; width:auto;">
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('exec',{
|
||||
category: 'function',
|
||||
color:"darksalmon",
|
||||
defaults: {
|
||||
command: {value:""},
|
||||
addpay: {value:""},
|
||||
append: {value:""},
|
||||
useSpawn: {value:"false"},
|
||||
timer: {value:""},
|
||||
winHide: {value:false},
|
||||
oldrc: {value:false},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:3,
|
||||
outputLabels: function(i) {
|
||||
return [
|
||||
this._("exec.label.stdout"),
|
||||
this._("exec.label.stderr"),
|
||||
this._("exec.label.retcode")
|
||||
][i];
|
||||
},
|
||||
icon: "cog.svg",
|
||||
label: function() {
|
||||
return this.name||this.command.replace(/\\n /g,"\\\\n ")||(this.useSpawn=="true"?this._("exec.spawn"):this._("exec.exec"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if ($("#node-input-useSpawn").val() === null) {
|
||||
$("#node-input-useSpawn").val(this.useSpawn.toString());
|
||||
}
|
||||
$("#node-input-addpay-cb").prop("checked", this.addpay === true || (this.addpay !== false && this.addpay !== ""))
|
||||
var addpayValue = (this.addpay === true)?"payload":((this.addpay === false || this.addpay === "")?"payload":this.addpay);
|
||||
$("#node-input-addpay-cb").on("change", function(evt) {
|
||||
$("#node-input-addpay").typedInput("disable",!$("#node-input-addpay-cb").prop("checked"));
|
||||
});
|
||||
|
||||
$("#node-input-addpay").val(addpayValue);
|
||||
$("#node-input-addpay").typedInput({
|
||||
default: "msg",
|
||||
types: ["msg"]
|
||||
});
|
||||
|
||||
$("#node-input-addpay-cb").trigger("change")
|
||||
|
||||
if (this.winHide === "true" || this.winHide === true) {
|
||||
$("#node-input-winHide").prop("checked",true);
|
||||
} else {
|
||||
$("#node-input-winHide").prop("checked",false);
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-addpay-cb").prop("checked")) {
|
||||
$("#node-input-addpay").val("");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,200 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var spawn = require('child_process').spawn;
|
||||
var exec = require('child_process').exec;
|
||||
var fs = require('fs');
|
||||
var isUtf8 = require('is-utf8');
|
||||
|
||||
function ExecNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.cmd = (n.command || "").trim();
|
||||
if (n.addpay === undefined) { n.addpay = true; }
|
||||
this.addpay = n.addpay;
|
||||
if (this.addpay === true) {
|
||||
this.addpay = "payload";
|
||||
}
|
||||
this.append = (n.append || "").trim();
|
||||
this.useSpawn = (n.useSpawn == "true");
|
||||
this.timer = Number(n.timer || 0)*1000;
|
||||
this.activeProcesses = {};
|
||||
this.oldrc = (n.oldrc || false).toString();
|
||||
this.execOpt = {encoding:'binary', maxBuffer:RED.settings.execMaxBufferSize||10000000, windowsHide: (n.winHide === true)};
|
||||
this.spawnOpt = {windowsHide: (n.winHide === true) }
|
||||
var node = this;
|
||||
|
||||
if (process.platform === 'linux' && fs.existsSync('/bin/bash')) { node.execOpt.shell = '/bin/bash'; }
|
||||
|
||||
var cleanup = function(p) {
|
||||
node.activeProcesses[p].kill();
|
||||
//node.status({fill:"red",shape:"dot",text:"timeout"});
|
||||
//node.error("Exec node timeout");
|
||||
}
|
||||
|
||||
this.on("input", function(msg, nodeSend, nodeDone) {
|
||||
if (msg.hasOwnProperty("kill")) {
|
||||
if (typeof msg.kill !== "string" || msg.kill.length === 0 || !msg.kill.toUpperCase().startsWith("SIG") ) { msg.kill = "SIGTERM"; }
|
||||
if (msg.hasOwnProperty("pid")) {
|
||||
if (node.activeProcesses.hasOwnProperty(msg.pid) ) {
|
||||
node.activeProcesses[msg.pid].kill(msg.kill.toUpperCase());
|
||||
node.status({fill:"red",shape:"dot",text:"killed"});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Object.keys(node.activeProcesses).length === 1) {
|
||||
node.activeProcesses[Object.keys(node.activeProcesses)[0]].kill(msg.kill.toUpperCase());
|
||||
node.status({fill:"red",shape:"dot",text:"killed"});
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
}
|
||||
else {
|
||||
var child;
|
||||
// make the extra args into an array
|
||||
// then prepend with the msg.payload
|
||||
var arg = node.cmd;
|
||||
if (node.addpay) {
|
||||
var value = RED.util.getMessageProperty(msg, node.addpay);
|
||||
if (value !== undefined) {
|
||||
arg += " " + value;
|
||||
}
|
||||
}
|
||||
if (node.append.trim() !== "") { arg += " " + node.append; }
|
||||
if (this.useSpawn === true) {
|
||||
// slice whole line by spaces and removes any quotes since spawn can't handle them
|
||||
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g).map((a) => {
|
||||
if (/^".*"$/.test(a)) {
|
||||
return a.slice(1,-1)
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
});
|
||||
var cmd = arg.shift();
|
||||
/* istanbul ignore else */
|
||||
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
||||
child = spawn(cmd,arg,node.spawnOpt);
|
||||
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
|
||||
var unknownCommand = (child.pid === undefined);
|
||||
if (node.timer !== 0) {
|
||||
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
||||
}
|
||||
node.activeProcesses[child.pid] = child;
|
||||
child.stdout.on('data', function (data) {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
// console.log('[exec] stdout: ' + data,child.pid);
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
nodeSend([RED.util.cloneMessage(msg),null,null]);
|
||||
}
|
||||
});
|
||||
child.stderr.on('data', function (data) {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = Buffer.from(data); }
|
||||
nodeSend([null,RED.util.cloneMessage(msg),null]);
|
||||
}
|
||||
});
|
||||
child.on('close', function (code,signal) {
|
||||
if (unknownCommand || (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null)) {
|
||||
delete node.activeProcesses[child.pid];
|
||||
if (child.tout) { clearTimeout(child.tout); }
|
||||
msg.payload = code;
|
||||
if (node.oldrc === "false") {
|
||||
msg.payload = {code:code};
|
||||
if (signal) { msg.payload.signal = signal; }
|
||||
}
|
||||
if (code === 0) { node.status({}); }
|
||||
if (code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
|
||||
else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc:"+code}); }
|
||||
else { node.status({fill:"yellow",shape:"dot",text:"rc:"+code}); }
|
||||
nodeSend([null,null,RED.util.cloneMessage(msg)]);
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
child.on('error', function (code) {
|
||||
if (child.tout) { clearTimeout(child.tout); }
|
||||
delete node.activeProcesses[child.pid];
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
node.error(code,RED.util.cloneMessage(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
/* istanbul ignore else */
|
||||
if (RED.settings.verbose) { node.log(arg); }
|
||||
child = exec(arg, node.execOpt, function (error, stdout, stderr) {
|
||||
var msg2, msg3;
|
||||
delete msg.payload;
|
||||
if (stderr) {
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
msg2.payload = stderr;
|
||||
}
|
||||
msg.payload = Buffer.from(stdout,"binary");
|
||||
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
||||
node.status({});
|
||||
//console.log('[exec] stdout: ' + stdout);
|
||||
//console.log('[exec] stderr: ' + stderr);
|
||||
if (error !== null) {
|
||||
msg3 = RED.util.cloneMessage(msg);
|
||||
msg3.payload = {code:error.code, message:error.message};
|
||||
if (error.signal) { msg3.payload.signal = error.signal; }
|
||||
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
|
||||
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
|
||||
if (RED.settings.verbose) { node.log('error:' + error); }
|
||||
}
|
||||
else if (node.oldrc === "false") {
|
||||
msg3 = RED.util.cloneMessage(msg);
|
||||
msg3.payload = {code:0};
|
||||
}
|
||||
if (!msg3) { node.status({}); }
|
||||
else {
|
||||
msg.rc = msg3.payload;
|
||||
if (msg2) { msg2.rc = msg3.payload; }
|
||||
}
|
||||
nodeSend([msg,msg2,msg3]);
|
||||
if (child.tout) { clearTimeout(child.tout); }
|
||||
delete node.activeProcesses[child.pid];
|
||||
nodeDone();
|
||||
});
|
||||
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
|
||||
child.on('error',function() {});
|
||||
if (node.timer !== 0) {
|
||||
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
||||
}
|
||||
node.activeProcesses[child.pid] = child;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.on('close',function() {
|
||||
for (var pid in node.activeProcesses) {
|
||||
/* istanbul ignore else */
|
||||
if (node.activeProcesses.hasOwnProperty(pid)) {
|
||||
if (node.activeProcesses[pid].tout) { clearTimeout(node.activeProcesses[pid].tout); }
|
||||
// console.log("KILLING",pid);
|
||||
var process = node.activeProcesses[pid];
|
||||
node.activeProcesses[pid] = null;
|
||||
process.kill();
|
||||
}
|
||||
}
|
||||
node.activeProcesses = {};
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("exec",ExecNode);
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="rbe">
|
||||
<div class="form-row">
|
||||
<label for="node-input-func"><i class="fa fa-wrench"></i> <span data-i18n="rbe.label.func"></span></label>
|
||||
<select type="text" id="node-input-func" style="width:70%;">
|
||||
<option value="rbe" data-i18n="rbe.opts.rbe"></option>
|
||||
<option value="rbei" data-i18n="rbe.opts.rbei"></option>
|
||||
<option value="deadbandEq" data-i18n="rbe.opts.deadbandEq"></option>
|
||||
<option value="deadband" data-i18n="rbe.opts.deadband"></option>
|
||||
<option value="narrowbandEq" data-i18n="rbe.opts.narrowbandEq"></option>
|
||||
<option value="narrowband" data-i18n="rbe.opts.narrowband"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-bandgap">
|
||||
<label for="node-input-gap"> </label>
|
||||
<input type="text" id="node-input-gap" data-i18n="[placeholder]rbe.placeholder.bandgap" style="width:95px;">
|
||||
<select type="text" id="node-input-inout" style="width:54%;">
|
||||
<option value="out" data-i18n="rbe.opts.out"></option>
|
||||
<option value="in" data-i18n="rbe.opts.in"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-startvalue">
|
||||
<label for="node-input-start"><i class="fa fa-thumb-tack"></i> <span data-i18n="rbe.label.start"></span></label>
|
||||
<input type="text" id="node-input-start" data-i18n="[placeholder]rbe.placeholder.start" style="width:70%;">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:70%;"/>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-septopics" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<label style="width: auto" for="node-input-septopics" data-i18n="rbe.label.septopics"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="text" id="node-input-topi" style="width:70%;"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rbe.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]rbe.label.name" style="width:70%;">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType("rbe", {
|
||||
color:"#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"rbe"},
|
||||
gap: {value:"",validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)},
|
||||
start: {value:""},
|
||||
inout: {value:"out"},
|
||||
septopics: {value:true},
|
||||
property: {value:"payload",required:true},
|
||||
topi: {value:"topic",required:true}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "rbe.png",
|
||||
paletteLabel: "filter",
|
||||
label: function() {
|
||||
var ll = (this.func||"").replace("Eq","").replace("rbei",this._("rbe.rbe")).replace("rbe",this._("rbe.rbe"))||this._("rbe.rbe");
|
||||
return this.name||ll||this._("rbe.rbe");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.property === undefined) {
|
||||
$("#node-input-property").val("payload");
|
||||
}
|
||||
if (this.septopics === undefined) {
|
||||
$("#node-input-septopics").prop('checked', true);
|
||||
}
|
||||
if (this.topi === undefined) {
|
||||
$("#node-input-topi").val("topic");
|
||||
}
|
||||
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
$("#node-input-topi").typedInput({default:'msg',types:['msg']});
|
||||
//$( "#node-input-gap" ).spinner({min:0});
|
||||
if ($("#node-input-inout").val() === null) {
|
||||
$("#node-input-inout").val("out");
|
||||
}
|
||||
$("#node-input-func").on("change",function() {
|
||||
if (($("#node-input-func").val() === "rbe")||($("#node-input-func").val() === "rbei")) {
|
||||
$("#node-bandgap").hide();
|
||||
} else {
|
||||
$("#node-bandgap").show();
|
||||
}
|
||||
if (($("#node-input-func").val() === "narrowband")||($("#node-input-func").val() === "narrowbandEq")) {
|
||||
$("#node-startvalue").show();
|
||||
} else {
|
||||
$("#node-startvalue").hide();
|
||||
}
|
||||
});
|
||||
$("#node-input-septopics").on("change", function() {
|
||||
$("#node-input-topi").typedInput("disable",!this.checked);
|
||||
})
|
||||
$("#node-input-topi").typedInput("disable",!!!this.septopics);
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,97 +0,0 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function RbeNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.func = n.func || "rbe";
|
||||
this.gap = n.gap || "0";
|
||||
this.start = n.start || '';
|
||||
this.inout = n.inout || "out";
|
||||
this.pc = false;
|
||||
if (this.gap.substr(-1) === "%") {
|
||||
this.pc = true;
|
||||
this.gap = parseFloat(this.gap);
|
||||
}
|
||||
this.g = this.gap;
|
||||
this.property = n.property || "payload";
|
||||
this.topi = n.topi || "topic";
|
||||
this.septopics = true;
|
||||
if (n.septopics !== undefined && n.septopics === false) {
|
||||
this.septopics = false;
|
||||
}
|
||||
|
||||
var node = this;
|
||||
|
||||
node.previous = {};
|
||||
this.on("input",function(msg) {
|
||||
var topic;
|
||||
try {
|
||||
topic = RED.util.getMessageProperty(msg,node.topi);
|
||||
}
|
||||
catch(e) { }
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (node.septopics && topic && (typeof topic === "string") && (topic !== "")) {
|
||||
delete node.previous[msg.topic];
|
||||
}
|
||||
else { node.previous = {}; }
|
||||
}
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
var t = "_no_topic";
|
||||
if (node.septopics) { t = topic || t; }
|
||||
if ((this.func === "rbe") || (this.func === "rbei")) {
|
||||
var doSend = (this.func !== "rbei") || (node.previous.hasOwnProperty(t)) || false;
|
||||
if (typeof(value) === "object") {
|
||||
if (typeof(node.previous[t]) !== "object") { node.previous[t] = {}; }
|
||||
if (!RED.util.compareObjects(value, node.previous[t])) {
|
||||
node.previous[t] = RED.util.cloneMessage(value);
|
||||
if (doSend) { node.send(msg); }
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (value !== node.previous[t]) {
|
||||
node.previous[t] = RED.util.cloneMessage(value);
|
||||
if (doSend) { node.send(msg); }
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var n = parseFloat(value);
|
||||
if (!isNaN(n)) {
|
||||
if ((typeof node.previous[t] === 'undefined') && (this.func === "narrowband")) {
|
||||
if (node.start === '') { node.previous[t] = n; }
|
||||
else { node.previous[t] = node.start; }
|
||||
}
|
||||
if (node.pc) { node.gap = Math.abs(node.previous[t] * node.g / 100) || 0; }
|
||||
else { node.gap = Number(node.gap); }
|
||||
if ((node.previous[t] === undefined) && (node.func === "narrowbandEq")) { node.previous[t] = n; }
|
||||
if (node.previous[t] === undefined) { node.previous[t] = n - node.gap - 1; }
|
||||
if (Math.abs(n - node.previous[t]) === node.gap) {
|
||||
if ((this.func === "deadbandEq")||(this.func === "narrowband")) {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else if (Math.abs(n - node.previous[t]) > node.gap) {
|
||||
if (this.func === "deadband" || this.func === "deadbandEq") {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else if (Math.abs(n - node.previous[t]) < node.gap) {
|
||||
if ((this.func === "narrowband")||(this.func === "narrowbandEq")) {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
if (node.inout === "in") { node.previous[t] = n; }
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rbe.warn.nonumber"));
|
||||
}
|
||||
}
|
||||
} // ignore msg with no payload property.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rbe",RbeNode);
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="tls-config">
|
||||
<div class="form-row" class="hide" id="node-config-row-uselocalfiles">
|
||||
<input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
|
||||
<span class="tls-config-input-data">
|
||||
<label class="red-ui-button" for="node-config-input-certfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
|
||||
<input class="hide" type="file" id="node-config-input-certfile">
|
||||
<span id="tls-config-certname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
|
||||
<button class="red-ui-button red-ui-button-small" id="tls-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
|
||||
</span>
|
||||
<input type="hidden" id="node-config-input-certname">
|
||||
<input type="hidden" id="node-config-input-certdata">
|
||||
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
|
||||
<span class="tls-config-input-data">
|
||||
<label class="red-ui-button" for="node-config-input-keyfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
|
||||
<input class="hide" type="file" id="node-config-input-keyfile">
|
||||
<span id="tls-config-keyname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
|
||||
<button class="red-ui-button red-ui-button-small" id="tls-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
|
||||
</span>
|
||||
<input type="hidden" id="node-config-input-keyname">
|
||||
<input type="hidden" id="node-config-input-keydata">
|
||||
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 100px; margin-left: 20px;" for="node-config-input-passphrase"> <span data-i18n="tls.label.passphrase"></span></label>
|
||||
<input type="password" style="width: calc(100% - 170px);" id="node-config-input-passphrase" data-i18n="[placeholder]tls.placeholder.passphrase">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;" for="node-config-input-ca"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
|
||||
<span class="tls-config-input-data">
|
||||
<label class="red-ui-button" for="node-config-input-cafile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
|
||||
<input class="hide" type="file" title=" " id="node-config-input-cafile">
|
||||
<span id="tls-config-caname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
|
||||
<button class="red-ui-button red-ui-button-small" id="tls-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
|
||||
</span>
|
||||
<input type="hidden" id="node-config-input-caname">
|
||||
<input type="hidden" id="node-config-input-cadata">
|
||||
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 126px;" for="node-config-input-servername"><i class="fa fa-server"></i> <span data-i18n="tls.label.servername"></span></label>
|
||||
<input style="width: calc(100% - 176px);" type="text" id="node-config-input-servername" data-i18n="[placeholder]tls.placeholder.servername">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 126px;" for="node-config-input-alpnprotocol"><i class="fa fa-cogs"></i> <span data-i18n="tls.label.alpnprotocol"></span></label>
|
||||
<input style="width: calc(100% - 176px);" type="text" id="node-config-input-alpnprotocol" data-i18n="[placeholder]tls.placeholder.alpnprotocol">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tls-config',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
cert: {value:"", validate: function(v) {
|
||||
var currentKey = $("#node-config-input-key").val();
|
||||
if (currentKey === undefined) {
|
||||
currentKey = this.key;
|
||||
}
|
||||
return currentKey === '' || v != '';
|
||||
}},
|
||||
key: {value:"", validate: function(v) {
|
||||
var currentCert = $("#node-config-input-cert").val();
|
||||
if (currentCert === undefined) {
|
||||
currentCert = this.cert;
|
||||
}
|
||||
return currentCert === '' || v != '';
|
||||
}},
|
||||
ca: {value:""},
|
||||
certname: {value:""},
|
||||
keyname: {value:""},
|
||||
caname: {value:""},
|
||||
servername: {value:""},
|
||||
verifyservercert: {value: true},
|
||||
alpnprotocol: {value: ""}
|
||||
},
|
||||
credentials: {
|
||||
certdata: {type:"text"},
|
||||
keydata: {type:"text"},
|
||||
cadata: {type:"text"},
|
||||
passphrase: {type:"password"}
|
||||
},
|
||||
label: function() {
|
||||
return this.name || this._("tls.tls");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
function updateFileUpload() {
|
||||
if ($("#node-config-input-uselocalfiles").is(':checked')) {
|
||||
$(".tls-config-input-path").show();
|
||||
$(".tls-config-input-data").hide();
|
||||
} else {
|
||||
$(".tls-config-input-data").show();
|
||||
$(".tls-config-input-path").hide();
|
||||
}
|
||||
}
|
||||
$("#node-config-input-uselocalfiles").on("click",function() {
|
||||
updateFileUpload();
|
||||
});
|
||||
|
||||
function saveFile(property, file) {
|
||||
var dataInputId = "#node-config-input-"+property+"data";
|
||||
var filenameInputId = "#node-config-input-"+property+"name";
|
||||
var filename = file.name;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
$("#tls-config-"+property+"name").text(filename);
|
||||
$(filenameInputId).val(filename);
|
||||
$(dataInputId).val(event.target.result);
|
||||
}
|
||||
reader.readAsText(file,"UTF-8");
|
||||
}
|
||||
$("#node-config-input-certfile" ).on("change", function() {
|
||||
saveFile("cert", this.files[0]);
|
||||
});
|
||||
$("#node-config-input-keyfile" ).on("change", function() {
|
||||
saveFile("key", this.files[0]);
|
||||
});
|
||||
$("#node-config-input-cafile" ).on("change", function() {
|
||||
saveFile("ca", this.files[0]);
|
||||
});
|
||||
|
||||
function clearNameData(prop) {
|
||||
$("#tls-config-"+prop+"name").text("");
|
||||
$("#node-config-input-"+prop+"data").val("");
|
||||
$("#node-config-input-"+prop+"name").val("");
|
||||
}
|
||||
$("#tls-config-button-cert-clear").on("click", function() {
|
||||
clearNameData("cert");
|
||||
});
|
||||
$("#tls-config-button-key-clear").on("click", function() {
|
||||
clearNameData("key");
|
||||
});
|
||||
$("#tls-config-button-ca-clear").on("click", function() {
|
||||
clearNameData("ca");
|
||||
});
|
||||
|
||||
if (RED.settings.tlsConfigDisableLocalFiles) {
|
||||
$("#node-config-row-uselocalfiles").hide();
|
||||
} else {
|
||||
$("#node-config-row-uselocalfiles").show();
|
||||
}
|
||||
// in case paths were set from old TLS config
|
||||
if(this.cert || this.key || this.ca) {
|
||||
$("#node-config-input-uselocalfiles").prop('checked',true);
|
||||
}
|
||||
$("#tls-config-certname").text(this.certname);
|
||||
$("#tls-config-keyname").text(this.keyname);
|
||||
$("#tls-config-caname").text(this.caname);
|
||||
updateFileUpload();
|
||||
},
|
||||
oneditsave: function() {
|
||||
if ($("#node-config-input-uselocalfiles").is(':checked')) {
|
||||
clearNameData("ca");
|
||||
clearNameData("cert");
|
||||
clearNameData("key");
|
||||
} else {
|
||||
$("#node-config-input-ca").val("");
|
||||
$("#node-config-input-cert").val("");
|
||||
$("#node-config-input-key").val("");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var fs = require('fs');
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function TLSConfig(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.valid = true;
|
||||
this.verifyservercert = n.verifyservercert;
|
||||
var certPath = n.cert.trim();
|
||||
var keyPath = n.key.trim();
|
||||
var caPath = n.ca.trim();
|
||||
this.servername = (n.servername||"").trim();
|
||||
this.alpnprotocol = (n.alpnprotocol||"").trim();
|
||||
|
||||
if ((certPath.length > 0) || (keyPath.length > 0) || (caPath.length > 0)) {
|
||||
|
||||
if ( (certPath.length > 0) !== (keyPath.length > 0)) {
|
||||
this.valid = false;
|
||||
this.error(RED._("tls.error.missing-file"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (certPath) {
|
||||
this.cert = fs.readFileSync(certPath);
|
||||
}
|
||||
if (keyPath) {
|
||||
this.key = fs.readFileSync(keyPath);
|
||||
}
|
||||
if (caPath) {
|
||||
this.ca = fs.readFileSync(caPath);
|
||||
}
|
||||
} catch(err) {
|
||||
this.valid = false;
|
||||
this.error(err.toString());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (this.credentials) {
|
||||
var certData = this.credentials.certdata || "";
|
||||
var keyData = this.credentials.keydata || "";
|
||||
var caData = this.credentials.cadata || "";
|
||||
|
||||
if ((certData.length > 0) !== (keyData.length > 0)) {
|
||||
this.valid = false;
|
||||
this.error(RED._("tls.error.missing-file"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (certData) {
|
||||
this.cert = certData;
|
||||
}
|
||||
if (keyData) {
|
||||
this.key = keyData;
|
||||
}
|
||||
if (caData) {
|
||||
this.ca = caData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("tls-config", TLSConfig, {
|
||||
credentials: {
|
||||
certdata: {type:"text"},
|
||||
keydata: {type:"text"},
|
||||
cadata: {type:"text"},
|
||||
passphrase: {type:"password"}
|
||||
},
|
||||
settings: {
|
||||
tlsConfigDisableLocalFiles: {
|
||||
value: false,
|
||||
exportable: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TLSConfig.prototype.addTLSOptions = function(opts) {
|
||||
if (this.valid) {
|
||||
if (this.key) {
|
||||
opts.key = this.key;
|
||||
}
|
||||
if (this.cert) {
|
||||
opts.cert = this.cert;
|
||||
}
|
||||
if (this.ca) {
|
||||
opts.ca = this.ca;
|
||||
}
|
||||
if (this.credentials && this.credentials.passphrase) {
|
||||
opts.passphrase = this.credentials.passphrase;
|
||||
}
|
||||
if (this.servername) {
|
||||
opts.servername = this.servername;
|
||||
}
|
||||
if (this.alpnprotocol) {
|
||||
opts.ALPNProtocols = [this.alpnprotocol];
|
||||
}
|
||||
opts.rejectUnauthorized = this.verifyservercert;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="http proxy">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-config-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
|
||||
<input type="text" id="node-config-input-url" placeholder="http://hostname:port">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-config-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-config-input-useAuth" style="width: 70%;"><span data-i18n="httpin.use-proxyauth"></span></label>
|
||||
<div style="margin-left: 20px" class="node-config-input-useAuth-row hide">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
|
||||
<input type="text" id="node-config-input-username">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="httpin.noproxy-hosts"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-config-input-noproxy-container-row">
|
||||
<ol id="node-config-input-noproxy-container"></ol>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('http proxy', {
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: {value:''},
|
||||
url: {value:'',validate:function(v) { return (v && (v.indexOf('://') !== -1) && (v.trim().indexOf('http') === 0)); }},
|
||||
noproxy: {value:[]}
|
||||
},
|
||||
credentials: {
|
||||
username: {type:'text'},
|
||||
password: {type:'password'}
|
||||
},
|
||||
label: function() {
|
||||
return this.name || this.url || ('http proxy:' + this.id);
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$('#node-config-input-useAuth').on("change", function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$('.node-config-input-useAuth-row').show();
|
||||
} else {
|
||||
$('.node-config-input-useAuth-row').hide();
|
||||
$('#node-config-input-username').val('');
|
||||
$('#node-config-input-password').val('');
|
||||
}
|
||||
});
|
||||
if (this.credentials.username || this.credentials.has_password) {
|
||||
$('#node-config-input-useAuth').prop('checked', true);
|
||||
} else {
|
||||
$('#node-config-input-useAuth').prop('checked', false);
|
||||
}
|
||||
$('#node-config-input-useAuth').change();
|
||||
|
||||
var hostList = $('#node-config-input-noproxy-container')
|
||||
.css({'min-height':'150px','min-width':'450px'})
|
||||
.editableList({
|
||||
addItem: function(container, index, data) {
|
||||
var row = $('<div/>')
|
||||
.css({overflow: 'hidden',whiteSpace: 'nowrap'})
|
||||
.appendTo(container);
|
||||
|
||||
var hostField = $('<input/>',{class:'node-config-input-host',type:'text',placeholder:'hostname'})
|
||||
.css({width:'100%'})
|
||||
.appendTo(row);
|
||||
if (data.host) {
|
||||
hostField.val(data.host);
|
||||
}
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
if (this.noproxy) {
|
||||
for (var i in this.noproxy) {
|
||||
hostList.editableList('addItem', {host:this.noproxy[i]});
|
||||
}
|
||||
}
|
||||
if (hostList.editableList('items').length == 0) {
|
||||
hostList.editableList('addItem', {host:''});
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var hosts = $('#node-config-input-noproxy-container').editableList('items');
|
||||
var node = this;
|
||||
node.noproxy = [];
|
||||
hosts.each(function(i) {
|
||||
var host = $(this).find('.node-config-input-host').val().trim();
|
||||
if (host) {
|
||||
node.noproxy.push(host);
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $('#node-config-dialog-edit-form>div:not(.node-config-input-noproxy-container-row)');
|
||||
var height = size.height;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
|
||||
var editorRow = $('#node-config-dialog-edit-form>div.node-config-input-noproxy-container-row');
|
||||
height -= (parseInt(editorRow.css('margin-top')) + parseInt(editorRow.css('margin-bottom')));
|
||||
$('#node-config-input-noproxy-container').editableList('height',height);
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
'use strict';
|
||||
|
||||
function HTTPProxyConfig(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.name = n.name;
|
||||
this.url = n.url;
|
||||
this.noproxy = n.noproxy;
|
||||
};
|
||||
|
||||
RED.nodes.registerType('http proxy', HTTPProxyConfig, {
|
||||
credentials: {
|
||||
username: {type:'text'},
|
||||
password: {type:'password'}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,908 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<style>
|
||||
|
||||
.mqtt-form-row-cols2 > input.mqtt-form-row-col1 {
|
||||
width: calc(35% - 75px);
|
||||
}
|
||||
.mqtt-form-row-cols2 > select.mqtt-form-row-col1 {
|
||||
width: calc(35% - 75px);
|
||||
}
|
||||
|
||||
.mqtt-form-row-cols2 > label.mqtt-form-row-col2 {
|
||||
width: 100px;
|
||||
margin-left: 42px;
|
||||
display: inline-block;
|
||||
}
|
||||
.mqtt-form-row-cols2 > input.mqtt-form-row-col2 {
|
||||
width: calc(35% - 75px);
|
||||
display: inline-block;
|
||||
}
|
||||
.mqtt-form-row-cols2 > select.mqtt-form-row-col2 {
|
||||
width: calc(35% - 75px);
|
||||
display: inline-block;
|
||||
}
|
||||
.form-row.mqtt5-out > label {
|
||||
width: 130px;
|
||||
}
|
||||
.form-row.mqtt-flags-row > label {
|
||||
vertical-align: top;
|
||||
}
|
||||
.form-row.mqtt-flags-row > .mqtt-flags {
|
||||
display: inline-block;
|
||||
width: 70%
|
||||
}
|
||||
|
||||
.form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label > input {
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
top: -2px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.form-row-mqtt5 {
|
||||
display: none;
|
||||
}
|
||||
.form-row-mqtt5.form-row-mqtt5-active:not(.form-row-mqtt-static-disabled) {
|
||||
display: block
|
||||
}
|
||||
.form-row-mqtt-static-disabled {
|
||||
display: none;
|
||||
/* opacity: 0.3;
|
||||
pointer-events: none; */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/html" data-template-name="mqtt in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
|
||||
<input type="text" id="node-input-broker">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topicType" data-i18n="mqtt.label.action"></label>
|
||||
<select id="node-input-topicType" style="width: 70%">
|
||||
<option value="topic" data-i18n="mqtt.label.staticTopic"></option>
|
||||
<option value="dynamic" data-i18n="mqtt.label.dynamicTopic"></option>
|
||||
</select>
|
||||
<input type="hidden" id="node-input-inputs">
|
||||
</div>
|
||||
<div class="form-row form-row-mqtt-static">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
|
||||
</div>
|
||||
<div class="form-row form-row-mqtt-static">
|
||||
<label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
|
||||
<select id="node-input-qos" style="width:125px !important">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row mqtt-flags-row form-row-mqtt5 form-row-mqtt-static">
|
||||
<label for="node-input-nl" ><i class="fa fa-flag"></i> <span data-i18n="mqtt.label.flags">Flags</span></label>
|
||||
<div class="mqtt-flags">
|
||||
<div class="mqtt-flag">
|
||||
<label for="node-input-nl">
|
||||
<input type="checkbox" id="node-input-nl">
|
||||
<span data-i18n="mqtt.label.nl"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mqtt-flag">
|
||||
<label for="node-input-rap">
|
||||
<input type="checkbox" id="node-input-rap">
|
||||
<span data-i18n="mqtt.label.rap"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-row-mqtt5 form-row-mqtt-static">
|
||||
<label for="node-input-rh" style="width:100%"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.rh"></span></label>
|
||||
<select id="node-input-rh" style="margin-left: 104px; width: 70%">
|
||||
<option value="0" data-i18n="mqtt.label.rh0"></option>
|
||||
<option value="1" data-i18n="mqtt.label.rh1"></option>
|
||||
<option value="2" data-i18n="mqtt.label.rh2"></option>
|
||||
</select>
|
||||
</div>
|
||||
<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" data-i18n="mqtt.output.auto"></option>
|
||||
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="mqtt.output.string"></option>
|
||||
<option value="json" data-i18n="mqtt.output.json"></option>
|
||||
<option value="base64" data-i18n="mqtt.output.base64"></option>
|
||||
</select>
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="mqtt out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
|
||||
<input type="text" id="node-input-broker">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
|
||||
</div>
|
||||
|
||||
<div class="form-row mqtt-form-row-cols2">
|
||||
<label for="node-input-qos" class="mqtt-form-row-col1"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
|
||||
<select id="node-input-qos" class="mqtt-form-row-col1">
|
||||
<option value=""></option>
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
|
||||
<label for="node-input-retain" class="mqtt-form-row-col2"><i class="fa fa-history"></i> <span data-i18n="mqtt.retain"></span></label>
|
||||
<select id="node-input-retain" class="mqtt-form-row-col2" >
|
||||
<option value=""></option>
|
||||
<option value="false" data-i18n="mqtt.false"></option>
|
||||
<option value="true" data-i18n="mqtt.true"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-input-userProps"><span data-i18n="mqtt.label.userProperties"></span></label>
|
||||
<input type="text" id="node-input-userProps" style="width: calc(100% - 166px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-input-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
|
||||
<input type="text" id="node-input-respTopic" style="width: calc(100% - 166px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-input-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
|
||||
<input type="text" id="node-input-correl" style="width: calc(100% - 166px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-input-contentType"><span data-i18n="mqtt.label.contentType"></span></label>
|
||||
<input type="text" id="node-input-contentType" style="width: calc(100% - 166px);">
|
||||
</div>
|
||||
|
||||
<div class="form-row mqtt-form-row-cols2 mqtt5 mqtt5-out">
|
||||
<label for="node-input-expiry" class="mqtt-form-row-col1"><span data-i18n="mqtt.label.expiry"></span></label>
|
||||
<input id="node-input-expiry" style="width: calc(100% - 166px);" class="mqtt-form-row-col1" >
|
||||
</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"><span data-i18n="mqtt.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="mqtt-broker">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<ul style="min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
|
||||
</div>
|
||||
<div id="node-config-mqtt-broker-tabs-content" style="min-height:150px;">
|
||||
<div id="mqtt-broker-tab-connection" style="display:none">
|
||||
<div class="form-row node-input-broker">
|
||||
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
|
||||
<input type="text" id="node-config-input-broker" style="width: calc(100% - 300px);" data-i18n="[placeholder]mqtt.label.example">
|
||||
<label for="node-config-input-port" style="margin-left:20px; width:43px; "> <span data-i18n="mqtt.label.port"></span></label>
|
||||
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0">
|
||||
<input type="checkbox" id="node-config-input-autoConnect" style="margin: 0 5px 0 104px; display: inline-block; width: auto;">
|
||||
<label for="node-config-input-autoConnect" style="width: auto"><span data-i18n="mqtt.label.auto-connect"></span></label>
|
||||
</div>
|
||||
<div class="form-row" style="height: 34px;">
|
||||
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
|
||||
<span id="node-config-row-tls" class="hide"><input style="width: 320px;" type="text" id="node-config-input-tls"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-protocolVersion"><i class="fa fa-cog"></i> <span data-i18n="mqtt.label.protocolVersion"></span></label>
|
||||
<select id="node-config-input-protocolVersion" style="width:70%;">
|
||||
<option value="3" data-i18n="mqtt.label.protocolVersion3"></option>
|
||||
<option value="4" data-i18n="mqtt.label.protocolVersion4"></option>
|
||||
<option value="5" data-i18n="mqtt.label.protocolVersion5"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
|
||||
<input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-keepalive"><i class="fa fa-heartbeat"></i> <span data-i18n="mqtt.label.keepalive"></span></label>
|
||||
<input type="number" min="0" id="node-config-input-keepalive" style="width: 100px">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0">
|
||||
<label style="vertical-align:top;"><i class="fa fa-info"></i> <span data-i18n="mqtt.label.session"></span></label>
|
||||
<div style="display: inline-block; width:calc(100% - 110px)">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-cleansession" style="width: auto;">
|
||||
<input type="checkbox" id="node-config-input-cleansession" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
|
||||
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row mqtt5">
|
||||
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
|
||||
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row mqtt5">
|
||||
<label style="width: 125px;" for="node-config-input-userProps"><span data-i18n="mqtt.label.userProperties"></span></label>
|
||||
<input type="text" id="node-config-input-userProps" style="width: calc(100% - 166px);">
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div id="mqtt-broker-tab-security" style="display:none">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
|
||||
<input type="text" id="node-config-input-user">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="mqtt-broker-tab-messages" style="display:none">
|
||||
<div id="mqtt-broker-section-birth">
|
||||
<div class="red-ui-palette-header">
|
||||
<i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.birth-message"></span>
|
||||
</div>
|
||||
<div class="section-content" style="padding:10px 0 0 10px">
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
|
||||
<select id="node-config-input-birthRetain" style="width:75px !important">
|
||||
<option value="false" data-i18n="mqtt.false"></option>
|
||||
<option value="true" data-i18n="mqtt.true"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
|
||||
<select id="node-config-input-birthQos" style="width:75px !important">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-birth-contentType" data-i18n="mqtt.label.contentType"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-birth-contentType">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-birth-props" data-i18n="mqtt.label.userProperties"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-birth-props">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-birth-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
|
||||
<input type="text" id="node-config-input-birth-respTopic" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-birth-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
|
||||
<input type="text" id="node-config-input-birth-correl" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-birth-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
|
||||
<input id="node-config-input-birth-expiry" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mqtt-broker-section-close">
|
||||
<div class="red-ui-palette-header">
|
||||
<i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.close-message"></span>
|
||||
</div>
|
||||
<div class="section-content" style="padding:10px 0 0 10px">
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-closeTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closeTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.close-topic">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
|
||||
<select id="node-config-input-closeRetain" style="width:75px !important">
|
||||
<option value="false" data-i18n="mqtt.false"></option>
|
||||
<option value="true" data-i18n="mqtt.true"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-closePayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closePayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
|
||||
<select id="node-config-input-closeQos" style="width:75px !important">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-close-contentType" data-i18n="mqtt.label.contentType"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-close-contentType">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-close-props" data-i18n="mqtt.label.userProperties"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-close-props">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-close-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
|
||||
<input type="text" id="node-config-input-close-respTopic" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-close-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
|
||||
<input type="text" id="node-config-input-close-correl" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-close-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
|
||||
<input id="node-config-input-close-expiry" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mqtt-broker-section-will">
|
||||
<div class="red-ui-palette-header">
|
||||
<i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.will-message"></span>
|
||||
</div>
|
||||
<div class="section-content" style="padding:10px 0 0 10px">
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.will-topic">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
|
||||
<select id="node-config-input-willRetain" style="width:75px !important">
|
||||
<option value="false" data-i18n="mqtt.false"></option>
|
||||
<option value="true" data-i18n="mqtt.true"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 100px !important;" for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
|
||||
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
|
||||
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
|
||||
<select id="node-config-input-willQos" style="width:75px !important">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row mqtt5">
|
||||
<label><span data-i18n="mqtt.label.delay"></span></label>
|
||||
<input type="number" min="0" id="node-config-input-will-delay" style="width: 100px" >
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-will-contentType" data-i18n="mqtt.label.contentType"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-will-contentType">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-will-props" data-i18n="mqtt.label.userProperties"></label>
|
||||
<input type="text" style="width:calc(100% - 200px);" id="node-config-input-will-props">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-will-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
|
||||
<input type="text" id="node-config-input-will-respTopic" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-will-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
|
||||
<input type="text" id="node-config-input-will-correl" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
<div class="form-row mqtt5 mqtt5-out">
|
||||
<label for="node-config-input-will-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
|
||||
<input id="node-config-input-will-expiry" style="width: calc(100% - 200px);">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
var typedInputNoneOpt = { value: 'none', label: '', hasValue: false };
|
||||
var makeTypedInputOpt = function(value){
|
||||
return {
|
||||
value: value,
|
||||
label:value,
|
||||
hasValue: false
|
||||
}
|
||||
}
|
||||
var contentTypeOpts = [
|
||||
typedInputNoneOpt,
|
||||
makeTypedInputOpt("application/json"),
|
||||
makeTypedInputOpt("application/octet-stream"),
|
||||
makeTypedInputOpt("text/csv"),
|
||||
makeTypedInputOpt("text/html"),
|
||||
makeTypedInputOpt("text/plain"),
|
||||
{value:"other", label:""}
|
||||
];
|
||||
|
||||
function getDefaultContentType(value) {
|
||||
var defaultContentType;
|
||||
var matchedContentType = contentTypeOpts.filter(function(v) {
|
||||
return v.value === value;
|
||||
})
|
||||
if (matchedContentType.length > 0) {
|
||||
defaultContentType = matchedContentType[0].value;
|
||||
}
|
||||
if (value && !defaultContentType) {
|
||||
defaultContentType = 'other';
|
||||
}
|
||||
return defaultContentType || 'none'
|
||||
}
|
||||
|
||||
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) {
|
||||
if ($("#node-config-input-clientid").length) {
|
||||
// Currently editing the node
|
||||
return $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
|
||||
} else {
|
||||
return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
|
||||
}
|
||||
}},
|
||||
autoConnect: {value: true},
|
||||
usetls: {value: false},
|
||||
verifyservercert: { value: false},
|
||||
compatmode: { value: false},
|
||||
protocolVersion: { value: 4},
|
||||
keepalive: {value:60,validate:RED.validators.number()},
|
||||
cleansession: {value: true},
|
||||
birthTopic: {value:""},
|
||||
birthQos: {value:"0"},
|
||||
birthRetain: {value:false},
|
||||
birthPayload: {value:""},
|
||||
birthMsg: { value: {}},
|
||||
closeTopic: {value:""},
|
||||
closeQos: {value:"0"},
|
||||
closeRetain: {value:false},
|
||||
closePayload: {value:""},
|
||||
closeMsg: { value: {}},
|
||||
willTopic: {value:""},
|
||||
willQos: {value:"0"},
|
||||
willRetain: {value:false},
|
||||
willPayload: {value:""},
|
||||
willMsg: { value: {}},
|
||||
sessionExpiry: {value:0}
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
},
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
var b = this.broker;
|
||||
if (!b) { b = "undefined"; }
|
||||
var lab = "";
|
||||
lab = (this.clientid?this.clientid+"@":"")+b;
|
||||
if (b.indexOf("://") === -1){
|
||||
if (!this.port){ lab = lab + ":1883"; }
|
||||
else { lab = lab + ":" + this.port; }
|
||||
}
|
||||
return lab;
|
||||
},
|
||||
oneditprepare: function () {
|
||||
var tabs = RED.tabs.create({
|
||||
id: "node-config-mqtt-broker-tabs",
|
||||
onchange: function(tab) {
|
||||
$("#node-config-mqtt-broker-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
}
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "mqtt-broker-tab-connection",
|
||||
label: this._("mqtt.tabs-label.connection")
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "mqtt-broker-tab-security",
|
||||
label: this._("mqtt.tabs-label.security")
|
||||
});
|
||||
|
||||
tabs.addTab({
|
||||
id: "mqtt-broker-tab-messages",
|
||||
label: this._("mqtt.tabs-label.messages")
|
||||
});
|
||||
|
||||
function setUpSection(sectionId, v5Opts, isExpanded) {
|
||||
var birthMessageSection = $("#mqtt-broker-section-"+sectionId);
|
||||
var paletteHeader = birthMessageSection.find('.red-ui-palette-header');
|
||||
var twistie = paletteHeader.find('i');
|
||||
var sectionContent = birthMessageSection.find('.section-content');
|
||||
|
||||
function toggleSection(expanded) {
|
||||
twistie.toggleClass('expanded', expanded);
|
||||
sectionContent.toggle(expanded);
|
||||
}
|
||||
paletteHeader.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var isExpanded = twistie.hasClass('expanded');
|
||||
toggleSection(!isExpanded);
|
||||
});
|
||||
toggleSection(isExpanded);
|
||||
$("#node-config-input-"+sectionId+"-contentType").val(v5Opts?v5Opts.contentType:"").typedInput({
|
||||
default: getDefaultContentType(v5Opts?v5Opts.contentType:""),
|
||||
types: contentTypeOpts,
|
||||
});
|
||||
|
||||
$("#node-config-input-"+sectionId+"-props").val(v5Opts?v5Opts.userProps:"").typedInput({
|
||||
default: !(v5Opts?v5Opts.userProps:null)? 'none':'json',
|
||||
types: [typedInputNoneOpt, 'json'],
|
||||
});
|
||||
$("#node-config-input-"+sectionId+"-respTopic").val(v5Opts?v5Opts.respTopic:"").typedInput({
|
||||
default: !(v5Opts?v5Opts.respTopic:null)? 'none':'str',
|
||||
types: [typedInputNoneOpt, 'str'],
|
||||
});
|
||||
$("#node-config-input-"+sectionId+"-correl").val(v5Opts?v5Opts.correl:"").typedInput({
|
||||
default: !(v5Opts?v5Opts.correl:null)? 'none':'str',
|
||||
types: [typedInputNoneOpt, 'str'],
|
||||
});
|
||||
$("#node-config-input-"+sectionId+"-expiry").val(v5Opts?v5Opts.expiry:"").typedInput({
|
||||
default: !(v5Opts?v5Opts.expiry:null)? 'none':'num',
|
||||
types: [typedInputNoneOpt, 'num'],
|
||||
});
|
||||
}
|
||||
|
||||
// show first section if none are set so the user gets the idea
|
||||
var showBirthSection = this.birthTopic !== ""
|
||||
|| this.willTopic === ""
|
||||
&& this.birthTopic === ""
|
||||
&& this.closeTopic == "";
|
||||
setUpSection('birth', this.birthMsg, showBirthSection);
|
||||
setUpSection('close', this.closeMsg, this.closeTopic !== "");
|
||||
setUpSection('will', this.willMsg, this.willTopic !== "");
|
||||
|
||||
if (this.willMsg) {
|
||||
$("#node-config-input-will-delay").val(this.willMsg.delay);
|
||||
}
|
||||
|
||||
setTimeout(function() { tabs.resize(); },0);
|
||||
if (typeof this.cleansession === 'undefined') {
|
||||
this.cleansession = true;
|
||||
$("#node-config-input-cleansession").prop("checked",true);
|
||||
}
|
||||
if (typeof this.usetls === 'undefined') {
|
||||
this.usetls = false;
|
||||
$("#node-config-input-usetls").prop("checked",false);
|
||||
}
|
||||
if (typeof this.autoConnect === 'undefined') {
|
||||
this.autoConnect = true;
|
||||
$("#node-config-input-autoConnect").prop("checked",true);
|
||||
}
|
||||
if (this.compatmode === 'true' || this.compatmode === true) {
|
||||
delete this.compatmode;
|
||||
this.protocolVersion = 4;
|
||||
}
|
||||
if (typeof this.protocolVersion === 'undefined') {
|
||||
this.protocolVersion = 4;
|
||||
}
|
||||
$("#node-config-input-protocolVersion").on("change", function() {
|
||||
var v5 = $("#node-config-input-protocolVersion").val() == "5";
|
||||
if(v5) {
|
||||
$("#node-config-input-cleansession-label").text(RED._("node-red:mqtt.label.cleanstart"))
|
||||
$("div.form-row.mqtt5").show();
|
||||
} else {
|
||||
$("#node-config-input-cleansession-label").text(RED._("node-red:mqtt.label.cleansession"))
|
||||
$("div.form-row.mqtt5").hide();
|
||||
}
|
||||
});
|
||||
$("#node-config-input-protocolVersion").val(this.protocolVersion);
|
||||
$("#node-config-input-userProps").typedInput({
|
||||
default: !this.userProps ? 'none':'json',
|
||||
types: [typedInputNoneOpt, 'json']
|
||||
});
|
||||
if (typeof this.keepalive === 'undefined') {
|
||||
this.keepalive = 15;
|
||||
$("#node-config-input-keepalive").val(this.keepalive);
|
||||
}
|
||||
if (typeof this.birthQos === 'undefined') {
|
||||
this.birthQos = "0";
|
||||
$("#node-config-input-birthQos").val("0");
|
||||
}
|
||||
if (typeof this.closeQos === 'undefined') {
|
||||
this.willQos = "0";
|
||||
$("#node-config-input-willQos").val("0");
|
||||
}
|
||||
if (typeof this.willQos === 'undefined') {
|
||||
this.willQos = "0";
|
||||
$("#node-config-input-willQos").val("0");
|
||||
}
|
||||
|
||||
|
||||
function updateTLSOptions() {
|
||||
if ($("#node-config-input-usetls").is(':checked')) {
|
||||
$("#node-config-row-tls").show();
|
||||
} else {
|
||||
$("#node-config-row-tls").hide();
|
||||
}
|
||||
}
|
||||
updateTLSOptions();
|
||||
$("#node-config-input-usetls").on("click",function() {
|
||||
updateTLSOptions();
|
||||
});
|
||||
var node = this;
|
||||
function updateClientId() {
|
||||
if ($("#node-config-input-cleansession").is(":checked")) {
|
||||
$("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid"));
|
||||
} else {
|
||||
$("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid-nonclean"));
|
||||
}
|
||||
$("#node-config-input-clientid").trigger("change");
|
||||
}
|
||||
setTimeout(updateClientId,0);
|
||||
$("#node-config-input-cleansession").on("click",function() {
|
||||
updateClientId();
|
||||
});
|
||||
|
||||
function updatePortEntry(){
|
||||
var disabled = $("#node-config-input-port").prop("disabled");
|
||||
if ($("#node-config-input-broker").val().indexOf("://") === -1){
|
||||
if (disabled){
|
||||
$("#node-config-input-port").prop("disabled", false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!disabled){
|
||||
$("#node-config-input-port").prop("disabled", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$("#node-config-input-broker").on("change", function() {
|
||||
updatePortEntry();
|
||||
});
|
||||
$("#node-config-input-broker").on( "keyup", function() {
|
||||
updatePortEntry();
|
||||
});
|
||||
setTimeout(updatePortEntry,50);
|
||||
setTimeout(function() {
|
||||
$("#node-config-input-protocolVersion").trigger("change");
|
||||
},50);
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-config-input-usetls").is(':checked')) {
|
||||
$("#node-config-input-tls").val("");
|
||||
}
|
||||
|
||||
var v5 = $("#node-config-input-protocolVersion").val() == "5";
|
||||
|
||||
function saveV5Message(section) {
|
||||
var msg = {};
|
||||
if ($("#node-config-input-"+section+"Topic").val().trim().length > 0) {
|
||||
var contentType = $("#node-config-input-"+section+"-contentType").val().trim();
|
||||
if (contentType === '') {
|
||||
contentType = $("#node-config-input-"+section+"-contentType").typedInput('type');
|
||||
if (contentType === 'none' || contentType === 'other') {
|
||||
contentType = "";
|
||||
}
|
||||
}
|
||||
if (contentType) {
|
||||
msg.contentType = contentType;
|
||||
}
|
||||
var props = $("#node-config-input-"+section+"-props").val().trim();
|
||||
if (props) {
|
||||
msg.userProps = props;
|
||||
}
|
||||
var resp = $("#node-config-input-"+section+"-respTopic").val().trim();
|
||||
if (props) {
|
||||
msg.respTopic = resp;
|
||||
}
|
||||
var correl = $("#node-config-input-"+section+"-correl").val().trim();
|
||||
if (correl) {
|
||||
msg.correl = correl;
|
||||
}
|
||||
var expiry = $("#node-config-input-"+section+"-expiry").val().trim();
|
||||
if (expiry) {
|
||||
msg.expiry = expiry;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (v5) {
|
||||
this.birthMsg = saveV5Message("birth");
|
||||
this.closeMsg = saveV5Message("close");
|
||||
this.willMsg = saveV5Message("will");
|
||||
var willDelay = $("#node-config-input-will-delay").val();
|
||||
if (willDelay) {
|
||||
this.willMsg.delay = willDelay;
|
||||
}
|
||||
} else {
|
||||
this.willMsg = {};
|
||||
this.birthMsg = {};
|
||||
this.closeMsg = {};
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('mqtt in',{
|
||||
category: 'network',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {
|
||||
value:"",
|
||||
validate: function(v) {
|
||||
var isDynamic = this.inputs === 1;
|
||||
var topicTypeSelect = $("#node-input-topicType");
|
||||
if (topicTypeSelect.length) {
|
||||
isDynamic = topicTypeSelect.val()==='dynamic'
|
||||
}
|
||||
return isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v));
|
||||
}
|
||||
},
|
||||
qos: {value: "2"},
|
||||
datatype: {value:"auto",required:true},
|
||||
broker: {type:"mqtt-broker", required:true},
|
||||
// subscriptionIdentifier: {value:0},
|
||||
nl: {value:false},
|
||||
rap: {value:true},
|
||||
rh: {value:0},
|
||||
inputs: {value:0},
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "bridge.svg",
|
||||
label: function() {
|
||||
var label = "mqtt";
|
||||
if(this.topicType !== "dynamic" && this.topic) {
|
||||
label = this.topic;
|
||||
}
|
||||
return this.name || label;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
const node = this;
|
||||
const isV5Broker = function() {
|
||||
var confNode = RED.nodes.node($("#node-input-broker").val());
|
||||
return confNode && confNode.protocolVersion === "5";
|
||||
}
|
||||
const isDynamic = function() {
|
||||
return $('#node-input-topicType').val() === "dynamic";
|
||||
}
|
||||
const updateVisibility = function() {
|
||||
var v5 = isV5Broker();
|
||||
var dynamic = isDynamic();
|
||||
$("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-broker").on("change",function(d){
|
||||
updateVisibility();
|
||||
});
|
||||
|
||||
$('#node-input-topicType').on("change", function () {
|
||||
$("#node-input-inputs").val(isDynamic() ? 1 : 0);
|
||||
updateVisibility();
|
||||
});
|
||||
|
||||
if (this.inputs === 1) {
|
||||
$('#node-input-topicType').val('dynamic')
|
||||
} else {
|
||||
$('#node-input-topicType').val('topic')
|
||||
}
|
||||
$('#node-input-topicType').trigger("change");
|
||||
|
||||
if (this.qos === undefined) {
|
||||
$("#node-input-qos").val("2");
|
||||
}
|
||||
if (this.datatype === undefined) {
|
||||
$("#node-input-datatype").val("auto");
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if ($('#node-input-topicType').val() === "dynamic") {
|
||||
$('#node-input-topic').val("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('mqtt out',{
|
||||
category: 'network',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
qos: {value:""},
|
||||
retain: {value:""},
|
||||
respTopic: {value:""},
|
||||
contentType: {value:""},
|
||||
userProps: {value:''},
|
||||
correl: {value:''},
|
||||
expiry: {value:''},
|
||||
broker: {type:"mqtt-broker", required:true}
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge.svg",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.topic||"mqtt";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
|
||||
function showHideDynamicFields() {
|
||||
var confNode = RED.nodes.node($("#node-input-broker").val());
|
||||
var v5 = confNode && confNode.protocolVersion == "5";
|
||||
if(v5) {
|
||||
$("div.form-row.mqtt5").show();
|
||||
var t = $("#node-input-respTopic").typedInput("type");
|
||||
if (t == 'none') {
|
||||
$("#node-input-correl").parent().hide();
|
||||
} else {
|
||||
$("#node-input-correl").parent().show();
|
||||
}
|
||||
} else {
|
||||
$("div.form-row.mqtt5").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$("#node-input-broker").on("change",function(d){
|
||||
showHideDynamicFields();
|
||||
});
|
||||
|
||||
var respTopicTI = $("#node-input-respTopic").typedInput({
|
||||
default: !this.respTopic ? 'none':'str',
|
||||
types: [typedInputNoneOpt, 'str'],
|
||||
});
|
||||
|
||||
var correlTI = $("#node-input-correl").typedInput({
|
||||
default: !this.correl ? 'none':'str',
|
||||
types: [typedInputNoneOpt, 'str']
|
||||
});
|
||||
//show / hide correlation data depending on respTopic
|
||||
respTopicTI.on("change", showHideDynamicFields);
|
||||
respTopicTI.triggerHandler("change");
|
||||
|
||||
$("#node-input-userProps").typedInput({
|
||||
default: !this.userProps ? 'none':'json',
|
||||
types: [typedInputNoneOpt, 'json'],
|
||||
});
|
||||
$("#node-input-expiry").typedInput({
|
||||
default: !this.expiry ? 'none':'num',
|
||||
types: [typedInputNoneOpt, 'num']
|
||||
});
|
||||
$("#node-input-contentType").typedInput({
|
||||
default: getDefaultContentType(this.contentType),
|
||||
types: contentTypeOpts
|
||||
})
|
||||
},
|
||||
oneditsave: function() {
|
||||
|
||||
var contentType = $("#node-input-contentType").val().trim();
|
||||
if (contentType === '') {
|
||||
contentType = $("#node-input-contentType").typedInput('type');
|
||||
if (contentType === 'none' || contentType === 'other') {
|
||||
contentType = "";
|
||||
}
|
||||
}
|
||||
$("#node-input-contentType").val(contentType)
|
||||
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
File diff suppressed because it is too large
Load Diff
@ -1,273 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="http in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
|
||||
<select type="text" id="node-input-method" style="width:70%;">
|
||||
<option value="get">GET</option>
|
||||
<option value="post">POST</option>
|
||||
<option value="put">PUT</option>
|
||||
<option value="delete">DELETE</option>
|
||||
<option value="patch">PATCH</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-http-in-upload hide">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-upload" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-upload" style="width: 70%;" data-i18n="httpin.label.upload"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
|
||||
<input id="node-input-url" type="text" placeholder="/url">
|
||||
</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-row row-swagger-doc">
|
||||
<label for="node-input-swaggerDoc"><i class="fa fa-file-text-o"></i> <span data-i18n="httpin.label.doc"></span></label>
|
||||
<input type="text" id="node-input-swaggerDoc">
|
||||
</div>
|
||||
<div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="http response">
|
||||
<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-row">
|
||||
<label for="node-input-statusCode"><i class="fa fa-long-arrow-left"></i> <span data-i18n="httpin.label.status"></span></label>
|
||||
<input type="text" id="node-input-statusCode" placeholder="msg.statusCode">
|
||||
</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-tips"><span data-i18n="[html]httpin.tip.res"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
RED.nodes.registerType('http in',{
|
||||
category: 'network',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
url: {value:"",required:true},
|
||||
method: {value:"get",required:true},
|
||||
upload: {value:false},
|
||||
swaggerDoc: {type:"swagger-doc", required:false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.svg",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
} else if (this.url) {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.url.charAt(0) == "/") {
|
||||
root += this.url.slice(1);
|
||||
} else {
|
||||
root += this.url;
|
||||
}
|
||||
return "["+this.method+"] "+root;
|
||||
} else {
|
||||
return "http";
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
$("#node-input-tip").hide();
|
||||
} else {
|
||||
$("#node-input-path").html(root);
|
||||
$("#node-input-tip").show();
|
||||
}
|
||||
if(!RED.nodes.getType("swagger-doc")){
|
||||
$('.row-swagger-doc').hide();
|
||||
}
|
||||
$("#node-input-method").on("change", function() {
|
||||
if ($(this).val() === "post") {
|
||||
$(".form-row-http-in-upload").show();
|
||||
} else {
|
||||
$(".form-row-http-in-upload").hide();
|
||||
}
|
||||
}).change();
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
var headerTypes = [
|
||||
{value:"content-type",label:"Content-Type",hasValue: false},
|
||||
{value:"location",label:"Location",hasValue: false},
|
||||
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
|
||||
]
|
||||
var contentTypes = [
|
||||
{value:"application/json",label:"application/json",hasValue: false},
|
||||
{value:"application/xml",label:"application/xml",hasValue: false},
|
||||
{value:"text/css",label:"text/css",hasValue: false},
|
||||
{value:"text/html",label:"text/html",hasValue: false},
|
||||
{value:"text/plain",label:"text/plain",hasValue: false},
|
||||
{value:"image/gif",label:"image/gif",hasValue: false},
|
||||
{value:"image/png",label:"image/png",hasValue: false},
|
||||
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
|
||||
];
|
||||
|
||||
RED.nodes.registerType('http response',{
|
||||
category: 'network',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
statusCode: {value:"",validate: RED.validators.number(true)},
|
||||
headers: {value:{}}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
align: "right",
|
||||
icon: "white-globe.svg",
|
||||
label: function() {
|
||||
return this.name||("http"+(this.statusCode?" ("+this.statusCode+")":""));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var headerList = $("#node-input-headers-container").css('min-height','150px').css('min-width','450px').editableList({
|
||||
addItem: function(container,i,header) {
|
||||
var row = $('<div/>').css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex'
|
||||
}).appendTo(container);
|
||||
var propertNameCell = $('<div/>').css({'flex-grow':1}).appendTo(row);
|
||||
var propertyName = $('<input/>',{class:"node-input-header-name",type:"text", style:"width: 100%"})
|
||||
.appendTo(propertNameCell)
|
||||
.typedInput({types:headerTypes});
|
||||
|
||||
var propertyValueCell = $('<div/>').css({'flex-grow':1,'margin-left':'10px'}).appendTo(row);
|
||||
var propertyValue = $('<input/>',{class:"node-input-header-value",type:"text",style:"width: 100%"})
|
||||
.appendTo(propertyValueCell)
|
||||
.typedInput({types:
|
||||
header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]
|
||||
});
|
||||
|
||||
var matchedType = headerTypes.filter(function(ht) {
|
||||
return ht.value === header.h
|
||||
});
|
||||
if (matchedType.length === 0) {
|
||||
propertyName.typedInput('type','other');
|
||||
propertyName.typedInput('value',header.h);
|
||||
propertyValue.typedInput('value',header.v);
|
||||
} else {
|
||||
propertyName.typedInput('type',header.h);
|
||||
|
||||
if (header.h === "content-type") {
|
||||
matchedType = contentTypes.filter(function(ct) {
|
||||
return ct.value === header.v;
|
||||
});
|
||||
if (matchedType.length === 0) {
|
||||
propertyValue.typedInput('type','other');
|
||||
propertyValue.typedInput('value',header.v);
|
||||
} else {
|
||||
propertyValue.typedInput('type',header.v);
|
||||
}
|
||||
} else {
|
||||
propertyValue.typedInput('value',header.v);
|
||||
}
|
||||
}
|
||||
|
||||
matchedType = headerTypes.filter(function(ht) {
|
||||
return ht.value === header.h
|
||||
});
|
||||
if (matchedType.length === 0) {
|
||||
propertyName.typedInput('type','other');
|
||||
propertyName.typedInput('value',header.h);
|
||||
} else {
|
||||
propertyName.typedInput('type',header.h);
|
||||
}
|
||||
|
||||
propertyName.on('change',function(event) {
|
||||
var type = propertyName.typedInput('type');
|
||||
if (type === 'content-type') {
|
||||
propertyValue.typedInput('types',contentTypes);
|
||||
} else {
|
||||
propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]);
|
||||
}
|
||||
});
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
|
||||
if (this.headers) {
|
||||
for (var key in this.headers) {
|
||||
if (this.headers.hasOwnProperty(key)) {
|
||||
headerList.editableList('addItem',{h:key,v:this.headers[key]});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var headers = $("#node-input-headers-container").editableList('items');
|
||||
var node = this;
|
||||
node.headers = {};
|
||||
headers.each(function(i) {
|
||||
var header = $(this);
|
||||
var keyType = header.find(".node-input-header-name").typedInput('type');
|
||||
var keyValue = header.find(".node-input-header-name").typedInput('value');
|
||||
var valueType = header.find(".node-input-header-value").typedInput('type');
|
||||
var valueValue = header.find(".node-input-header-value").typedInput('value');
|
||||
var key = keyType;
|
||||
var value = valueType;
|
||||
if (keyType === 'other') {
|
||||
key = keyValue;
|
||||
}
|
||||
if (valueType === 'other') {
|
||||
value = valueValue;
|
||||
}
|
||||
if (key !== '') {
|
||||
node.headers[key] = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-headers-container-row)");
|
||||
var height = size.height;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-headers-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
|
||||
$("#node-input-headers-container").editableList('height',height);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,356 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var bodyParser = require("body-parser");
|
||||
var multer = require("multer");
|
||||
var cookieParser = require("cookie-parser");
|
||||
var getBody = require('raw-body');
|
||||
var cors = require('cors');
|
||||
var onHeaders = require('on-headers');
|
||||
var typer = require('content-type');
|
||||
var mediaTyper = require('media-typer');
|
||||
var isUtf8 = require('is-utf8');
|
||||
var hashSum = require("hash-sum");
|
||||
|
||||
function rawBodyParser(req, res, next) {
|
||||
if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip
|
||||
if (req._body) { return next(); }
|
||||
req.body = "";
|
||||
req._body = true;
|
||||
|
||||
var isText = true;
|
||||
var checkUTF = false;
|
||||
|
||||
if (req.headers['content-type']) {
|
||||
var contentType = typer.parse(req.headers['content-type'])
|
||||
if (contentType.type) {
|
||||
var parsedType = mediaTyper.parse(contentType.type);
|
||||
if (parsedType.type === "text") {
|
||||
isText = true;
|
||||
} else if (parsedType.subtype === "xml" || parsedType.suffix === "xml") {
|
||||
isText = true;
|
||||
} else if (parsedType.type !== "application") {
|
||||
isText = false;
|
||||
} else if ((parsedType.subtype !== "octet-stream")
|
||||
&& (parsedType.subtype !== "cbor")
|
||||
&& (parsedType.subtype !== "x-protobuf")) {
|
||||
checkUTF = true;
|
||||
} else {
|
||||
// application/octet-stream or application/cbor
|
||||
isText = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getBody(req, {
|
||||
length: req.headers['content-length'],
|
||||
encoding: isText ? "utf8" : null
|
||||
}, function (err, buf) {
|
||||
if (err) { return next(err); }
|
||||
if (!isText && checkUTF && isUtf8(buf)) {
|
||||
buf = buf.toString()
|
||||
}
|
||||
req.body = buf;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
var corsSetup = false;
|
||||
|
||||
function createRequestWrapper(node,req) {
|
||||
// This misses a bunch of properties (eg headers). Before we use this function
|
||||
// need to ensure it captures everything documented by Express and HTTP modules.
|
||||
var wrapper = {
|
||||
_req: req
|
||||
};
|
||||
var toWrap = [
|
||||
"param",
|
||||
"get",
|
||||
"is",
|
||||
"acceptsCharset",
|
||||
"acceptsLanguage",
|
||||
"app",
|
||||
"baseUrl",
|
||||
"body",
|
||||
"cookies",
|
||||
"fresh",
|
||||
"hostname",
|
||||
"ip",
|
||||
"ips",
|
||||
"originalUrl",
|
||||
"params",
|
||||
"path",
|
||||
"protocol",
|
||||
"query",
|
||||
"route",
|
||||
"secure",
|
||||
"signedCookies",
|
||||
"stale",
|
||||
"subdomains",
|
||||
"xhr",
|
||||
"socket" // TODO: tidy this up
|
||||
];
|
||||
toWrap.forEach(function(f) {
|
||||
if (typeof req[f] === "function") {
|
||||
wrapper[f] = function() {
|
||||
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.req."+f}));
|
||||
var result = req[f].apply(req,arguments);
|
||||
if (result === req) {
|
||||
return wrapper;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wrapper[f] = req[f];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
function createResponseWrapper(node,res) {
|
||||
var wrapper = {
|
||||
_res: res
|
||||
};
|
||||
var toWrap = [
|
||||
"append",
|
||||
"attachment",
|
||||
"cookie",
|
||||
"clearCookie",
|
||||
"download",
|
||||
"end",
|
||||
"format",
|
||||
"get",
|
||||
"json",
|
||||
"jsonp",
|
||||
"links",
|
||||
"location",
|
||||
"redirect",
|
||||
"render",
|
||||
"send",
|
||||
"sendfile",
|
||||
"sendFile",
|
||||
"sendStatus",
|
||||
"set",
|
||||
"status",
|
||||
"type",
|
||||
"vary"
|
||||
];
|
||||
toWrap.forEach(function(f) {
|
||||
wrapper[f] = function() {
|
||||
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.res."+f}));
|
||||
var result = res[f].apply(res,arguments);
|
||||
if (result === res) {
|
||||
return wrapper;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
var corsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeCors) {
|
||||
corsHandler = cors(RED.settings.httpNodeCors);
|
||||
RED.httpNode.options("*",corsHandler);
|
||||
}
|
||||
|
||||
function HTTPIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
if (RED.settings.httpNodeRoot !== false) {
|
||||
|
||||
if (!n.url) {
|
||||
this.warn(RED._("httpin.errors.missing-path"));
|
||||
return;
|
||||
}
|
||||
this.url = n.url;
|
||||
if (this.url[0] !== '/') {
|
||||
this.url = '/'+this.url;
|
||||
}
|
||||
this.method = n.method;
|
||||
this.upload = n.upload;
|
||||
this.swaggerDoc = n.swaggerDoc;
|
||||
|
||||
var node = this;
|
||||
|
||||
this.errorHandler = function(err,req,res,next) {
|
||||
node.warn(err);
|
||||
res.sendStatus(500);
|
||||
};
|
||||
|
||||
this.callback = function(req,res) {
|
||||
var msgid = RED.util.generateId();
|
||||
res._msgid = msgid;
|
||||
if (node.method.match(/^(post|delete|put|options|patch)$/)) {
|
||||
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
|
||||
} else if (node.method == "get") {
|
||||
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.query});
|
||||
} else {
|
||||
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res)});
|
||||
}
|
||||
};
|
||||
|
||||
var httpMiddleware = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeMiddleware) {
|
||||
if (typeof RED.settings.httpNodeMiddleware === "function" || Array.isArray(RED.settings.httpNodeMiddleware)) {
|
||||
httpMiddleware = RED.settings.httpNodeMiddleware;
|
||||
}
|
||||
}
|
||||
|
||||
var maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
|
||||
var jsonParser = bodyParser.json({limit:maxApiRequestSize});
|
||||
var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true});
|
||||
|
||||
var metricsHandler = function(req,res,next) { next(); }
|
||||
if (this.metric()) {
|
||||
metricsHandler = function(req, res, next) {
|
||||
var startAt = process.hrtime();
|
||||
onHeaders(res, function() {
|
||||
if (res._msgid) {
|
||||
var diff = process.hrtime(startAt);
|
||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||
var metricResponseTime = ms.toFixed(3);
|
||||
var metricContentLength = res.getHeader("content-length");
|
||||
//assuming that _id has been set for res._metrics in HttpOut node!
|
||||
node.metric("response.time.millis", {_msgid:res._msgid} , metricResponseTime);
|
||||
node.metric("response.content-length.bytes", {_msgid:res._msgid} , metricContentLength);
|
||||
}
|
||||
});
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
var multipartParser = function(req,res,next) { next(); }
|
||||
if (this.upload) {
|
||||
var mp = multer({ storage: multer.memoryStorage() }).any();
|
||||
multipartParser = function(req,res,next) {
|
||||
mp(req,res,function(err) {
|
||||
req._body = true;
|
||||
next(err);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
if (this.method == "get") {
|
||||
RED.httpNode.get(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,this.callback,this.errorHandler);
|
||||
} else if (this.method == "post") {
|
||||
RED.httpNode.post(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,multipartParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
} else if (this.method == "put") {
|
||||
RED.httpNode.put(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
} else if (this.method == "patch") {
|
||||
RED.httpNode.patch(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
} else if (this.method == "delete") {
|
||||
RED.httpNode.delete(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
}
|
||||
|
||||
this.on("close",function() {
|
||||
var node = this;
|
||||
RED.httpNode._router.stack.forEach(function(route,i,routes) {
|
||||
if (route.route && route.route.path === node.url && route.route.methods[node.method]) {
|
||||
routes.splice(i,1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.warn(RED._("httpin.errors.not-created"));
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("http in",HTTPIn);
|
||||
|
||||
|
||||
function HTTPOut(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.headers = n.headers||{};
|
||||
this.statusCode = n.statusCode;
|
||||
this.on("input",function(msg,_send,done) {
|
||||
if (msg.res) {
|
||||
var headers = RED.util.cloneMessage(node.headers);
|
||||
if (msg.headers) {
|
||||
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
|
||||
var headerHash = msg.headers['x-node-red-request-node'];
|
||||
delete msg.headers['x-node-red-request-node'];
|
||||
var hash = hashSum(msg.headers);
|
||||
if (hash === headerHash) {
|
||||
delete msg.headers;
|
||||
}
|
||||
}
|
||||
if (msg.headers) {
|
||||
for (var h in msg.headers) {
|
||||
if (msg.headers.hasOwnProperty(h) && !headers.hasOwnProperty(h)) {
|
||||
headers[h] = msg.headers[h];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(headers).length > 0) {
|
||||
msg.res._res.set(headers);
|
||||
}
|
||||
if (msg.cookies) {
|
||||
for (var name in msg.cookies) {
|
||||
if (msg.cookies.hasOwnProperty(name)) {
|
||||
if (msg.cookies[name] === null || msg.cookies[name].value === null) {
|
||||
if (msg.cookies[name]!==null) {
|
||||
msg.res._res.clearCookie(name,msg.cookies[name]);
|
||||
} else {
|
||||
msg.res._res.clearCookie(name);
|
||||
}
|
||||
} else if (typeof msg.cookies[name] === 'object') {
|
||||
msg.res._res.cookie(name,msg.cookies[name].value,msg.cookies[name]);
|
||||
} else {
|
||||
msg.res._res.cookie(name,msg.cookies[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var statusCode = node.statusCode || msg.statusCode || 200;
|
||||
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
|
||||
msg.res._res.status(statusCode).jsonp(msg.payload);
|
||||
} else {
|
||||
if (msg.res._res.get('content-length') == null) {
|
||||
var len;
|
||||
if (msg.payload == null) {
|
||||
len = 0;
|
||||
} else if (Buffer.isBuffer(msg.payload)) {
|
||||
len = msg.payload.length;
|
||||
} else if (typeof msg.payload == "number") {
|
||||
len = Buffer.byteLength(""+msg.payload);
|
||||
} else {
|
||||
len = Buffer.byteLength(msg.payload);
|
||||
}
|
||||
msg.res._res.set('content-length', len);
|
||||
}
|
||||
|
||||
if (typeof msg.payload === "number") {
|
||||
msg.payload = ""+msg.payload;
|
||||
}
|
||||
msg.res._res.status(statusCode).send(msg.payload);
|
||||
}
|
||||
} else {
|
||||
node.warn(RED._("httpin.errors.no-response"));
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("http response",HTTPOut);
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="http request">
|
||||
<div class="form-row">
|
||||
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
|
||||
<select type="text" id="node-input-method" style="width:70%;">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="HEAD">HEAD</option>
|
||||
<option value="use" data-i18n="httpin.setby"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
|
||||
<input id="node-input-url" type="text" placeholder="http://">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-paytoqs-row">
|
||||
<label for="node-input-paytoqs"><span data-i18n="common.label.payload"></span></label>
|
||||
<select id="node-input-paytoqs" style="width: 70%;">
|
||||
<option value="ignore" data-i18n="httpin.label.paytoqs.ignore"></option>
|
||||
<option value="query" data-i18n="httpin.label.paytoqs.query"></option>
|
||||
<option value="body" data-i18n="httpin.label.paytoqs.body"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
|
||||
<div id="node-row-tls" class="hide">
|
||||
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
|
||||
<div style="margin-left: 20px" class="node-input-useAuth-row hide">
|
||||
<div class="form-row">
|
||||
<label for="node-input-authType-select"><i class="fa fa-user-secret "></i> <span data-i18n="httpin.label.authType"></span></label>
|
||||
<select type="text" id="node-input-authType-select" style="width:70%;">
|
||||
<option value="basic" data-i18n="httpin.basic"></option>
|
||||
<option value="digest" data-i18n="httpin.digest"></option>
|
||||
<option value="bearer" data-i18n="httpin.bearer"></option>
|
||||
</select>
|
||||
<input type="hidden" id="node-input-authType">
|
||||
</div>
|
||||
<div class="form-row node-input-basic-row">
|
||||
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
|
||||
<input type="text" id="node-input-user">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-password"> <i class="fa fa-lock"></i> <span data-i18n="common.label.password" id="node-span-password"></span><span data-i18n="httpin.label.bearerToken" id="node-span-token" style="display:none"></span></label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-persist" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-persist" style="width: auto" data-i18n="httpin.persist"></label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-useProxy" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-useProxy" style="width: auto;"><span data-i18n="httpin.use-proxy"></span></label>
|
||||
<div id="node-input-useProxy-row" class="hide">
|
||||
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-proxy"><i class="fa fa-globe"></i> <span data-i18n="httpin.proxy-config"></span></label><input type="text" style="width: 270px" id="node-input-proxy">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-senderr" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-senderr" style="width: auto" data-i18n="httpin.senderr"></label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
|
||||
<select type="text" id="node-input-ret" style="width:70%;">
|
||||
<option value="txt" data-i18n="httpin.utf8"></option>
|
||||
<option value="bin" data-i18n="httpin.binary"></option>
|
||||
<option value="obj" data-i18n="httpin.json"></option>
|
||||
</select>
|
||||
</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">
|
||||
RED.nodes.registerType('http request',{
|
||||
category: 'network',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
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},
|
||||
persist: {value:false},
|
||||
proxy: {type:"http proxy",required: false},
|
||||
authType: {value: ""},
|
||||
senderr: {value: false}
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
outputLabels: function(i) {
|
||||
return ({
|
||||
txt: this._("httpin.label.utf8String"),
|
||||
bin: this._("httpin.label.binaryBuffer"),
|
||||
obj: this._("httpin.label.jsonObject")
|
||||
}[this.ret]);
|
||||
},
|
||||
icon: "white-globe.svg",
|
||||
label: function() {
|
||||
return this.name||this._("httpin.httpreq");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-useAuth").on("change", function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$(".node-input-useAuth-row").show();
|
||||
// Nodes (< version 0.20.x) with credentials but without authentication type, need type 'basic'
|
||||
if (!$('#node-input-authType').val()) {
|
||||
$("#node-input-authType-select").val('basic').trigger("change");
|
||||
}
|
||||
} else {
|
||||
$(".node-input-useAuth-row").hide();
|
||||
$('#node-input-authType').val('');
|
||||
$('#node-input-user').val('');
|
||||
$('#node-input-password').val('');
|
||||
}
|
||||
});
|
||||
$("#node-input-authType-select").on("change", function() {
|
||||
var val = $(this).val();
|
||||
$("#node-input-authType").val(val);
|
||||
if (val === "basic" || val === "digest") {
|
||||
$(".node-input-basic-row").show();
|
||||
$('#node-span-password').show();
|
||||
$('#node-span-token').hide();
|
||||
} else if (val === "bearer") {
|
||||
$(".node-input-basic-row").hide();
|
||||
$('#node-span-password').hide();
|
||||
$('#node-span-token').show();
|
||||
$('#node-input-user').val('');
|
||||
}
|
||||
});
|
||||
$("#node-input-method").on("change", function() {
|
||||
if ($(this).val() == "GET") {
|
||||
$(".node-input-paytoqs-row").show();
|
||||
} else {
|
||||
$(".node-input-paytoqs-row").hide();
|
||||
}
|
||||
});
|
||||
if (this.paytoqs === true || this.paytoqs == "query") {
|
||||
$("#node-input-paytoqs").val("query");
|
||||
} else if (this.paytoqs === "body") {
|
||||
$("#node-input-paytoqs").val("body");
|
||||
} else {
|
||||
$("#node-input-paytoqs").val("ignore");
|
||||
}
|
||||
if (this.authType) {
|
||||
$('#node-input-useAuth').prop('checked', true);
|
||||
$("#node-input-authType-select").val(this.authType);
|
||||
$("#node-input-authType-select").change();
|
||||
} else {
|
||||
$('#node-input-useAuth').prop('checked', false);
|
||||
}
|
||||
$("#node-input-useAuth").change();
|
||||
|
||||
function updateTLSOptions() {
|
||||
if ($("#node-input-usetls").is(':checked')) {
|
||||
$("#node-row-tls").show();
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
}
|
||||
if (this.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
}
|
||||
updateTLSOptions();
|
||||
$("#node-input-usetls").on("click",function() {
|
||||
updateTLSOptions();
|
||||
});
|
||||
|
||||
function updateProxyOptions() {
|
||||
if ($("#node-input-useProxy").is(":checked")) {
|
||||
$("#node-input-useProxy-row").show();
|
||||
} else {
|
||||
$("#node-input-useProxy-row").hide();
|
||||
}
|
||||
}
|
||||
if (this.proxy) {
|
||||
$("#node-input-useProxy").prop("checked", true);
|
||||
} else {
|
||||
$("#node-input-useProxy").prop("checked", false);
|
||||
}
|
||||
updateProxyOptions();
|
||||
$("#node-input-useProxy").on("click", function() {
|
||||
updateProxyOptions();
|
||||
});
|
||||
|
||||
$("#node-input-ret").on("change", function() {
|
||||
if ($("#node-input-ret").val() === "obj") {
|
||||
$("#tip-json").show();
|
||||
} else {
|
||||
$("#tip-json").hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
$("#node-input-tls").val("_ADD_");
|
||||
}
|
||||
if (!$("#node-input-useProxy").is(":checked")) {
|
||||
$("#node-input-proxy").val("_ADD_");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,673 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
const got = require("got");
|
||||
const {CookieJar} = require("tough-cookie");
|
||||
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
||||
const FormData = require('form-data');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const URL = require("url").URL
|
||||
var mustache = require("mustache");
|
||||
var querystring = require("querystring");
|
||||
var cookie = require("cookie");
|
||||
var hashSum = require("hash-sum");
|
||||
|
||||
|
||||
// Cache a reference to the existing https.request function
|
||||
// so we can compare later to see if an old agent-base instance
|
||||
// has been required.
|
||||
// This is generally okay as the core nodes are required before
|
||||
// any contrib nodes. Where it will fail is if the agent-base module
|
||||
// is required via the settings file or outside of Node-RED before it
|
||||
// is started.
|
||||
// If there are other modules that patch the function, they will get undone
|
||||
// as well. Not much we can do about that right now. Patching core
|
||||
// functions is bad.
|
||||
const HTTPS_MODULE = require("https");
|
||||
const HTTPS_REQUEST = HTTPS_MODULE.request;
|
||||
|
||||
function checkNodeAgentPatch() {
|
||||
if (HTTPS_MODULE.request !== HTTPS_REQUEST && HTTPS_MODULE.request.length === 2) {
|
||||
RED.log.warn(`
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Patched https.request function detected. This will break the
|
||||
HTTP Request node. The original code has now been restored.
|
||||
|
||||
This is likely caused by a contrib node including an old version of
|
||||
the 'agent-base@<5.0.0' module.
|
||||
|
||||
You can identify what node is at fault by running:
|
||||
npm list agent-base
|
||||
in your Node-RED user directory (${RED.settings.userDir}).
|
||||
---------------------------------------------------------------------
|
||||
`);
|
||||
HTTPS_MODULE.request = HTTPS_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
function HTTPRequest(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
checkNodeAgentPatch();
|
||||
var node = this;
|
||||
var nodeUrl = n.url;
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
var paytoqs = false;
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
}
|
||||
this.ret = n.ret || "txt";
|
||||
this.authType = n.authType || "basic";
|
||||
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
|
||||
else { this.reqTimeout = 120000; }
|
||||
|
||||
if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; }
|
||||
else if (n.paytoqs === "body") { paytobody = true; }
|
||||
|
||||
|
||||
var prox, noprox;
|
||||
if (process.env.http_proxy) { prox = process.env.http_proxy; }
|
||||
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
|
||||
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
|
||||
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
|
||||
|
||||
var proxyConfig = null;
|
||||
if (n.proxy) {
|
||||
proxyConfig = RED.nodes.getNode(n.proxy);
|
||||
prox = proxyConfig.url;
|
||||
noprox = proxyConfig.noproxy;
|
||||
}
|
||||
|
||||
let timingLog = false;
|
||||
if (RED.settings.hasOwnProperty("httpRequestTimingLog")) {
|
||||
timingLog = RED.settings.httpRequestTimingLog;
|
||||
}
|
||||
|
||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||
checkNodeAgentPatch();
|
||||
//reset redirectList on each request
|
||||
redirectList = [];
|
||||
var preRequestTimestamp = process.hrtime();
|
||||
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
|
||||
var url = nodeUrl || msg.url;
|
||||
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
|
||||
node.warn(RED._("common.errors.nooverride"));
|
||||
}
|
||||
|
||||
if (isTemplatedUrl) {
|
||||
url = mustache.render(nodeUrl,msg);
|
||||
}
|
||||
if (!url) {
|
||||
node.error(RED._("httpin.errors.no-url"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// url must start http:// or https:// so assume http:// if not set
|
||||
if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) {
|
||||
node.warn(RED._("httpin.errors.invalid-transport"));
|
||||
node.status({fill:"red",shape:"ring",text:"httpin.errors.invalid-transport"});
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
|
||||
if (tlsNode) {
|
||||
url = "https://"+url;
|
||||
} else {
|
||||
url = "http://"+url;
|
||||
}
|
||||
}
|
||||
|
||||
// The Request module used in Node-RED 1.x was tolerant of query strings that
|
||||
// were partially encoded. For example - "?a=hello%20there&b=20%"
|
||||
// The GOT module doesn't like that.
|
||||
// The following is an attempt to normalise the url to ensure it is properly
|
||||
// encoded. We cannot just encode it directly as we don't want any valid
|
||||
// encoded entity to end up doubly encoded.
|
||||
if (url.indexOf("?") > -1) {
|
||||
// Only do this if there is a query string to deal with
|
||||
const [hostPath, ...queryString] = url.split("?")
|
||||
const query = queryString.join("?");
|
||||
if (query) {
|
||||
// Look for any instance of % not followed by two hex chars.
|
||||
// Replace any we find with %25.
|
||||
const escapedQueryString = query.replace(/(%.?.?)/g, function(v) {
|
||||
if (/^%[a-f0-9]{2}/i.test(v)) {
|
||||
return v;
|
||||
}
|
||||
return v.replace(/%/,"%25")
|
||||
})
|
||||
url = hostPath+"?"+escapedQueryString;
|
||||
}
|
||||
}
|
||||
|
||||
var method = nodeMethod.toUpperCase() || "GET";
|
||||
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
|
||||
node.warn(RED._("common.errors.nooverride"));
|
||||
}
|
||||
if (msg.method && n.method && (n.method === "use")) {
|
||||
method = msg.method.toUpperCase(); // use the msg parameter
|
||||
}
|
||||
|
||||
// var isHttps = (/^https/i.test(url));
|
||||
|
||||
var opts = {};
|
||||
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
||||
// Had to remove this to get http->https redirect to work
|
||||
// opts.defaultPort = isHttps?443:80;
|
||||
opts.timeout = node.reqTimeout;
|
||||
opts.throwHttpErrors = false;
|
||||
// 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;
|
||||
opts.cookieJar = new CookieJar();
|
||||
opts.ignoreInvalidCookies = true;
|
||||
opts.forever = nodeHTTPPersistent;
|
||||
if (msg.requestTimeout !== undefined) {
|
||||
if (isNaN(msg.requestTimeout)) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnan"));
|
||||
} else if (msg.requestTimeout < 1) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnegative"));
|
||||
} else {
|
||||
opts.timeout = msg.requestTimeout;
|
||||
}
|
||||
}
|
||||
const originalHeaderMap = {};
|
||||
|
||||
opts.hooks = {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
// Whilst HTTP headers are meant to be case-insensitive,
|
||||
// in the real world, there are servers that aren't so compliant.
|
||||
// GOT will lower case all headers given a chance, so we need
|
||||
// to restore the case of any headers the user has set.
|
||||
Object.keys(options.headers).forEach(h => {
|
||||
if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
|
||||
options.headers[originalHeaderMap[h]] = options.headers[h];
|
||||
delete options.headers[h];
|
||||
}
|
||||
})
|
||||
}
|
||||
],
|
||||
beforeRedirect: [
|
||||
(options, response) => {
|
||||
let redirectInfo = {
|
||||
location: response.headers.location
|
||||
}
|
||||
if (response.headers.hasOwnProperty('set-cookie')) {
|
||||
redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
|
||||
}
|
||||
redirectList.push(redirectInfo)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var ctSet = "Content-Type"; // set default camel case
|
||||
var clSet = "Content-Length";
|
||||
if (msg.headers) {
|
||||
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
|
||||
var headerHash = msg.headers['x-node-red-request-node'];
|
||||
delete msg.headers['x-node-red-request-node'];
|
||||
var 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.hasOwnProperty('followRedirects')) {
|
||||
opts.followRedirect = !!msg.followRedirects;
|
||||
}
|
||||
|
||||
if (opts.headers.hasOwnProperty('cookie')) {
|
||||
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
|
||||
for (var name in cookies) {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url, {ignoreError: true});
|
||||
}
|
||||
delete opts.headers.cookie;
|
||||
}
|
||||
if (msg.cookies) {
|
||||
for (var name in msg.cookies) {
|
||||
if (msg.cookies.hasOwnProperty(name)) {
|
||||
if (msg.cookies[name] === null || msg.cookies[name].value === null) {
|
||||
// This case clears a cookie for HTTP In/Response nodes.
|
||||
// Ignore for this node.
|
||||
} else if (typeof msg.cookies[name] === 'object') {
|
||||
if(msg.cookies[name].encode === false){
|
||||
// If the encode option is false, the value is not encoded.
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url, {ignoreError: true});
|
||||
} else {
|
||||
// The value is encoded by encodeURIComponent().
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value), url, {ignoreError: true});
|
||||
}
|
||||
} else {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name]), url, {ignoreError: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var parsedURL = new URL(url)
|
||||
this.credentials = this.credentials || {}
|
||||
if (parsedURL.username && !this.credentials.user) {
|
||||
this.credentials.user = parsedURL.username
|
||||
}
|
||||
if (parsedURL.password && !this.credentials.password) {
|
||||
this.credentials.password = parsedURL.password
|
||||
}
|
||||
if (Object.keys(this.credentials).length != 0) {
|
||||
if (this.authType === "basic") {
|
||||
// Workaround for https://github.com/sindresorhus/got/issues/1169 (fixed in got v12)
|
||||
// var cred = ""
|
||||
if (this.credentials.user || this.credentials.password) {
|
||||
// cred = `${this.credentials.user}:${this.credentials.password}`;
|
||||
opts.headers.Authorization = "Basic " + Buffer.from(`${this.credentials.user}:${this.credentials.password}`).toString("base64");
|
||||
}
|
||||
// build own basic auth header
|
||||
// opts.headers.Authorization = "Basic " + Buffer.from(cred).toString("base64");
|
||||
} else if (this.authType === "digest") {
|
||||
let digestCreds = this.credentials;
|
||||
let sentCreds = false;
|
||||
opts.hooks.afterResponse = [(response, retry) => {
|
||||
if (response.statusCode === 401) {
|
||||
if (sentCreds) {
|
||||
return response
|
||||
}
|
||||
const requestUrl = new URL(response.request.requestUrl);
|
||||
const options = response.request.options;
|
||||
const normalisedHeaders = {};
|
||||
Object.keys(response.headers).forEach(k => {
|
||||
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||
})
|
||||
if (normalisedHeaders['www-authenticate']) {
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
||||
options.headers.Authorization = authHeader;
|
||||
}
|
||||
sentCreds = true;
|
||||
return retry(options);
|
||||
}
|
||||
return response
|
||||
}];
|
||||
} else if (this.authType === "bearer") {
|
||||
opts.headers.Authorization = `Bearer ${this.credentials.password||""}`
|
||||
}
|
||||
}
|
||||
var payload = null;
|
||||
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD' && typeof msg.payload !== "undefined") {
|
||||
if (opts.headers['content-type'] == 'multipart/form-data' && typeof msg.payload === "object") {
|
||||
let formData = new FormData();
|
||||
for (var opt in msg.payload) {
|
||||
if (msg.payload.hasOwnProperty(opt)) {
|
||||
var val = msg.payload[opt];
|
||||
if (val !== undefined && val !== null) {
|
||||
if (typeof val === 'string' || Buffer.isBuffer(val)) {
|
||||
formData.append(opt, val);
|
||||
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
|
||||
formData.append(opt,val.value,val.options || {});
|
||||
} else {
|
||||
formData.append(opt,JSON.stringify(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// GOT will only set the content-type header with the correct boundary
|
||||
// if the header isn't set. So we delete it here, for GOT to reset it.
|
||||
delete opts.headers['content-type'];
|
||||
opts.body = formData;
|
||||
} else {
|
||||
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||
payload = msg.payload;
|
||||
} else if (typeof msg.payload == "number") {
|
||||
payload = msg.payload+"";
|
||||
} else {
|
||||
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
|
||||
payload = querystring.stringify(msg.payload);
|
||||
} else {
|
||||
payload = JSON.stringify(msg.payload);
|
||||
if (opts.headers['content-type'] == null) {
|
||||
opts.headers[ctSet] = "application/json";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opts.headers['content-length'] == null) {
|
||||
if (Buffer.isBuffer(payload)) {
|
||||
opts.headers[clSet] = payload.length;
|
||||
} else {
|
||||
opts.headers[clSet] = Buffer.byteLength(payload);
|
||||
}
|
||||
}
|
||||
opts.body = payload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
|
||||
if (typeof msg.payload === "object") {
|
||||
try {
|
||||
if (url.indexOf("?") !== -1) {
|
||||
url += (url.endsWith("?")?"":"&") + querystring.stringify(msg.payload);
|
||||
} else {
|
||||
url += "?" + querystring.stringify(msg.payload);
|
||||
}
|
||||
} catch(err) {
|
||||
|
||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
} else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
|
||||
opts.allowGetBody = true;
|
||||
if (typeof msg.payload === "object") {
|
||||
opts.body = JSON.stringify(msg.payload);
|
||||
} else if (typeof msg.payload == "number") {
|
||||
opts.body = msg.payload+"";
|
||||
} else if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||
opts.body = msg.payload;
|
||||
}
|
||||
}
|
||||
|
||||
// revert to user supplied Capitalisation if needed.
|
||||
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
|
||||
opts.headers[ctSet] = opts.headers['content-type'];
|
||||
delete opts.headers['content-type'];
|
||||
}
|
||||
if (opts.headers.hasOwnProperty('content-length') && (clSet !== 'content-length')) {
|
||||
opts.headers[clSet] = opts.headers['content-length'];
|
||||
delete opts.headers['content-length'];
|
||||
}
|
||||
|
||||
var noproxy;
|
||||
if (noprox) {
|
||||
for (var i = 0; i < noprox.length; i += 1) {
|
||||
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
|
||||
}
|
||||
}
|
||||
if (prox && !noproxy) {
|
||||
var match = prox.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
|
||||
if (match) {
|
||||
let proxyAgent;
|
||||
let proxyURL = new URL(prox);
|
||||
//set username/password to null to stop empty creds header
|
||||
let proxyOptions = {
|
||||
proxy: {
|
||||
protocol: proxyURL.protocol,
|
||||
hostname: proxyURL.hostname,
|
||||
port: proxyURL.port,
|
||||
username: null,
|
||||
password: null
|
||||
},
|
||||
maxFreeSockets: 256,
|
||||
maxSockets: 256,
|
||||
keepAlive: true
|
||||
}
|
||||
if (proxyConfig && proxyConfig.credentials) {
|
||||
let proxyUsername = proxyConfig.credentials.username || '';
|
||||
let proxyPassword = proxyConfig.credentials.password || '';
|
||||
if (proxyUsername || proxyPassword) {
|
||||
proxyOptions.proxy.username = proxyUsername;
|
||||
proxyOptions.proxy.password = proxyPassword;
|
||||
}
|
||||
} else if (proxyURL.username || proxyURL.password){
|
||||
proxyOptions.proxy.username = proxyURL.username;
|
||||
proxyOptions.proxy.password = proxyURL.password;
|
||||
}
|
||||
//need both incase of http -> https redirect
|
||||
opts.agent = {
|
||||
http: new HttpProxyAgent(proxyOptions),
|
||||
https: new HttpsProxyAgent(proxyOptions)
|
||||
};
|
||||
|
||||
} else {
|
||||
node.warn("Bad proxy url: "+ prox);
|
||||
}
|
||||
}
|
||||
if (tlsNode) {
|
||||
opts.https = {};
|
||||
tlsNode.addTLSOptions(opts.https);
|
||||
if (opts.https.ca) {
|
||||
opts.https.certificateAuthority = opts.https.ca;
|
||||
delete opts.https.ca;
|
||||
}
|
||||
if (opts.https.cert) {
|
||||
opts.https.certificate = opts.https.cert;
|
||||
delete opts.https.cert;
|
||||
}
|
||||
} else {
|
||||
if (msg.hasOwnProperty('rejectUnauthorized')) {
|
||||
opts.https = { rejectUnauthorized: msg.rejectUnauthorized };
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have established all of our own headers, take a snapshot
|
||||
// of their case so we can restore it prior to the request being sent.
|
||||
if (opts.headers) {
|
||||
Object.keys(opts.headers).forEach(h => {
|
||||
originalHeaderMap[h.toLowerCase()] = h
|
||||
})
|
||||
}
|
||||
got(url,opts).then(res => {
|
||||
msg.statusCode = res.statusCode;
|
||||
msg.headers = res.headers;
|
||||
msg.responseUrl = res.url;
|
||||
msg.payload = res.body;
|
||||
msg.redirectList = redirectList;
|
||||
msg.retry = 0;
|
||||
|
||||
if (msg.headers.hasOwnProperty('set-cookie')) {
|
||||
msg.responseCookies = extractCookies(msg.headers['set-cookie']);
|
||||
}
|
||||
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
|
||||
// msg.url = url; // revert when warning above finally removed
|
||||
if (node.metric()) {
|
||||
// Calculate request time
|
||||
var diff = process.hrtime(preRequestTimestamp);
|
||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||
var metricRequestDurationMillis = ms.toFixed(3);
|
||||
node.metric("duration.millis", msg, metricRequestDurationMillis);
|
||||
if (res.client && res.client.bytesRead) {
|
||||
node.metric("size.bytes", msg, res.client.bytesRead);
|
||||
}
|
||||
if (timingLog) {
|
||||
emitTimingMetricLog(res.timings, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the payload to the required return type
|
||||
if (node.ret !== "bin") {
|
||||
msg.payload = msg.payload.toString('utf8'); // txt
|
||||
|
||||
if (node.ret === "obj") {
|
||||
try { msg.payload = JSON.parse(msg.payload); } // obj
|
||||
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
||||
}
|
||||
}
|
||||
node.status({});
|
||||
nodeSend(msg);
|
||||
nodeDone();
|
||||
}).catch(err => {
|
||||
// Pre 2.1, any errors would be sent to both Catch node and sent on as normal.
|
||||
// This is not ideal but is the legacy behaviour of the node.
|
||||
// 2.1 adds the 'senderr' option, if set to true, will *only* send errors
|
||||
// to Catch nodes. If false, it still does both behaviours.
|
||||
// TODO: 3.0 - make it one or the other.
|
||||
|
||||
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
||||
node.error(RED._("common.notification.errors.no-response"), msg);
|
||||
node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
|
||||
} else {
|
||||
node.error(err,msg);
|
||||
node.status({fill:"red", shape:"ring", text:err.code});
|
||||
}
|
||||
msg.payload = err.toString() + " : " + url;
|
||||
msg.statusCode = err.code || (err.response?err.response.statusCode:undefined);
|
||||
if (node.metric() && timingLog) {
|
||||
emitTimingMetricLog(err.timings, msg);
|
||||
}
|
||||
if (!sendErrorsToCatch) {
|
||||
nodeSend(msg);
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
});
|
||||
|
||||
this.on("close",function() {
|
||||
node.status({});
|
||||
});
|
||||
|
||||
function emitTimingMetricLog(timings, msg) {
|
||||
const props = [
|
||||
"start",
|
||||
"socket",
|
||||
"lookup",
|
||||
"connect",
|
||||
"secureConnect",
|
||||
"upload",
|
||||
"response",
|
||||
"end",
|
||||
"error",
|
||||
"abort"
|
||||
];
|
||||
if (timings) {
|
||||
props.forEach(p => {
|
||||
if (timings[p]) {
|
||||
node.metric(`timings.${p}`, msg, timings[p]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function extractCookies(setCookie) {
|
||||
var cookies = {};
|
||||
setCookie.forEach(function(c) {
|
||||
var parsedCookie = cookie.parse(c);
|
||||
var eq_idx = c.indexOf('=');
|
||||
var key = c.substr(0, eq_idx).trim()
|
||||
parsedCookie.value = parsedCookie[key];
|
||||
delete parsedCookie[key];
|
||||
cookies[key] = parsedCookie;
|
||||
});
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("http request",HTTPRequest,{
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
}
|
||||
});
|
||||
|
||||
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
||||
|
||||
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
||||
/**
|
||||
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
||||
*
|
||||
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
||||
* HA1=MD5(username:realm:password)
|
||||
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
||||
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
||||
*/
|
||||
var ha1 = md5(user + ':' + realm + ':' + pass)
|
||||
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
||||
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
||||
} else {
|
||||
return ha1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||
var challenge = {}
|
||||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||
for (;;) {
|
||||
var match = re.exec(authHeader)
|
||||
if (!match) {
|
||||
break
|
||||
}
|
||||
challenge[match[1]] = match[2] || match[3]
|
||||
}
|
||||
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
||||
var nc = qop && '00000001'
|
||||
var cnonce = qop && uuid().replace(/-/g, '')
|
||||
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
||||
var ha2 = md5(method + ':' + path)
|
||||
var digestResponse = qop
|
||||
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||
var authValues = {
|
||||
username: user,
|
||||
realm: challenge.realm,
|
||||
nonce: challenge.nonce,
|
||||
uri: path,
|
||||
qop: qop,
|
||||
response: digestResponse,
|
||||
nc: nc,
|
||||
cnonce: cnonce,
|
||||
algorithm: challenge.algorithm,
|
||||
opaque: challenge.opaque
|
||||
}
|
||||
|
||||
authHeader = []
|
||||
for (var k in authValues) {
|
||||
if (authValues[k]) {
|
||||
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
|
||||
authHeader.push(k + '=' + authValues[k])
|
||||
} else {
|
||||
authHeader.push(k + '="' + authValues[k] + '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
authHeader = 'Digest ' + authHeader.join(', ')
|
||||
return authHeader
|
||||
}
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- WebSocket Input Node -->
|
||||
<script type="text/html" data-template-name="websocket in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
|
||||
<select id="node-input-mode">
|
||||
<option value="server" data-i18n="websocket.listenon"></option>
|
||||
<option value="client" data-i18n="websocket.connectto"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="websocket-server-row">
|
||||
<label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row" id="websocket-client-row">
|
||||
<label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
|
||||
<input type="text" id="node-input-client">
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function() {
|
||||
|
||||
function ws_oneditprepare() {
|
||||
$("#websocket-client-row").hide();
|
||||
$("#node-input-mode").on("change", function() {
|
||||
if ( $("#node-input-mode").val() === 'client') {
|
||||
$("#websocket-server-row").hide();
|
||||
$("#websocket-client-row").show();
|
||||
}
|
||||
else {
|
||||
$("#websocket-server-row").show();
|
||||
$("#websocket-client-row").hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.client) {
|
||||
$("#node-input-mode").val('client').change();
|
||||
}
|
||||
else {
|
||||
$("#node-input-mode").val('server').change();
|
||||
}
|
||||
}
|
||||
|
||||
function ws_oneditsave() {
|
||||
if ($("#node-input-mode").val() === 'client') {
|
||||
$("#node-input-server").append('<option value="">Dummy</option>');
|
||||
$("#node-input-server").val('');
|
||||
}
|
||||
else {
|
||||
$("#node-input-client").append('<option value="">Dummy</option>');
|
||||
$("#node-input-client").val('');
|
||||
}
|
||||
}
|
||||
|
||||
function ws_label() {
|
||||
var nodeid = (this.client)?this.client:this.server;
|
||||
var wsNode = RED.nodes.node(nodeid);
|
||||
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
|
||||
}
|
||||
|
||||
function ws_validateserver() {
|
||||
if ($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return RED.nodes.node(this.server) != null;
|
||||
}
|
||||
}
|
||||
|
||||
function ws_validateclient() {
|
||||
if ($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return RED.nodes.node(this.client) != null;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType('websocket in',{
|
||||
category: 'network',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.svg",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket out',{
|
||||
category: 'network',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "white-globe.svg",
|
||||
align: "right",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket-listener',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.path) {
|
||||
if (this.path.charAt(0) == "/") {
|
||||
root += this.path.slice(1);
|
||||
} else {
|
||||
root += this.path;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root === "") {
|
||||
$("#node-config-ws-tip").hide();
|
||||
} else {
|
||||
$("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
|
||||
$("#node-config-ws-tip").show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket-client',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
tls: {type:"tls-config",required: false},
|
||||
wholemsg: {value:"false"},
|
||||
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
return this.path;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-config-input-path").on("change keyup paste",function() {
|
||||
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
|
||||
});
|
||||
$("#node-config-input-path").change();
|
||||
|
||||
var heartbeatActive = (this.hb && this.hb != "0");
|
||||
$("#node-config-input-hb-cb").prop("checked",heartbeatActive);
|
||||
$("#node-config-input-hb-cb").on("change", function(evt) {
|
||||
$("#node-config-input-hb-row").toggle(this.checked);
|
||||
})
|
||||
$("#node-config-input-hb-cb").trigger("change");
|
||||
if (!heartbeatActive) {
|
||||
$("#node-config-input-hb").val("");
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!/^wss:/i.test($("#node-config-input-path").val())) {
|
||||
$("#node-config-input-tls").val("_ADD_");
|
||||
}
|
||||
if (!$("#node-config-input-hb-cb").prop("checked")) {
|
||||
$("#node-config-input-hb").val("0");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- WebSocket out Node -->
|
||||
<script type="text/html" data-template-name="websocket out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
|
||||
<select id="node-input-mode">
|
||||
<option value="server" data-i18n="websocket.listenon"></option>
|
||||
<option value="client" data-i18n="websocket.connectto"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="websocket-server-row">
|
||||
<label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row" id="websocket-client-row">
|
||||
<label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
|
||||
<input type="text" id="node-input-client">
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<!-- WebSocket Server configuration node -->
|
||||
<script type="text/html" data-template-name="websocket-listener">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
|
||||
<input id="node-config-input-path" type="text" placeholder="/ws/example">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
|
||||
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
|
||||
<option value="false" data-i18n="websocket.payload"></option>
|
||||
<option value="true" data-i18n="websocket.message"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<span data-i18n="[html]websocket.tip.path1"></span>
|
||||
<p id="node-config-ws-tip"><span id="node-config-ws-path"></span></p>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- WebSocket Client configuration node -->
|
||||
<script type="text/html" data-template-name="websocket-client">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
|
||||
<input id="node-config-input-path" type="text" placeholder="ws://example.com/ws">
|
||||
</div>
|
||||
<div class="form-row node-config-row-tls hide">
|
||||
<label for="node-config-input-tls" data-i18n="httpin.tls-config"></label>
|
||||
<input type="text" id="node-config-input-tls">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
|
||||
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
|
||||
<option value="false" data-i18n="websocket.payload"></option>
|
||||
<option value="true" data-i18n="websocket.message"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="display: flex; align-items: center; min-height: 34px">
|
||||
<label for="node-config-input-hb-cb" data-i18n="websocket.sendheartbeat"></label>
|
||||
<input type="checkbox" style="margin: 0 8px; width:auto" id="node-config-input-hb-cb">
|
||||
<span id="node-config-input-hb-row" class="hide" >
|
||||
<input type="text" style="width: 70px; margin-right: 3px" id="node-config-input-hb">
|
||||
<span data-i18n="inject.seconds"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
|
||||
<span data-i18n="[html]websocket.tip.url2"></span>
|
||||
</div>
|
||||
</script>
|
@ -1,415 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ws = require("ws");
|
||||
var inspect = require("util").inspect;
|
||||
var url = require("url");
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
|
||||
var serverUpgradeAdded = false;
|
||||
function handleServerUpgrade(request, socket, head) {
|
||||
const pathname = url.parse(request.url).pathname;
|
||||
if (listenerNodes.hasOwnProperty(pathname)) {
|
||||
listenerNodes[pathname].server.handleUpgrade(request, socket, head, function done(ws) {
|
||||
listenerNodes[pathname].server.emit('connection', ws, request);
|
||||
});
|
||||
} else {
|
||||
// Don't destroy the socket as other listeners may want to handle the
|
||||
// event.
|
||||
}
|
||||
}
|
||||
var listenerNodes = {};
|
||||
var activeListenerNodes = 0;
|
||||
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
node._clients = {};
|
||||
// match absolute url
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
node.closing = false;
|
||||
node.tls = n.tls;
|
||||
|
||||
if (n.hb) {
|
||||
var heartbeat = parseInt(n.hb);
|
||||
if (heartbeat > 0) {
|
||||
node.heartbeat = heartbeat * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
function startconn() { // Connect to remote endpoint
|
||||
node.tout = null;
|
||||
var prox, noprox;
|
||||
if (process.env.http_proxy) { prox = process.env.http_proxy; }
|
||||
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
|
||||
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
|
||||
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
|
||||
|
||||
var noproxy = false;
|
||||
if (noprox) {
|
||||
for (var i in noprox) {
|
||||
if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
|
||||
}
|
||||
}
|
||||
|
||||
var agent = undefined;
|
||||
if (prox && !noproxy) {
|
||||
agent = new HttpsProxyAgent(prox);
|
||||
}
|
||||
|
||||
var options = {};
|
||||
if (agent) {
|
||||
options.agent = agent;
|
||||
}
|
||||
if (node.tls) {
|
||||
var tlsNode = RED.nodes.getNode(node.tls);
|
||||
if (tlsNode) {
|
||||
tlsNode.addTLSOptions(options);
|
||||
}
|
||||
}
|
||||
var socket = new ws(node.path,options);
|
||||
socket.setMaxListeners(0);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
}
|
||||
|
||||
function handleConnection(/*socket*/socket) {
|
||||
var id = RED.util.generateId();
|
||||
socket.nrId = id;
|
||||
socket.nrPendingHeartbeat = false;
|
||||
if (node.isServer) {
|
||||
node._clients[id] = socket;
|
||||
node.emit('opened',{count:Object.keys(node._clients).length,id:id});
|
||||
} else {
|
||||
if (node.heartbeat) {
|
||||
node.heartbeatInterval = setInterval(function() {
|
||||
if (socket.nrPendingHeartbeat) {
|
||||
// No pong received
|
||||
socket.terminate();
|
||||
socket.nrErrorHandler(new Error("timeout"));
|
||||
return;
|
||||
}
|
||||
socket.nrPendingHeartbeat = true;
|
||||
socket.ping();
|
||||
},node.heartbeat);
|
||||
}
|
||||
}
|
||||
socket.on('open',function() {
|
||||
if (!node.isServer) {
|
||||
node.emit('opened',{count:'',id:id});
|
||||
}
|
||||
});
|
||||
socket.on('close',function() {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
if (node.isServer) {
|
||||
delete node._clients[id];
|
||||
node.emit('closed',{count:Object.keys(node._clients).length,id:id});
|
||||
} else {
|
||||
node.emit('closed',{count:'',id:id});
|
||||
}
|
||||
if (!node.closing && !node.isServer) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
});
|
||||
socket.on('message',function(data,flags) {
|
||||
node.handleEvent(id,socket,'message',data,flags);
|
||||
});
|
||||
socket.nrErrorHandler = function(err) {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
node.emit('erro',{err:err,id:id});
|
||||
if (!node.closing && !node.isServer) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
}
|
||||
socket.on('error',socket.nrErrorHandler);
|
||||
socket.on('ping', function() {
|
||||
socket.nrPendingHeartbeat = false;
|
||||
})
|
||||
socket.on('pong', function() {
|
||||
socket.nrPendingHeartbeat = false;
|
||||
})
|
||||
}
|
||||
|
||||
if (node.isServer) {
|
||||
activeListenerNodes++;
|
||||
if (!serverUpgradeAdded) {
|
||||
RED.server.on('upgrade', handleServerUpgrade);
|
||||
serverUpgradeAdded = true
|
||||
}
|
||||
|
||||
var path = RED.settings.httpNodeRoot || "/";
|
||||
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
|
||||
node.fullPath = path;
|
||||
|
||||
if (listenerNodes.hasOwnProperty(path)) {
|
||||
node.error(RED._("websocket.errors.duplicate-path",{path: node.path}));
|
||||
return;
|
||||
}
|
||||
listenerNodes[node.fullPath] = node;
|
||||
var serverOptions = {
|
||||
noServer: true
|
||||
}
|
||||
if (RED.settings.webSocketNodeVerifyClient) {
|
||||
serverOptions.verifyClient = RED.settings.webSocketNodeVerifyClient;
|
||||
}
|
||||
// Create a WebSocket Server
|
||||
node.server = new ws.Server(serverOptions);
|
||||
node.server.setMaxListeners(0);
|
||||
node.server.on('connection', handleConnection);
|
||||
// Not adding server-initiated heartbeats yet
|
||||
// node.heartbeatInterval = setInterval(function() {
|
||||
// node.server.clients.forEach(function(ws) {
|
||||
// if (ws.nrPendingHeartbeat) {
|
||||
// // No pong received
|
||||
// ws.terminate();
|
||||
// ws.nrErrorHandler(new Error("timeout"));
|
||||
// return;
|
||||
// }
|
||||
// ws.nrPendingHeartbeat = true;
|
||||
// ws.ping();
|
||||
// });
|
||||
// })
|
||||
}
|
||||
else {
|
||||
node.closing = false;
|
||||
startconn(); // start outbound connection
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
if (node.heartbeatInterval) {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
}
|
||||
if (node.isServer) {
|
||||
delete listenerNodes[node.fullPath];
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
activeListenerNodes--;
|
||||
// if (activeListenerNodes === 0 && serverUpgradeAdded) {
|
||||
// RED.server.removeListener('upgrade', handleServerUpgrade);
|
||||
// serverUpgradeAdded = false;
|
||||
// }
|
||||
}
|
||||
else {
|
||||
node.closing = true;
|
||||
node.server.close();
|
||||
if (node.tout) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
|
||||
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
|
||||
|
||||
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.push(handler);
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.removeInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.forEach(function(node, i, inputNodes) {
|
||||
if (node === handler) {
|
||||
inputNodes.splice(i, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags) {
|
||||
var msg;
|
||||
if (this.wholemsg) {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
if (typeof msg !== "object" && !Array.isArray(msg) && (msg !== null)) {
|
||||
msg = { payload:msg };
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
msg = { payload:data };
|
||||
}
|
||||
} else {
|
||||
msg = {
|
||||
payload:data
|
||||
};
|
||||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.broadcast = function(data) {
|
||||
if (this.isServer) {
|
||||
for (let client in this._clients) {
|
||||
if (this._clients.hasOwnProperty(client)) {
|
||||
try {
|
||||
this._clients[client].send(data);
|
||||
} catch(err) {
|
||||
this.warn(RED._("websocket.errors.send-error")+" "+client+" "+err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
this.server.send(data);
|
||||
} catch(err) {
|
||||
this.warn(RED._("websocket.errors.send-error")+" "+err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.reply = function(id,data) {
|
||||
var session = this._clients[id];
|
||||
if (session) {
|
||||
try {
|
||||
session.send(data);
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function WebSocketInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
var node = this;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(event) {
|
||||
node.status({
|
||||
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
|
||||
event:"connect",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('erro', function(event) {
|
||||
node.status({
|
||||
fill:"red",shape:"ring",text:"common.status.error",
|
||||
event:"error",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('closed', function(event) {
|
||||
var status;
|
||||
if (event.count > 0) {
|
||||
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
|
||||
} else {
|
||||
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
|
||||
}
|
||||
status.event = "disconnect";
|
||||
status._session = {type:"websocket",id:event.id}
|
||||
node.status(status);
|
||||
});
|
||||
} else {
|
||||
this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (node.serverConfig) {
|
||||
node.serverConfig.removeInputNode(node);
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket in",WebSocketInNode);
|
||||
|
||||
function WebSocketOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (!this.serverConfig) {
|
||||
return this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
else {
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(event) {
|
||||
node.status({
|
||||
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
|
||||
event:"connect",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('erro', function(event) {
|
||||
node.status({
|
||||
fill:"red",shape:"ring",text:"common.status.error",
|
||||
event:"error",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
})
|
||||
});
|
||||
this.serverConfig.on('closed', function(event) {
|
||||
var status;
|
||||
if (event.count > 0) {
|
||||
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
|
||||
} else {
|
||||
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
|
||||
}
|
||||
status.event = "disconnect";
|
||||
status._session = {type:"websocket",id:event.id}
|
||||
node.status(status);
|
||||
});
|
||||
}
|
||||
this.on("input", function(msg, nodeSend, nodeDone) {
|
||||
var payload;
|
||||
if (this.serverConfig.wholemsg) {
|
||||
var sess;
|
||||
if (msg._session) { sess = JSON.stringify(msg._session); }
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
if (sess) { msg._session = JSON.parse(sess); }
|
||||
}
|
||||
else if (msg.hasOwnProperty("payload")) {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
else {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (payload) {
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error) {
|
||||
if (!!error) {
|
||||
node.warn(RED._("websocket.errors.send-error")+inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
this.on('close', function() {
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="tcp in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
|
||||
<select id="node-input-server" style="width:120px; margin-right:5px;">
|
||||
<option value="server" data-i18n="tcpin.type.listen"></option>
|
||||
<option value="client" data-i18n="tcpin.type.connect"></option>
|
||||
</select>
|
||||
<span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width:65px">
|
||||
</div>
|
||||
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
|
||||
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
|
||||
<select id="node-input-datamode" style="width:110px;">
|
||||
<option value="stream" data-i18n="tcpin.output.stream"></option>
|
||||
<option value="single" data-i18n="tcpin.output.single"></option>
|
||||
</select>
|
||||
<select id="node-input-datatype" style="width:140px;">
|
||||
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="tcpin.output.string"></option>
|
||||
<option value="base64" data-i18n="tcpin.output.base64"></option>
|
||||
</select>
|
||||
<span data-i18n="tcpin.label.payload"></span>
|
||||
</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;">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
|
||||
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp in',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
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()},
|
||||
datamode:{value:"stream"},
|
||||
datatype:{value:"buffer"},
|
||||
newline:{value:""},
|
||||
topic: {value:""},
|
||||
base64: {/*deprecated*/ value:false,required:true}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "bridge-dash.svg",
|
||||
label: function() {
|
||||
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var updateOptions = function() {
|
||||
var sockettype = $("#node-input-server").val();
|
||||
if (sockettype == "client") {
|
||||
$("#node-input-host-row").show();
|
||||
} else {
|
||||
$("#node-input-host-row").hide();
|
||||
}
|
||||
var datamode = $("#node-input-datamode").val();
|
||||
var datatype = $("#node-input-datatype").val();
|
||||
if (datamode == "stream") {
|
||||
if (datatype == "utf8") {
|
||||
$("#node-row-newline").show();
|
||||
} else {
|
||||
$("#node-row-newline").hide();
|
||||
}
|
||||
} else {
|
||||
$("#node-row-newline").hide();
|
||||
}
|
||||
};
|
||||
updateOptions();
|
||||
$("#node-input-server").change(updateOptions);
|
||||
$("#node-input-datatype").change(updateOptions);
|
||||
$("#node-input-datamode").change(updateOptions);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" data-template-name="tcp out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
|
||||
<select id="node-input-beserver" style="width:150px; margin-right:5px;">
|
||||
<option value="server" data-i18n="tcpin.type.listen"></option>
|
||||
<option value="client" data-i18n="tcpin.type.connect"></option>
|
||||
<option value="reply" data-i18n="tcpin.type.reply"></option>
|
||||
</select>
|
||||
<span id="node-input-port-row"><span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 65px"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
|
||||
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
|
||||
</div>
|
||||
|
||||
<div class="form-row hidden" id="node-input-end-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-end" style="width: 70%;"><span data-i18n="tcpin.label.close-connection"></span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-base64" style="width: 70%;"><span data-i18n="tcpin.label.decode-base64"></span></label>
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp out',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
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); } },
|
||||
beserver: {value:"client",required:true},
|
||||
base64: {value:false,required:true},
|
||||
end: {value:false,required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge-dash.svg",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return (this.name)?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var updateOptions = function() {
|
||||
var sockettype = $("#node-input-beserver").val();
|
||||
if (sockettype == "reply") {
|
||||
$("#node-input-port-row").hide();
|
||||
$("#node-input-host-row").hide();
|
||||
$("#node-input-end-row").hide();
|
||||
} else if (sockettype == "client"){
|
||||
$("#node-input-port-row").show();
|
||||
$("#node-input-host-row").show();
|
||||
$("#node-input-end-row").show();
|
||||
} else {
|
||||
$("#node-input-port-row").show();
|
||||
$("#node-input-host-row").hide();
|
||||
$("#node-input-end-row").show();
|
||||
}
|
||||
};
|
||||
updateOptions();
|
||||
$("#node-input-beserver").change(updateOptions);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" data-template-name="tcp request">
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label>
|
||||
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
|
||||
<span data-i18n="tcpin.label.port"></span>
|
||||
<input type="text" id="node-input-port" style="width:60px">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
|
||||
<select type="text" id="node-input-ret" style="width:54%;">
|
||||
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
|
||||
<option value="string" data-i18n="tcpin.output.string"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-out"> </label>
|
||||
<select type="text" id="node-input-out" style="width:54%;">
|
||||
<option value="time" data-i18n="tcpin.return.timeout"></option>
|
||||
<option value="char" data-i18n="tcpin.return.character"></option>
|
||||
<option value="count" data-i18n="tcpin.return.number"></option>
|
||||
<option value="sit" data-i18n="tcpin.return.never"></option>
|
||||
<option value="immed" data-i18n="tcpin.return.immed"></option>
|
||||
</select>
|
||||
<input type="text" id="node-input-splitc" style="width:50px;">
|
||||
<span id="node-units"></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>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp request',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
server: {value:""},
|
||||
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
|
||||
out: {value:"time",required:true},
|
||||
ret: {value:"buffer"},
|
||||
splitc: {value:"0",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "bridge-dash.svg",
|
||||
label: function() {
|
||||
return this.name || "tcp:"+(this.server?this.server+":":"")+this.port;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var previous = null;
|
||||
if ($("#node-input-ret").val() == undefined) {
|
||||
$("#node-input-ret").val("buffer");
|
||||
this.ret = "buffer";
|
||||
}
|
||||
$("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
|
||||
$("#node-input-splitc").show();
|
||||
if (previous === null) { previous = $("#node-input-out").val(); }
|
||||
if ($("#node-input-out").val() == "char") {
|
||||
if (previous != "char") { $("#node-input-splitc").val("\\n"); }
|
||||
$("#node-units").text("");
|
||||
}
|
||||
else if ($("#node-input-out").val() == "time") {
|
||||
if (previous != "time") { $("#node-input-splitc").val("0"); }
|
||||
$("#node-units").text(RED._("node-red:tcpin.label.ms"));
|
||||
}
|
||||
else if ($("#node-input-out").val() == "immed") {
|
||||
if (previous != "immed") { $("#node-input-splitc").val(" "); }
|
||||
$("#node-units").text("");
|
||||
$("#node-input-splitc").hide();
|
||||
}
|
||||
else if ($("#node-input-out").val() == "count") {
|
||||
if (previous != "count") { $("#node-input-splitc").val("12"); }
|
||||
$("#node-units").text(RED._("node-red:tcpin.label.chars"));
|
||||
}
|
||||
else {
|
||||
if (previous != "sit") { $("#node-input-splitc").val(" "); }
|
||||
$("#node-units").text("");
|
||||
$("#node-input-splitc").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,719 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var reconnectTime = RED.settings.socketReconnectTime||10000;
|
||||
var socketTimeout = RED.settings.socketTimeout||null;
|
||||
const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
|
||||
const Denque = require('denque');
|
||||
var net = require('net');
|
||||
|
||||
var connectionPool = {};
|
||||
|
||||
/**
|
||||
* Enqueue `item` in `queue`
|
||||
* @param {Denque} queue - Queue
|
||||
* @param {*} item - Item to enqueue
|
||||
* @private
|
||||
* @returns {Denque} `queue`
|
||||
*/
|
||||
const enqueue = (queue, item) => {
|
||||
// drop msgs from front of queue if size is going to be exceeded
|
||||
if (queue.length === msgQueueSize) { queue.shift(); }
|
||||
queue.push(item);
|
||||
return queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shifts item off front of queue
|
||||
* @param {Deque} queue - Queue
|
||||
* @private
|
||||
* @returns {*} Item previously at front of queue
|
||||
*/
|
||||
const dequeue = queue => queue.shift();
|
||||
|
||||
function TcpIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.host = n.host;
|
||||
this.port = n.port * 1;
|
||||
this.topic = n.topic;
|
||||
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
|
||||
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
|
||||
this.base64 = n.base64;
|
||||
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
var count = 0;
|
||||
|
||||
if (!node.server) {
|
||||
var buffer = null;
|
||||
var client;
|
||||
var reconnectTimeout;
|
||||
var end = false;
|
||||
var setupTcpClient = function() {
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
var id = RED.util.generateId();
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
|
||||
});
|
||||
client.setKeepAlive(true,120000);
|
||||
connectionPool[id] = client;
|
||||
|
||||
client.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((node.datatype) === "utf8" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i]};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
} else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
client.on('end', function() {
|
||||
if (!node.stream || (node.datatype == "utf8" && node.newline !== "" && buffer.length > 0)) {
|
||||
var msg = {topic:node.topic, payload:buffer};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
if (buffer.length !== 0) {
|
||||
end = true; // only ask for fast re-connect if we actually got something
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = null;
|
||||
}
|
||||
});
|
||||
client.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
node.connected = false;
|
||||
node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}});
|
||||
if (!node.closing) {
|
||||
if (end) { // if we were asked to close then try to reconnect once very quick.
|
||||
end = false;
|
||||
reconnectTimeout = setTimeout(setupTcpClient, 20);
|
||||
}
|
||||
else {
|
||||
node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
|
||||
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
|
||||
}
|
||||
} else {
|
||||
if (node.doneClose) { node.doneClose(); }
|
||||
}
|
||||
});
|
||||
client.on('error', function(err) {
|
||||
node.log(err);
|
||||
});
|
||||
}
|
||||
setupTcpClient();
|
||||
|
||||
this.on('close', function(done) {
|
||||
node.doneClose = done;
|
||||
this.closing = true;
|
||||
if (client) { client.destroy(); }
|
||||
clearTimeout(reconnectTimeout);
|
||||
if (!node.connected) { done(); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.setKeepAlive(true,120000);
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
var id = RED.util.generateId();
|
||||
var fromi;
|
||||
var fromp;
|
||||
connectionPool[id] = socket;
|
||||
count++;
|
||||
node.status({
|
||||
text:RED._("tcpin.status.connections",{count:count}),
|
||||
event:"connect",
|
||||
ip:socket.remoteAddress,
|
||||
port:socket.remotePort,
|
||||
_session: {type:"tcp",id:id}
|
||||
});
|
||||
|
||||
var buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
socket.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((typeof data) === "string" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
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};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data, ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
fromi = socket.remoteAddress;
|
||||
fromp = socket.remotePort;
|
||||
}
|
||||
});
|
||||
socket.on('end', function() {
|
||||
if (!node.stream || (node.datatype === "utf8" && node.newline !== "") || (node.datatype === "base64")) {
|
||||
if (buffer.length > 0) {
|
||||
var msg = {topic:node.topic, payload:buffer, ip:fromi, port:fromp};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = null;
|
||||
}
|
||||
});
|
||||
socket.on('timeout', function() {
|
||||
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
|
||||
socket.end();
|
||||
});
|
||||
socket.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
count--;
|
||||
node.status({
|
||||
text:RED._("tcpin.status.connections",{count:count}),
|
||||
event:"disconnect",
|
||||
ip:socket.remoteAddress,
|
||||
port:socket.remotePort,
|
||||
_session: {type:"tcp",id:id}
|
||||
|
||||
});
|
||||
});
|
||||
socket.on('error',function(err) {
|
||||
node.log(err);
|
||||
});
|
||||
});
|
||||
|
||||
server.on('error', function(err) {
|
||||
if (err) {
|
||||
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(node.port, function(err) {
|
||||
if (err) {
|
||||
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
|
||||
} else {
|
||||
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
|
||||
node.on('close', function() {
|
||||
for (var c in connectionPool) {
|
||||
if (connectionPool.hasOwnProperty(c)) {
|
||||
connectionPool[c].end();
|
||||
connectionPool[c].unref();
|
||||
}
|
||||
}
|
||||
node.closing = true;
|
||||
server.close();
|
||||
node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("tcp in",TcpIn);
|
||||
|
||||
|
||||
function TcpOut(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.host = n.host;
|
||||
this.port = n.port * 1;
|
||||
this.base64 = n.base64;
|
||||
this.doend = n.end || false;
|
||||
this.beserver = n.beserver;
|
||||
this.name = n.name;
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
|
||||
if (!node.beserver||node.beserver=="client") {
|
||||
var reconnectTimeout;
|
||||
var client = null;
|
||||
var end = false;
|
||||
|
||||
var setupTcpClient = function() {
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
client.setKeepAlive(true,120000);
|
||||
client.on('error', function (err) {
|
||||
node.log(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
});
|
||||
client.on('end', function (err) {
|
||||
node.status({});
|
||||
node.connected = false;
|
||||
});
|
||||
client.on('close', function() {
|
||||
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
|
||||
node.connected = false;
|
||||
client.destroy();
|
||||
if (!node.closing) {
|
||||
if (end) {
|
||||
end = false;
|
||||
reconnectTimeout = setTimeout(setupTcpClient,20);
|
||||
}
|
||||
else {
|
||||
node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
|
||||
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
|
||||
}
|
||||
} else {
|
||||
if (node.doneClose) { node.doneClose(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
setupTcpClient();
|
||||
|
||||
node.on("input", function(msg, nodeSend, nodeDone) {
|
||||
if (node.connected && msg.payload != null) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
client.write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
client.write(Buffer.from(msg.payload,'base64'));
|
||||
} else {
|
||||
client.write(Buffer.from(""+msg.payload));
|
||||
}
|
||||
if (node.doend === true) {
|
||||
end = true;
|
||||
if (client) { node.status({}); client.destroy(); }
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.doneClose = done;
|
||||
this.closing = true;
|
||||
if (client) { client.destroy(); }
|
||||
clearTimeout(reconnectTimeout);
|
||||
if (!node.connected) { done(); }
|
||||
});
|
||||
|
||||
}
|
||||
else if (node.beserver == "reply") {
|
||||
node.on("input",function(msg, nodeSend, nodeDone) {
|
||||
if (msg._session && msg._session.type == "tcp") {
|
||||
var client = connectionPool[msg._session.id];
|
||||
if (client) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
client.write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
client.write(Buffer.from(msg.payload,'base64'));
|
||||
} else {
|
||||
client.write(Buffer.from(""+msg.payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i in connectionPool) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
connectionPool[i].write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
connectionPool[i].write(Buffer.from(msg.payload,'base64'));
|
||||
} else {
|
||||
connectionPool[i].write(Buffer.from(""+msg.payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
}
|
||||
else {
|
||||
var connectedSockets = [];
|
||||
node.status({text:RED._("tcpin.status.connections",{count:0})});
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.setKeepAlive(true,120000);
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
|
||||
socket.on('timeout', function() {
|
||||
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
|
||||
socket.end();
|
||||
});
|
||||
socket.on('data', function(d) {
|
||||
// console.log("DATA",d)
|
||||
});
|
||||
socket.on('close',function() {
|
||||
node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
|
||||
connectedSockets.splice(connectedSockets.indexOf(socket),1);
|
||||
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
|
||||
});
|
||||
socket.on('error',function() {
|
||||
node.log(RED._("tcpin.errors.socket-error",{host:socket.remoteAddress, port:socket.remotePort}));
|
||||
connectedSockets.splice(connectedSockets.indexOf(socket),1);
|
||||
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
|
||||
});
|
||||
connectedSockets.push(socket);
|
||||
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
|
||||
});
|
||||
|
||||
node.on("input", function(msg, nodeSend, nodeDone) {
|
||||
if (msg.payload != null) {
|
||||
var buffer;
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
buffer = msg.payload;
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
buffer = Buffer.from(msg.payload,'base64');
|
||||
} else {
|
||||
buffer = Buffer.from(""+msg.payload);
|
||||
}
|
||||
for (var i = 0; i < connectedSockets.length; i += 1) {
|
||||
if (node.doend === true) { connectedSockets[i].end(buffer); }
|
||||
else { connectedSockets[i].write(buffer); }
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
|
||||
server.on('error', function(err) {
|
||||
if (err) {
|
||||
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(node.port, function(err) {
|
||||
if (err) {
|
||||
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
|
||||
} else {
|
||||
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
|
||||
node.on('close', function() {
|
||||
for (var c in connectedSockets) {
|
||||
if (connectedSockets.hasOwnProperty(c)) {
|
||||
connectedSockets[c].end();
|
||||
connectedSockets[c].unref();
|
||||
}
|
||||
}
|
||||
server.close();
|
||||
node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("tcp out",TcpOut);
|
||||
|
||||
|
||||
function TcpGet(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.port = Number(n.port);
|
||||
this.out = n.out;
|
||||
this.ret = n.ret || "buffer";
|
||||
this.splitc = n.splitc;
|
||||
|
||||
if (this.out === "immed") { this.splitc = -1; this.out = "time"; }
|
||||
if (this.out !== "char") { this.splitc = Number(this.splitc); }
|
||||
else {
|
||||
if (this.splitc[0] == '\\') {
|
||||
this.splitc = parseInt(this.splitc.replace("\\n",0x0A).replace("\\r",0x0D).replace("\\t",0x09).replace("\\e",0x1B).replace("\\f",0x0C).replace("\\0",0x00));
|
||||
} // jshint ignore:line
|
||||
if (typeof this.splitc == "string") {
|
||||
if (this.splitc.substr(0,2) == "0x") {
|
||||
this.splitc = parseInt(this.splitc);
|
||||
}
|
||||
else {
|
||||
this.splitc = this.splitc.charCodeAt(0);
|
||||
}
|
||||
} // jshint ignore:line
|
||||
}
|
||||
|
||||
var node = this;
|
||||
|
||||
var clients = {};
|
||||
|
||||
this.on("input", function(msg, nodeSend, nodeDone) {
|
||||
var i = 0;
|
||||
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
|
||||
msg.payload = msg.payload.toString();
|
||||
}
|
||||
|
||||
var host = node.server || msg.host;
|
||||
var port = node.port || msg.port;
|
||||
|
||||
// Store client information independently
|
||||
// the clients object will have:
|
||||
// clients[id].client, clients[id].msg, clients[id].timeout
|
||||
var connection_id = host + ":" + port;
|
||||
if (connection_id !== node.last_id) {
|
||||
node.status({});
|
||||
node.last_id = connection_id;
|
||||
}
|
||||
clients[connection_id] = clients[connection_id] || {
|
||||
msgQueue: new Denque(),
|
||||
connected: false,
|
||||
connecting: false
|
||||
};
|
||||
enqueue(clients[connection_id].msgQueue, {msg:msg, nodeSend:nodeSend, nodeDone:nodeDone});
|
||||
clients[connection_id].lastMsg = msg;
|
||||
|
||||
if (!clients[connection_id].connecting && !clients[connection_id].connected) {
|
||||
var buf;
|
||||
if (this.out == "count") {
|
||||
if (this.splitc === 0) { buf = Buffer.alloc(1); }
|
||||
else { buf = Buffer.alloc(this.splitc); }
|
||||
}
|
||||
else { buf = Buffer.alloc(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
|
||||
|
||||
clients[connection_id].client = net.Socket();
|
||||
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
|
||||
|
||||
if (host && port) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
//node.log(RED._("tcpin.errors.client-connected"));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].connected = true;
|
||||
clients[connection_id].connecting = false;
|
||||
let event;
|
||||
while (event = dequeue(clients[connection_id].msgQueue)) {
|
||||
clients[connection_id].client.write(event.msg.payload);
|
||||
event.nodeDone();
|
||||
}
|
||||
if (node.out === "time" && node.splitc < 0) {
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
clients[connection_id].client.end();
|
||||
delete clients[connection_id];
|
||||
node.status({});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("tcpin.errors.no-host"));
|
||||
}
|
||||
|
||||
clients[connection_id].client.on('data', function(data) {
|
||||
if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = RED.util.cloneMessage(data);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
}
|
||||
}
|
||||
// else if (node.splitc === 0) {
|
||||
// clients[connection_id].msg.payload = data;
|
||||
// node.send(clients[connection_id].msg);
|
||||
// }
|
||||
else {
|
||||
for (var j = 0; j < data.length; j++ ) {
|
||||
if (node.out === "time") {
|
||||
if (clients[connection_id]) {
|
||||
// do the timer thing
|
||||
if (clients[connection_id].timeout) {
|
||||
i += 1;
|
||||
buf[i] = data[j];
|
||||
}
|
||||
else {
|
||||
clients[connection_id].timeout = setTimeout(function () {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].timeout = null;
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i+1);
|
||||
buf.copy(msg.payload,0,0,i+1);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
}
|
||||
}, node.splitc);
|
||||
i = 0;
|
||||
buf[0] = data[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
// count bytes into a buffer...
|
||||
else if (node.out == "count") {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if ( i >= node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// look for a char
|
||||
else {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if (data[j] == node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clients[connection_id].client.on('end', function() {
|
||||
//console.log("END");
|
||||
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
clients[connection_id].client = null;
|
||||
}
|
||||
});
|
||||
|
||||
clients[connection_id].client.on('close', function() {
|
||||
//console.log("CLOSE");
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
}
|
||||
|
||||
var anyConnected = false;
|
||||
|
||||
for (var client in clients) {
|
||||
if (clients[client].connected) {
|
||||
anyConnected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (node.doneClose && !anyConnected) {
|
||||
clients = {};
|
||||
node.doneClose();
|
||||
}
|
||||
});
|
||||
|
||||
clients[connection_id].client.on('error', function() {
|
||||
//console.log("ERROR");
|
||||
node.status({fill:"red",shape:"ring",text:"common.status.error"});
|
||||
node.error(RED._("tcpin.errors.connect-fail") + " " + connection_id, msg);
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
});
|
||||
|
||||
clients[connection_id].client.on('timeout',function() {
|
||||
//console.log("TIMEOUT");
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
|
||||
//node.warn(RED._("tcpin.errors.connect-timeout"));
|
||||
if (clients[connection_id].client) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
clients[connection_id].connected = true;
|
||||
clients[connection_id].connecting = false;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
let event = dequeue(clients[connection_id].msgQueue)
|
||||
clients[connection_id].client.write(event.msg.payload);
|
||||
event.nodeDone();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.on("close", function(done) {
|
||||
node.doneClose = done;
|
||||
for (var cl in clients) {
|
||||
if (clients[cl].hasOwnProperty("client")) {
|
||||
clients[cl].client.destroy();
|
||||
}
|
||||
}
|
||||
node.status({});
|
||||
|
||||
// this is probably not necessary and may be removed
|
||||
var anyConnected = false;
|
||||
for (var c in clients) {
|
||||
if (clients[c].connected) {
|
||||
anyConnected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anyConnected) { clients = {}; }
|
||||
done();
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("tcp request",TcpGet);
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- The Input Node -->
|
||||
<script type="text/html" data-template-name="udp in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label>
|
||||
<select id="node-input-multicast" style='width:70%'>
|
||||
<option value="false" data-i18n="udp.udpmsgs"></option>
|
||||
<option value="true" data-i18n="udp.mcmsgs"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-group">
|
||||
<label for="node-input-group"><i class="fa fa-list"></i> <span data-i18n="udp.label.group"></span></label>
|
||||
<input type="text" id="node-input-group" placeholder="225.0.18.83">
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
|
||||
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interfaceprompt">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
|
||||
<input type="text" id="node-input-port" style="width:80px">
|
||||
<span data-i18n="udp.label.using"></span> <select id="node-input-ipv" style="width:80px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="udp.label.output"></span></label>
|
||||
<select id="node-input-datatype" style="width:70%;">
|
||||
<option value="buffer" data-i18n="udp.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="udp.output.string"></option>
|
||||
<option value="base64" data-i18n="udp.output.base64"></option>
|
||||
</select>
|
||||
</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"><span data-i18n="udp.tip.in"></span></div>
|
||||
<div class="form-tips" id="udpporttip"><span data-i18n="[html]udp.tip.port"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('udp in',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:"",required:true,validate:RED.validators.number()},
|
||||
ipv: {value:"udp4"},
|
||||
multicast: {value:"false"},
|
||||
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} },
|
||||
datatype: {value:"buffer",required:true}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "bridge-dash.svg",
|
||||
label: function() {
|
||||
if (this.multicast=="false") {
|
||||
return this.name||"udp "+this.port;
|
||||
}
|
||||
else {
|
||||
return this.name||"udp "+(this.group+":"+this.port);
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-multicast").on("change", function() {
|
||||
var id = $("#node-input-multicast").val();
|
||||
if (id == "false") {
|
||||
$(".node-input-group").hide();
|
||||
$(".node-input-iface").hide();
|
||||
}
|
||||
else {
|
||||
$(".node-input-group").show();
|
||||
$(".node-input-iface").show();
|
||||
}
|
||||
});
|
||||
$("#node-input-multicast").change();
|
||||
|
||||
var porttip = this._("udp.tip.port");
|
||||
var alreadyused = this._("udp.errors.alreadyused");
|
||||
var portsInUse = {};
|
||||
$.getJSON('udp-ports/'+this.id,function(data) {
|
||||
portsInUse = data || {};
|
||||
$('#udpporttip').html(porttip + data);
|
||||
});
|
||||
$("#node-input-port").on("change", function() {
|
||||
var portnew = $("#node-input-port").val();
|
||||
if (portsInUse.hasOwnProperty($("#node-input-port").val())) {
|
||||
RED.notify(alreadyused+" "+$("#node-input-port").val(),"warn");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- The Output Node -->
|
||||
<script type="text/html" data-template-name="udp out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label>
|
||||
<select id="node-input-multicast" style="width:40%">
|
||||
<option value="false" data-i18n="udp.udpmsg"></option>
|
||||
<option value="broad" data-i18n="udp.bcmsg"></option>
|
||||
<option value="multi" data-i18n="udp.mcmsg"></option>
|
||||
</select>
|
||||
<span data-i18n="udp.label.toport"></span> <input type="text" id="node-input-port" style="width:70px">
|
||||
</div>
|
||||
<div class="form-row node-input-addr">
|
||||
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> <span data-i18n="udp.label.address"></span></label>
|
||||
<input type="text" id="node-input-addr" data-i18n="[placeholder]udp.placeholder.address" style="width:50%;">
|
||||
<select id="node-input-ipv" style="width:70px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
|
||||
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interface">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outport-type"> </label>
|
||||
<select id="node-input-outport-type">
|
||||
<option id="node-input-outport-type-random" value="random" data-i18n="udp.bind.random"></option>
|
||||
<option value="fixed" data-i18n="udp.bind.local"></option>
|
||||
</select>
|
||||
<input type="text" id="node-input-outport" style="width:70px;">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-base64" style="display:inline-block; width:auto; vertical-align:top;">
|
||||
<label for="node-input-base64" style="width:70%;"><span data-i18n="udp.label.decode-base64"></span></label>
|
||||
</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"><span data-i18n="[html]udp.tip.out"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('udp out',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
addr: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:""},
|
||||
ipv: {value:"udp4"},
|
||||
outport: {value:""},
|
||||
base64: {value:false,required:true},
|
||||
multicast: {value:"false"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge-dash.svg",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"udp "+(this.addr+":"+this.port);
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var addresslabel = this._("udp.label.address");
|
||||
var addressph = this._("udp.placeholder.address");
|
||||
var grouplabel = this._("udp.label.group");
|
||||
var bindrandom = this._("udp.bind.random");
|
||||
var bindtarget = this._("udp.bind.target");
|
||||
|
||||
var type = this.outport===""?"random":"fixed";
|
||||
$("#node-input-outport-type").val(type);
|
||||
|
||||
$("#node-input-outport-type").on("change", function() {
|
||||
var type = $(this).val();
|
||||
if (type == "random") {
|
||||
$("#node-input-outport").val("").hide();
|
||||
} else {
|
||||
$("#node-input-outport").show();
|
||||
}
|
||||
});
|
||||
$("#node-input-outport-type").change();
|
||||
|
||||
$("#node-input-multicast").on("change", function() {
|
||||
var id = $("#node-input-multicast").val();
|
||||
if (id === "multi") {
|
||||
$(".node-input-iface").show();
|
||||
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + grouplabel);
|
||||
$("#node-input-addr")[0].placeholder = '225.0.18.83';
|
||||
}
|
||||
else if (id === "broad") {
|
||||
$(".node-input-iface").hide();
|
||||
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
|
||||
$("#node-input-addr")[0].placeholder = '255.255.255.255';
|
||||
}
|
||||
else {
|
||||
$(".node-input-iface").hide();
|
||||
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
|
||||
$("#node-input-addr")[0].placeholder = addressph;
|
||||
}
|
||||
var type = $(this).val();
|
||||
if (type == "false") {
|
||||
$("#node-input-outport-type-random").html(bindrandom);
|
||||
} else {
|
||||
$("#node-input-outport-type-random").html(bindtarget);
|
||||
}
|
||||
});
|
||||
$("#node-input-multicast").change();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,280 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var os = require('os');
|
||||
var dgram = require('dgram');
|
||||
var udpInputPortsInUse = {};
|
||||
|
||||
// The Input Node
|
||||
function UDPin(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.group = n.group;
|
||||
this.port = n.port;
|
||||
this.datatype = n.datatype;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
this.ipv = n.ipv || "udp4";
|
||||
var node = this;
|
||||
|
||||
if (node.iface && node.iface.indexOf(".") === -1) {
|
||||
try {
|
||||
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
|
||||
if (node.ipv === "udp4") {
|
||||
node.iface = (os.networkInterfaces())[node.iface][1].address;
|
||||
} else {
|
||||
node.iface = (os.networkInterfaces())[node.iface][0].address;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.ipv === "udp4") {
|
||||
node.iface = (os.networkInterfaces())[node.iface][0].address;
|
||||
} else {
|
||||
node.iface = (os.networkInterfaces())[node.iface][1].address;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
|
||||
node.iface = null;
|
||||
}
|
||||
}
|
||||
|
||||
var opts = {type:node.ipv, reuseAddr:true};
|
||||
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
|
||||
var server;
|
||||
|
||||
if (!udpInputPortsInUse.hasOwnProperty(node.port)) {
|
||||
server = dgram.createSocket(opts); // default to udp4
|
||||
server.bind(node.port, function() {
|
||||
if (node.multicast == "true") {
|
||||
server.setBroadcast(true);
|
||||
server.setMulticastLoopback(false);
|
||||
try {
|
||||
server.setMulticastTTL(128);
|
||||
server.addMembership(node.group,node.iface);
|
||||
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
|
||||
node.log(RED._("udp.status.mc-group",{group:node.group}));
|
||||
} catch (e) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error(RED._("udp.errors.bad-mcaddress"));
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error(RED._("udp.errors.interface"));
|
||||
} else {
|
||||
node.error(RED._("udp.errors.error",{error:e.errno}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
udpInputPortsInUse[node.port] = server;
|
||||
}
|
||||
else {
|
||||
node.log(RED._("udp.errors.alreadyused",{port:node.port}));
|
||||
server = udpInputPortsInUse[node.port]; // re-use existing
|
||||
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
|
||||
}
|
||||
|
||||
server.on("error", function (err) {
|
||||
if ((err.code == "EACCES") && (node.port < 1024)) {
|
||||
node.error(RED._("udp.errors.access-error"));
|
||||
} else {
|
||||
node.error(RED._("udp.errors.error",{error:err.code}));
|
||||
}
|
||||
server.close();
|
||||
});
|
||||
|
||||
server.on('message', function (message, remote) {
|
||||
var msg;
|
||||
if (node.datatype =="base64") {
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else if (node.datatype =="utf8") {
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else {
|
||||
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
server.on('listening', function () {
|
||||
var address = server.address();
|
||||
node.log(RED._("udp.status.listener-at",{host:node.iface||address.address,port:address.port}));
|
||||
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
try {
|
||||
if (node.multicast == "true") { server.dropMembership(node.group); }
|
||||
server.close();
|
||||
node.log(RED._("udp.status.listener-stopped"));
|
||||
} catch (err) {
|
||||
//node.error(err);
|
||||
}
|
||||
if (udpInputPortsInUse.hasOwnProperty(node.port)) {
|
||||
delete udpInputPortsInUse[node.port];
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
|
||||
}
|
||||
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
|
||||
res.json(Object.keys(udpInputPortsInUse));
|
||||
});
|
||||
RED.nodes.registerType("udp in",UDPin);
|
||||
|
||||
|
||||
|
||||
// The Output Node
|
||||
function UDPout(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
//this.group = n.group;
|
||||
this.port = n.port;
|
||||
this.outport = n.outport||"";
|
||||
this.base64 = n.base64;
|
||||
this.addr = n.addr;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
this.ipv = n.ipv || "udp4";
|
||||
var node = this;
|
||||
|
||||
if (node.iface && node.iface.indexOf(".") === -1) {
|
||||
try {
|
||||
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
|
||||
if (node.ipv === "udp4") {
|
||||
node.iface = (os.networkInterfaces())[node.iface][1].address;
|
||||
} else {
|
||||
node.iface = (os.networkInterfaces())[node.iface][0].address;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.ipv === "udp4") {
|
||||
node.iface = (os.networkInterfaces())[node.iface][0].address;
|
||||
} else {
|
||||
node.iface = (os.networkInterfaces())[node.iface][1].address;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
|
||||
node.iface = null;
|
||||
}
|
||||
}
|
||||
|
||||
var opts = {type:node.ipv, reuseAddr:true};
|
||||
|
||||
var sock;
|
||||
var p = this.outport || this.port || "0";
|
||||
node.tout = setTimeout(function() {
|
||||
if ((p != 0) && udpInputPortsInUse[p]) {
|
||||
sock = udpInputPortsInUse[p];
|
||||
if (node.multicast != "false") {
|
||||
sock.setBroadcast(true);
|
||||
sock.setMulticastLoopback(false);
|
||||
}
|
||||
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
|
||||
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
|
||||
}
|
||||
else {
|
||||
sock = dgram.createSocket(opts); // default to udp4
|
||||
if (node.multicast != "false") {
|
||||
sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
|
||||
sock.setBroadcast(true); // turn on broadcast
|
||||
sock.setMulticastLoopback(false); // turn off loopback
|
||||
if (node.multicast == "multi") {
|
||||
try {
|
||||
sock.setMulticastTTL(128);
|
||||
sock.addMembership(node.addr,node.iface); // Add to the multicast group
|
||||
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
|
||||
node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port}));
|
||||
} catch (e) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error(RED._("udp.errors.bad-mcaddress"));
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error(RED._("udp.errors.interface"));
|
||||
} else {
|
||||
node.error(RED._("udp.errors.error",{error:e.errno}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
|
||||
}
|
||||
});
|
||||
} else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) {
|
||||
sock.bind(node.outport);
|
||||
node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
|
||||
} else {
|
||||
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
|
||||
}
|
||||
sock.on("error", function(err) {
|
||||
// Any async error will also get reported in the sock.send call.
|
||||
// This handler is needed to ensure the error marked as handled to
|
||||
// prevent it going to the global error handler and shutting node-red
|
||||
// down.
|
||||
});
|
||||
udpInputPortsInUse[p] = sock;
|
||||
}
|
||||
|
||||
node.on("input", function(msg, nodeSend, nodeDone) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
var add = node.addr || msg.ip || "";
|
||||
var por = node.port || msg.port || 0;
|
||||
if (add === "") {
|
||||
node.warn(RED._("udp.errors.ip-notset"));
|
||||
nodeDone();
|
||||
} else if (por === 0) {
|
||||
node.warn(RED._("udp.errors.port-notset"));
|
||||
nodeDone();
|
||||
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
|
||||
node.warn(RED._("udp.errors.port-invalid"));
|
||||
nodeDone();
|
||||
} else {
|
||||
var message;
|
||||
if (node.base64) {
|
||||
message = Buffer.from(msg.payload, 'base64');
|
||||
} else if (msg.payload instanceof Buffer) {
|
||||
message = msg.payload;
|
||||
} else {
|
||||
message = Buffer.from(""+msg.payload);
|
||||
}
|
||||
sock.send(message, 0, message.length, por, add, function(err, bytes) {
|
||||
if (err) {
|
||||
node.error("udp : "+err,msg);
|
||||
}
|
||||
message = null;
|
||||
nodeDone();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 75);
|
||||
|
||||
node.on("close", function() {
|
||||
if (node.tout) { clearTimeout(node.tout); }
|
||||
try {
|
||||
if (node.multicast == "multi") { sock.dropMembership(node.group); }
|
||||
sock.close();
|
||||
node.log(RED._("udp.status.output-stopped"));
|
||||
} catch (err) {
|
||||
//node.error(err);
|
||||
}
|
||||
if (udpInputPortsInUse.hasOwnProperty(p)) {
|
||||
delete udpInputPortsInUse[p];
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("udp out",UDPout);
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var util = require("util");
|
||||
var mqtt = require("mqtt");
|
||||
var events = require("events");
|
||||
|
||||
util.log("[warn] nodes/core/io/lib/mqtt.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
|
||||
|
||||
//var inspect = require("util").inspect;
|
||||
|
||||
//var Client = module.exports.Client = function(
|
||||
|
||||
var port = 1883;
|
||||
var host = "localhost";
|
||||
|
||||
function MQTTClient(port,host) {
|
||||
this.port = port||1883;
|
||||
this.host = host||"localhost";
|
||||
this.messageId = 1;
|
||||
this.pendingSubscriptions = {};
|
||||
this.inboundMessages = {};
|
||||
this.lastOutbound = (new Date()).getTime();
|
||||
this.lastInbound = (new Date()).getTime();
|
||||
this.connected = false;
|
||||
|
||||
this._nextMessageId = function() {
|
||||
this.messageId += 1;
|
||||
if (this.messageId > 0xFFFF) {
|
||||
this.messageId = 1;
|
||||
}
|
||||
return this.messageId;
|
||||
}
|
||||
events.EventEmitter.call(this);
|
||||
}
|
||||
util.inherits(MQTTClient, events.EventEmitter);
|
||||
|
||||
MQTTClient.prototype.connect = function(options) {
|
||||
if (!this.connected) {
|
||||
var self = this;
|
||||
options = options||{};
|
||||
self.options = options;
|
||||
self.options.keepalive = options.keepalive||15;
|
||||
self.options.clean = self.options.clean||true;
|
||||
self.options.protocolId = 'MQIsdp';
|
||||
self.options.protocolVersion = 3;
|
||||
|
||||
self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
|
||||
if (err) {
|
||||
self.connected = false;
|
||||
clearInterval(self.watchdog);
|
||||
self.connectionError = true;
|
||||
//util.log('[mqtt] ['+self.uid+'] connection error 1 : '+inspect(err));
|
||||
self.emit('connectionlost',err);
|
||||
return;
|
||||
}
|
||||
client.on('close',function(e) {
|
||||
//util.log('[mqtt] ['+self.uid+'] on close');
|
||||
clearInterval(self.watchdog);
|
||||
if (!self.connectionError) {
|
||||
if (self.connected) {
|
||||
self.connected = false;
|
||||
self.emit('connectionlost',e);
|
||||
} else {
|
||||
self.emit('disconnect');
|
||||
}
|
||||
}
|
||||
});
|
||||
client.on('error',function(e) {
|
||||
//util.log('[mqtt] ['+self.uid+'] on error : '+inspect(e));
|
||||
clearInterval(self.watchdog);
|
||||
if (self.connected) {
|
||||
self.connected = false;
|
||||
self.emit('connectionlost',e);
|
||||
}
|
||||
});
|
||||
client.on('connack',function(packet) {
|
||||
if (packet.returnCode === 0) {
|
||||
self.watchdog = setInterval(function(self) {
|
||||
var now = (new Date()).getTime();
|
||||
|
||||
//util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
|
||||
|
||||
if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
|
||||
if (self.pingOutstanding) {
|
||||
//util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
|
||||
try {
|
||||
self.client.disconnect();
|
||||
} catch (err) {
|
||||
}
|
||||
} else {
|
||||
//util.log('[mqtt] ['+self.uid+'] watchdog pinging');
|
||||
self.lastOutbound = (new Date()).getTime();
|
||||
self.lastInbound = (new Date()).getTime();
|
||||
self.pingOutstanding = true;
|
||||
self.client.pingreq();
|
||||
}
|
||||
}
|
||||
|
||||
},self.options.keepalive*500,self);
|
||||
self.pingOutstanding = false;
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
self.lastOutbound = (new Date()).getTime()
|
||||
self.connected = true;
|
||||
self.connectionError = false;
|
||||
self.emit('connect');
|
||||
} else {
|
||||
self.connected = false;
|
||||
self.emit('connectionlost');
|
||||
}
|
||||
});
|
||||
client.on('suback',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
var topic = self.pendingSubscriptions[packet.messageId];
|
||||
self.emit('subscribe',topic,packet.granted[0]);
|
||||
delete self.pendingSubscriptions[packet.messageId];
|
||||
});
|
||||
client.on('unsuback',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
var topic = self.pendingSubscriptions[packet.messageId];
|
||||
self.emit('unsubscribe',topic);
|
||||
delete self.pendingSubscriptions[packet.messageId];
|
||||
});
|
||||
client.on('publish',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime();
|
||||
if (packet.qos < 2) {
|
||||
var p = packet;
|
||||
self.emit('message',p.topic,p.payload,p.qos,p.retain);
|
||||
} else {
|
||||
self.inboundMessages[packet.messageId] = packet;
|
||||
this.lastOutbound = (new Date()).getTime()
|
||||
self.client.pubrec(packet);
|
||||
}
|
||||
if (packet.qos == 1) {
|
||||
this.lastOutbound = (new Date()).getTime()
|
||||
self.client.puback(packet);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('pubrel',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
var p = self.inboundMessages[packet.messageId];
|
||||
if (p) {
|
||||
self.emit('message',p.topic,p.payload,p.qos,p.retain);
|
||||
delete self.inboundMessages[packet.messageId];
|
||||
}
|
||||
self.lastOutbound = (new Date()).getTime()
|
||||
self.client.pubcomp(packet);
|
||||
});
|
||||
|
||||
client.on('puback',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
// outbound qos-1 complete
|
||||
});
|
||||
|
||||
client.on('pubrec',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
self.lastOutbound = (new Date()).getTime()
|
||||
self.client.pubrel(packet);
|
||||
});
|
||||
client.on('pubcomp',function(packet) {
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
// outbound qos-2 complete
|
||||
});
|
||||
client.on('pingresp',function(packet) {
|
||||
//util.log('[mqtt] ['+self.uid+'] received pingresp');
|
||||
self.lastInbound = (new Date()).getTime()
|
||||
self.pingOutstanding = false;
|
||||
});
|
||||
|
||||
this.lastOutbound = (new Date()).getTime()
|
||||
this.connectionError = false;
|
||||
client.connect(self.options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MQTTClient.prototype.subscribe = function(topic,qos) {
|
||||
var self = this;
|
||||
if (self.connected) {
|
||||
var options = {
|
||||
subscriptions:[{topic:topic,qos:qos}],
|
||||
messageId: self._nextMessageId()
|
||||
};
|
||||
this.pendingSubscriptions[options.messageId] = topic;
|
||||
this.lastOutbound = (new Date()).getTime();
|
||||
self.client.subscribe(options);
|
||||
self.client.setPacketEncoding('binary');
|
||||
}
|
||||
}
|
||||
MQTTClient.prototype.unsubscribe = function(topic) {
|
||||
var self = this;
|
||||
if (self.connected) {
|
||||
var options = {
|
||||
unsubscriptions:[topic],
|
||||
messageId: self._nextMessageId()
|
||||
};
|
||||
this.pendingSubscriptions[options.messageId] = topic;
|
||||
this.lastOutbound = (new Date()).getTime()
|
||||
self.client.unsubscribe(options);
|
||||
}
|
||||
}
|
||||
|
||||
MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
|
||||
var self = this;
|
||||
if (self.connected) {
|
||||
|
||||
if (!Buffer.isBuffer(payload)) {
|
||||
if (typeof payload === "object") {
|
||||
payload = JSON.stringify(payload);
|
||||
} else if (typeof payload !== "string") {
|
||||
payload = ""+payload;
|
||||
}
|
||||
}
|
||||
var options = {
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
qos: qos||0,
|
||||
retain:retain||false
|
||||
};
|
||||
if (options.qos !== 0) {
|
||||
options.messageId = self._nextMessageId();
|
||||
}
|
||||
this.lastOutbound = (new Date()).getTime()
|
||||
self.client.publish(options);
|
||||
}
|
||||
}
|
||||
|
||||
MQTTClient.prototype.disconnect = function() {
|
||||
var self = this;
|
||||
if (this.connected) {
|
||||
this.connected = false;
|
||||
try {
|
||||
this.client.disconnect();
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
MQTTClient.prototype.isConnected = function() {
|
||||
return this.connected;
|
||||
}
|
||||
module.exports.createClient = function(port,host) {
|
||||
var mqtt_client = new MQTTClient(port,host);
|
||||
return mqtt_client;
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="csv">
|
||||
<div class="form-row">
|
||||
<label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
|
||||
<input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> <span data-i18n="csv.label.separator"></span></label>
|
||||
<select style="width:150px" id="node-input-select-sep">
|
||||
<option value="," data-i18n="csv.separator.comma"></option>
|
||||
<option value="\t" data-i18n="csv.separator.tab"></option>
|
||||
<option value=" " data-i18n="csv.separator.space"></option>
|
||||
<option value=";" data-i18n="csv.separator.semicolon"></option>
|
||||
<option value=":" data-i18n="csv.separator.colon"></option>
|
||||
<option value="#" data-i18n="csv.separator.hashtag"></option>
|
||||
<option value="" data-i18n="csv.separator.other"></option>
|
||||
</select>
|
||||
<input style="width:40px;" type="text" id="node-input-sep" pattern=".">
|
||||
</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>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width:100%;"><span data-i18n="csv.label.c2o"></span></label>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:20px;">
|
||||
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
|
||||
<span data-i18n="csv.label.skip-s"></span> <input type="text" id="node-input-skip" style="width:40px; height:25px;"/> <span data-i18n="csv.label.skip-e"></span><br/>
|
||||
<label> </label>
|
||||
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
|
||||
<label> </label>
|
||||
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/>
|
||||
<label> </label>
|
||||
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br/>
|
||||
<label> </label>
|
||||
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br/>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:20px;">
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
|
||||
<select type="text" id="node-input-multi" style="width:250px;">
|
||||
<option value="one" data-i18n="csv.output.row"></option>
|
||||
<option value="mult" data-i18n="csv.output.array"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="margin-top:20px">
|
||||
<label style="width:100%;"><span data-i18n="csv.label.o2c"></span></label>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:20px;">
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
|
||||
<!-- <input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span> -->
|
||||
<select style="width:60%" id="node-input-hdrout">
|
||||
<option value="none" data-i18n="csv.hdrout.none"></option>
|
||||
<option value="all" data-i18n="csv.hdrout.all"></option>
|
||||
<option value="once" data-i18n="csv.hdrout.once"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:20px;">
|
||||
<label></label>
|
||||
<label style="width:auto; margin-right:10px;" for="node-input-ret"><span data-i18n="csv.label.newline"></span></label>
|
||||
<select style="width:150px;" id="node-input-ret">
|
||||
<option value='\n' data-i18n="csv.newline.linux"></option>
|
||||
<option value='\r' data-i18n="csv.newline.mac"></option>
|
||||
<option value='\r\n' data-i18n="csv.newline.windows"></option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('csv',{
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
|
||||
//quo: {value:'"',required:true},
|
||||
hdrin: {value:""},
|
||||
hdrout: {value:"none"},
|
||||
multi: {value:"one",required:true},
|
||||
ret: {value:'\\n'},
|
||||
temp: {value:""},
|
||||
skip: {value:"0"},
|
||||
strings: {value:true},
|
||||
include_empty_strings: {value:""},
|
||||
include_null_values: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "parser-csv.svg",
|
||||
label: function() {
|
||||
return this.name||"csv";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); }
|
||||
if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");}
|
||||
if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); }
|
||||
if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
|
||||
$("#node-input-skip").spinner({ min:0 });
|
||||
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
|
||||
$("#node-input-select-sep").val(this.sep);
|
||||
$("#node-input-sep").hide();
|
||||
} else {
|
||||
$("#node-input-select-sep").val("");
|
||||
$("#node-input-sep").val(this.sep);
|
||||
$("#node-input-sep").show();
|
||||
}
|
||||
$("#node-input-select-sep").on("change", function() {
|
||||
var v = $("#node-input-select-sep").val();
|
||||
$("#node-input-sep").val(v);
|
||||
if (v == "") {
|
||||
$("#node-input-sep").val("");
|
||||
$("#node-input-sep").show().focus();
|
||||
} else {
|
||||
$("#node-input-sep").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,319 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function CSVNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.template = (n.temp || "");
|
||||
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
|
||||
this.quo = '"';
|
||||
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
|
||||
this.winflag = (this.ret === "\r\n");
|
||||
this.lineend = "\n";
|
||||
this.multi = n.multi || "one";
|
||||
this.hdrin = n.hdrin || false;
|
||||
this.hdrout = n.hdrout || "none";
|
||||
this.goodtmpl = true;
|
||||
this.skip = parseInt(n.skip || 0);
|
||||
this.store = [];
|
||||
this.parsestrings = n.strings;
|
||||
this.include_empty_strings = n.include_empty_strings || false;
|
||||
this.include_null_values = n.include_null_values || false;
|
||||
if (this.parsestrings === undefined) { this.parsestrings = true; }
|
||||
if (this.hdrout === false) { this.hdrout = "none"; }
|
||||
if (this.hdrout === true) { this.hdrout = "all"; }
|
||||
var tmpwarn = true;
|
||||
var node = this;
|
||||
var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
|
||||
|
||||
// pass in an array of column names to be trimmed, de-quoted and retrimmed
|
||||
var clean = function(col,sep) {
|
||||
if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
|
||||
col = col.trim().split(re) || [""];
|
||||
col = col.map(x => x.replace(/"/g,'').trim());
|
||||
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
|
||||
else { node.goodtmpl = true; }
|
||||
return col;
|
||||
}
|
||||
var template = clean(node.template,',');
|
||||
var notemplate = template.length === 1 && template[0] === '';
|
||||
node.hdrSent = false;
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
node.hdrSent = false;
|
||||
}
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (typeof msg.payload == "object") { // convert object to CSV string
|
||||
try {
|
||||
if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
|
||||
template = clean(node.template);
|
||||
}
|
||||
var ou = "";
|
||||
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
|
||||
if (node.hdrout !== "none" && node.hdrSent === false) {
|
||||
if ((template.length === 1) && (template[0] === '')) {
|
||||
if (msg.hasOwnProperty("columns")) {
|
||||
template = clean(msg.columns || "",",");
|
||||
}
|
||||
else {
|
||||
template = Object.keys(msg.payload[0]);
|
||||
}
|
||||
}
|
||||
ou += template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret;
|
||||
if (node.hdrout === "once") { node.hdrSent = true; }
|
||||
}
|
||||
for (var s = 0; s < msg.payload.length; s++) {
|
||||
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
|
||||
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
|
||||
for (var t = 0; t < msg.payload[s].length; t++) {
|
||||
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
|
||||
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
|
||||
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
|
||||
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
|
||||
}
|
||||
else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
|
||||
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
|
||||
}
|
||||
}
|
||||
ou += msg.payload[s].join(node.sep) + node.ret;
|
||||
}
|
||||
else {
|
||||
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
|
||||
template = clean(msg.columns || "",",");
|
||||
}
|
||||
if ((template.length === 1) && (template[0] === '')) {
|
||||
/* istanbul ignore else */
|
||||
if (tmpwarn === true) { // just warn about missing template once
|
||||
node.warn(RED._("csv.errors.obj_csv"));
|
||||
tmpwarn = false;
|
||||
}
|
||||
for (var p in msg.payload[0]) {
|
||||
/* istanbul ignore else */
|
||||
if (msg.payload[s].hasOwnProperty(p)) {
|
||||
/* istanbul ignore else */
|
||||
if (typeof msg.payload[s][p] !== "object") {
|
||||
var q = "" + msg.payload[s][p];
|
||||
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
|
||||
q = q.replace(/"/g, '""');
|
||||
ou += node.quo + q + node.quo + node.sep;
|
||||
}
|
||||
else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas"
|
||||
ou += node.quo + q + node.quo + node.sep;
|
||||
}
|
||||
else { ou += q + node.sep; } // otherwise just add
|
||||
}
|
||||
}
|
||||
}
|
||||
ou = ou.slice(0,-1) + node.ret;
|
||||
}
|
||||
else {
|
||||
for (var t=0; t < template.length; t++) {
|
||||
if (template[t] === '') {
|
||||
ou += node.sep;
|
||||
}
|
||||
else {
|
||||
var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
|
||||
/* istanbul ignore else */
|
||||
if (p === "undefined") { p = ""; }
|
||||
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
|
||||
p = p.replace(/"/g, '""');
|
||||
ou += node.quo + p + node.quo + node.sep;
|
||||
}
|
||||
else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas"
|
||||
ou += node.quo + p + node.quo + node.sep;
|
||||
}
|
||||
else { ou += p + node.sep; } // otherwise just add
|
||||
}
|
||||
}
|
||||
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.payload = ou;
|
||||
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
|
||||
if (msg.payload !== '') { send(msg); }
|
||||
done();
|
||||
}
|
||||
catch(e) { done(e); }
|
||||
}
|
||||
else if (typeof msg.payload == "string") { // convert CSV string to object
|
||||
try {
|
||||
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
|
||||
var j = 0; // pointer into array of template items
|
||||
var k = [""]; // array of data for each of the template items
|
||||
var o = {}; // output object to build up
|
||||
var a = []; // output array is needed for multiline option
|
||||
var first = true; // is this the first line
|
||||
var last = false;
|
||||
var line = msg.payload;
|
||||
var linecount = 0;
|
||||
var tmp = "";
|
||||
var has_parts = msg.hasOwnProperty("parts");
|
||||
var reg = /^[-]?(?!E)(?!0\d)\d*\.?\d*(E-?\+?)?\d+$/i;
|
||||
if (msg.hasOwnProperty("parts")) {
|
||||
linecount = msg.parts.index;
|
||||
if (msg.parts.index > node.skip) { first = false; }
|
||||
if (msg.parts.hasOwnProperty("count") && (msg.parts.index+1 >= msg.parts.count)) { last = true; }
|
||||
}
|
||||
|
||||
// For now we are just going to assume that any \r or \n means an end of line...
|
||||
// got to be a weird csv that has singleton \r \n in it for another reason...
|
||||
|
||||
// Now process the whole file/line
|
||||
var nocr = (line.match(/[\r\n]/g)||[]).length;
|
||||
if (has_parts && node.multi === "mult" && nocr > 1) { tmp = ""; first = true; }
|
||||
for (var i = 0; i < line.length; i++) {
|
||||
if (first && (linecount < node.skip)) {
|
||||
if (line[i] === "\n") { linecount += 1; }
|
||||
continue;
|
||||
}
|
||||
if ((node.hdrin === true) && first) { // if the template is in the first line
|
||||
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
|
||||
if (line.length - i === 1) { tmp += line[i]; }
|
||||
template = clean(tmp,node.sep);
|
||||
first = false;
|
||||
}
|
||||
else { tmp += line[i]; }
|
||||
}
|
||||
else {
|
||||
if (line[i] === node.quo) { // if it's a quote toggle inside or outside
|
||||
f = !f;
|
||||
if (line[i-1] === node.quo) {
|
||||
if (f === false) { k[j] += '\"'; }
|
||||
} // if it's a quotequote then it's actually a quote
|
||||
//if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
|
||||
}
|
||||
else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
|
||||
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
|
||||
if ( template[j] && (template[j] !== "") ) {
|
||||
// if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
|
||||
if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
|
||||
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
|
||||
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
|
||||
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
|
||||
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
|
||||
}
|
||||
j += 1;
|
||||
// if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
|
||||
k[j] = line.length - 1 === i ? null : "";
|
||||
}
|
||||
else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines
|
||||
//console.log(j,k,o,k[j]);
|
||||
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
|
||||
if ( template[j] && (template[j] !== "") ) {
|
||||
// if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
|
||||
if (line[i-1] === node.sep) k[j] = null;
|
||||
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
|
||||
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
|
||||
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
|
||||
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
|
||||
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
|
||||
}
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
a.push(o); // add to the array
|
||||
}
|
||||
j = 0;
|
||||
k = [""];
|
||||
o = {};
|
||||
f = true; // reset in/out flag ready for next line.
|
||||
}
|
||||
else { // just add to the part of the message
|
||||
k[j] += line[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finished so finalize and send anything left
|
||||
if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
|
||||
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
|
||||
|
||||
if ( template[j] && (template[j] !== "") ) {
|
||||
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
|
||||
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
|
||||
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
|
||||
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
|
||||
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
|
||||
}
|
||||
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
a.push(o); // add to the array
|
||||
}
|
||||
|
||||
if (node.multi !== "one") {
|
||||
msg.payload = a;
|
||||
if (has_parts && nocr <= 1) {
|
||||
if (JSON.stringify(o) !== "{}") {
|
||||
node.store.push(o);
|
||||
}
|
||||
if (msg.parts.index + 1 === msg.parts.count) {
|
||||
msg.payload = node.store;
|
||||
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
|
||||
delete msg.parts;
|
||||
send(msg);
|
||||
node.store = [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
|
||||
send(msg); // finally send the array
|
||||
}
|
||||
}
|
||||
else {
|
||||
var len = a.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var newMessage = RED.util.cloneMessage(msg);
|
||||
newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
|
||||
newMessage.payload = a[i];
|
||||
if (!has_parts) {
|
||||
newMessage.parts = {
|
||||
id: msg._msgid,
|
||||
index: i,
|
||||
count: len
|
||||
};
|
||||
}
|
||||
else {
|
||||
newMessage.parts.index -= node.skip;
|
||||
newMessage.parts.count -= node.skip;
|
||||
if (node.hdrin) { // if we removed the header line then shift the counts by 1
|
||||
newMessage.parts.index -= 1;
|
||||
newMessage.parts.count -= 1;
|
||||
}
|
||||
}
|
||||
if (last) { newMessage.complete = true; }
|
||||
send(newMessage);
|
||||
}
|
||||
if (has_parts && last && len === 0) {
|
||||
send({complete:true});
|
||||
}
|
||||
}
|
||||
node.linecount = 0;
|
||||
done();
|
||||
}
|
||||
catch(e) { done(e); }
|
||||
}
|
||||
else { node.warn(RED._("csv.errors.csv_js")); done(); }
|
||||
}
|
||||
else {
|
||||
if (!msg.hasOwnProperty("reset")) {
|
||||
node.send(msg); // If no payload and not reset - just pass it on.
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("csv",CSVNode);
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="html">
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:70%">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label>
|
||||
<input type="text" id="node-input-tag" placeholder="h1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
|
||||
<select id="node-input-ret" style="width:70%">
|
||||
<option value="html" data-i18n="html.output.html"></option>
|
||||
<option value="text" data-i18n="html.output.text"></option>
|
||||
<option value="attr" data-i18n="html.output.attr"></option>
|
||||
<!-- <option value="val">return the value from a form element</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-as"> </label>
|
||||
<select id="node-input-as" style="width:70%">
|
||||
<option value="single" data-i18n="html.format.single"></option>
|
||||
<option value="multi" data-i18n="html.format.multi"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outproperty"> </label>
|
||||
<span data-i18n="html.label.in" style="padding-left:8px; padding-right:2px; vertical-align:-1px;"></span> <input type="text" id="node-input-outproperty" style="width:64%">
|
||||
</div>
|
||||
<br/>
|
||||
<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" style="width:70%" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('html',{
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload"},
|
||||
outproperty: {value:"payload"},
|
||||
tag: {value:""},
|
||||
ret: {value:"html"},
|
||||
as: {value:"single"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "parser-html.svg",
|
||||
label: function() {
|
||||
return this.name||this.tag||"html";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
$("#node-input-outproperty").typedInput({default:'msg',types:['msg']});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var cheerio = require('cheerio');
|
||||
|
||||
function CheerioNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.property = n.property||"payload";
|
||||
this.outproperty = n.outproperty||this.property||"payload";
|
||||
this.tag = n.tag;
|
||||
this.ret = n.ret || "html";
|
||||
this.as = n.as || "single";
|
||||
var node = this;
|
||||
this.on("input", function(msg,send,done) {
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
var tag = node.tag;
|
||||
if (msg.hasOwnProperty("select")) { tag = node.tag || msg.select; }
|
||||
try {
|
||||
var $ = cheerio.load(value);
|
||||
var pay = [];
|
||||
var count = 0;
|
||||
$(tag).each(function() {
|
||||
count++;
|
||||
});
|
||||
var index = 0;
|
||||
$(tag).each(function() {
|
||||
if (node.as === "multi") {
|
||||
var pay2 = null;
|
||||
if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim(),null,false).xml(); }
|
||||
if (node.ret === "text") { pay2 = $(this).text(); }
|
||||
if (node.ret === "attr") {
|
||||
pay2 = Object.assign({},this.attribs);
|
||||
}
|
||||
//if (node.ret === "val") { pay2 = $(this).val(); }
|
||||
/* istanbul ignore else */
|
||||
if (pay2) {
|
||||
var new_msg = RED.util.cloneMessage(msg);
|
||||
RED.util.setMessageProperty(new_msg,node.outproperty,pay2);
|
||||
new_msg.parts = {
|
||||
id: msg._msgid,
|
||||
index: index,
|
||||
count: count,
|
||||
type: "string",
|
||||
ch: ""
|
||||
};
|
||||
send(new_msg);
|
||||
}
|
||||
}
|
||||
if (node.as === "single") {
|
||||
if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim(),null,false).xml() ); }
|
||||
if (node.ret === "text") { pay.push( $(this).text() ); }
|
||||
if (node.ret === "attr") {
|
||||
var attribs = Object.assign({},this.attribs);
|
||||
pay.push( attribs );
|
||||
}
|
||||
//if (node.ret === "val") { pay.push( $(this).val() ); }
|
||||
}
|
||||
index++;
|
||||
});
|
||||
if (node.as === "single") { // Always return an array - even if blank
|
||||
RED.util.setMessageProperty(msg,node.outproperty,pay);
|
||||
send(msg);
|
||||
}
|
||||
done();
|
||||
}
|
||||
catch (error) {
|
||||
done(error.message);
|
||||
}
|
||||
}
|
||||
else { send(msg); done(); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("html",CheerioNode);
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="json">
|
||||
<div class="form-row">
|
||||
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="json.label.action"></span></label>
|
||||
<select style="width:70%" id="node-input-action">
|
||||
<option value="" data-i18n="json.label.actions.toggle"></option>
|
||||
<option value="str" data-i18n="json.label.actions.str"></option>
|
||||
<option value="obj" data-i18n="json.label.actions.obj"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="json.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:70%;"/>
|
||||
</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>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row node-json-to-json-options">
|
||||
<label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-json-to-json-options" style="padding-left: 20px;">
|
||||
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('json',{
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload",required:true},
|
||||
action: {value:""},
|
||||
pretty: {value:false}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "parser-json.svg",
|
||||
label: function() {
|
||||
return this.name||"json";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.property === undefined) {
|
||||
$("#node-input-property").val("payload");
|
||||
}
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
$("#node-input-action").on("change", function() {
|
||||
if (this.value === "" || this.value === "str") {
|
||||
$(".node-json-to-json-options").show();
|
||||
} else {
|
||||
$(".node-json-to-json-options").hide();
|
||||
}
|
||||
});
|
||||
$("#node-input-action").change();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
const Ajv = require('ajv');
|
||||
const ajv = new Ajv({allErrors: true});
|
||||
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
|
||||
|
||||
function JSONNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.indent = n.pretty ? 4 : 0;
|
||||
this.action = n.action||"";
|
||||
this.property = n.property||"payload";
|
||||
this.schema = null;
|
||||
this.compiledSchema = null;
|
||||
|
||||
var node = this;
|
||||
|
||||
this.on("input", function(msg,send,done) {
|
||||
var validate = false;
|
||||
if (msg.schema) {
|
||||
// If input schema is different, re-compile it
|
||||
if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) {
|
||||
try {
|
||||
this.compiledSchema = ajv.compile(msg.schema);
|
||||
this.schema = msg.schema;
|
||||
} catch(e) {
|
||||
this.schema = null;
|
||||
this.compiledSchema = null;
|
||||
done(RED._("json.errors.schema-error-compile"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
validate = true;
|
||||
}
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
if (typeof value === "string") {
|
||||
if (node.action === "" || node.action === "obj") {
|
||||
try {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
|
||||
if (validate) {
|
||||
if (this.compiledSchema(msg[node.property])) {
|
||||
delete msg.schema;
|
||||
send(msg);
|
||||
done();
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
|
||||
}
|
||||
} else {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
}
|
||||
catch(e) { done(e.message); }
|
||||
} else {
|
||||
// If node.action is str and value is str
|
||||
if (validate) {
|
||||
if (this.compiledSchema(JSON.parse(msg[node.property]))) {
|
||||
delete msg.schema;
|
||||
send(msg);
|
||||
done();
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
|
||||
}
|
||||
} else {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((typeof value === "object") || (typeof value === "boolean") || (typeof value === "number")) {
|
||||
if (node.action === "" || node.action === "str") {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
try {
|
||||
if (validate) {
|
||||
if (this.compiledSchema(value)) {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
|
||||
delete msg.schema;
|
||||
send(msg);
|
||||
done();
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
|
||||
}
|
||||
} else {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
}
|
||||
catch(e) { done(RED._("json.errors.dropped-error")); }
|
||||
}
|
||||
else { node.warn(RED._("json.errors.dropped-object")); done(); }
|
||||
} else {
|
||||
// If node.action is obj and value is object
|
||||
if (validate) {
|
||||
if (this.compiledSchema(value)) {
|
||||
delete msg.schema;
|
||||
send(msg);
|
||||
done();
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
|
||||
}
|
||||
} else {
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
else { node.warn(RED._("json.errors.dropped")); done(); }
|
||||
}
|
||||
else { send(msg); done(); } // If no property - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("json",JSONNode);
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="xml">
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:70%;"/>
|
||||
</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>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width:100%;"><span data-i18n="xml.label.x2o"></span></label>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left: 20px;">
|
||||
<label style="width:250px;" for="node-input-attr" data-i18n="xml.label.represent"></label> <input type="text" id="node-input-attr" style="text-align:center; width:40px" placeholder="$">
|
||||
</div>
|
||||
<div class="form-row" style="padding-left: 20px;">
|
||||
<label style="width:250px;" for="node-input-chr" data-i18n="xml.label.prefix"></label> <input type="text" id="node-input-chr" style="text-align:center; width:40px" placeholder="_">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('xml',{
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload",required:true},
|
||||
attr: {value:""},
|
||||
chr: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "parser-xml.svg",
|
||||
label: function() {
|
||||
return this.name||"xml";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.property === undefined) {
|
||||
$("#node-input-property").val("payload");
|
||||
}
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,49 +0,0 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var xml2js = require('xml2js');
|
||||
var parseString = xml2js.parseString;
|
||||
|
||||
function XMLNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.attrkey = n.attr;
|
||||
this.charkey = n.chr;
|
||||
this.property = n.property||"payload";
|
||||
var node = this;
|
||||
this.on("input", function(msg,send,done) {
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
var options;
|
||||
if (typeof value === "object") {
|
||||
options = {renderOpts:{pretty:false}};
|
||||
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
|
||||
options.async = false;
|
||||
var builder = new xml2js.Builder(options);
|
||||
value = builder.buildObject(value, options);
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
else if (typeof value == "string") {
|
||||
options = {};
|
||||
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
|
||||
options.async = true;
|
||||
options.attrkey = node.attrkey || options.attrkey || '$';
|
||||
options.charkey = node.charkey || options.charkey || '_';
|
||||
parseString(value, options, function (err, result) {
|
||||
if (err) { done(err); }
|
||||
else {
|
||||
value = result;
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
else { node.warn(RED._("xml.errors.xml_js")); done(); }
|
||||
}
|
||||
else { send(msg); done(); } // If no property - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("xml",XMLNode);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="yaml">
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
|
||||
<input type="text" id="node-input-property" style="width:70%;"/>
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('yaml',{
|
||||
category: 'parser',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
property: {value:"payload",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "parser-yaml.svg",
|
||||
label: function() {
|
||||
return this.name||"yaml";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.property === undefined) {
|
||||
$("#node-input-property").val("payload");
|
||||
}
|
||||
$("#node-input-property").typedInput({default:'msg',types:['msg']});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,41 +0,0 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var yaml = require('js-yaml');
|
||||
function YAMLNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.property = n.property||"payload";
|
||||
var node = this;
|
||||
this.on("input", function(msg,send,done) {
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
value = yaml.load(value);
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
catch(e) { done(e.message); }
|
||||
}
|
||||
else if (typeof value === "object") {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
try {
|
||||
value = yaml.dump(value);
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
catch(e) {
|
||||
done(RED._("yaml.errors.dropped-error"));
|
||||
}
|
||||
}
|
||||
else { node.warn(RED._("yaml.errors.dropped-object")); done(); }
|
||||
}
|
||||
else { node.warn(RED._("yaml.errors.dropped")); done(); }
|
||||
}
|
||||
else { send(msg); done(); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("yaml",YAMLNode);
|
||||
};
|
@ -1,325 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="split">
|
||||
<div class="form-row"><span data-i18n="[html]split.intro"></span></div>
|
||||
<div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-splt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
|
||||
<input type="text" id="node-input-splt" style="width:70%">
|
||||
<input type="hidden" id="node-input-spltType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-stream" style="margin-left:10px; vertical-align:top; width:auto;">
|
||||
<label for="node-input-stream" style="width:auto;" data-i18n="split.stream"></label>
|
||||
</div>
|
||||
<div class="form-row"><span data-i18n="[html]split.array"></span></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-arraySplt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
|
||||
<input type="text" id="node-input-arraySplt" style="width:70%">
|
||||
<input type="hidden" id="node-input-arraySpltType">
|
||||
</div>
|
||||
<div class="form-row"><span data-i18n="[html]split.object"></span></div>
|
||||
<div class="form-row" style="padding-left: 10px"><span data-i18n="[html]split.objectSend"></span></div>
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-addname-cb" style="margin-left:10px; vertical-align:baseline; width:auto;">
|
||||
<label for="node-input-addname-cb" style="width:auto;" data-i18n="split.addname"></label>
|
||||
<input type="text" id="node-input-addname" style="width:70%">
|
||||
</div>
|
||||
<hr/>
|
||||
<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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('split',{
|
||||
category: 'sequence',
|
||||
color:"#E2D96E",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
splt: {value:"\\n"},
|
||||
spltType: {value:"str"},
|
||||
arraySplt: {value:1},
|
||||
arraySpltType: {value:"len"},
|
||||
stream: {value:false},
|
||||
addname: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "split.svg",
|
||||
label: function() {
|
||||
return this.name||this._("split.split");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-splt").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-spltType"),
|
||||
types:[
|
||||
'str',
|
||||
'bin',
|
||||
{value:"len", label:RED._("node-red:split.splitLength"),validate:/^\d+$/}
|
||||
]
|
||||
});
|
||||
if (this.arraySplt === undefined) {
|
||||
$("#node-input-arraySplt").val(1);
|
||||
}
|
||||
$("#node-input-arraySplt").typedInput({
|
||||
default: 'len',
|
||||
typeField: $("#node-input-arraySpltType"),
|
||||
types:[
|
||||
{value:"len", label:RED._("node-red:split.splitLength"),validate:/^\d+$/}
|
||||
]
|
||||
});
|
||||
$("#node-input-addname").typedInput({
|
||||
typeField: $("#node-input-fnameType"),
|
||||
types:['msg']
|
||||
});
|
||||
|
||||
$("#node-input-addname-cb").on("change", function() {
|
||||
$("#node-input-addname").prop('disabled',!this.checked);
|
||||
})
|
||||
if (this.addname === "") {
|
||||
$("#node-input-addname-cb").prop('checked',false);
|
||||
$("#node-input-addname").val('topic');
|
||||
} else {
|
||||
$("#node-input-addname-cb").prop('checked',true);
|
||||
}
|
||||
$("#node-input-addname-cb").change();
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-addname-cb").prop('checked')) {
|
||||
$("#node-input-addname").val('');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" data-template-name="join">
|
||||
<div class="form-row">
|
||||
<label data-i18n="join.mode.mode"></label>
|
||||
<select id="node-input-mode" style="width:200px;">
|
||||
<option value="auto" data-i18n="join.mode.auto"></option>
|
||||
<option value="custom" data-i18n="join.mode.custom"></option>
|
||||
<option value="reduce" data-i18n="join.mode.reduce"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="node-row-custom">
|
||||
<div class="form-row node-row-property">
|
||||
<label data-i18n="join.combine"> </label>
|
||||
<input type="text" id="node-input-property" style="width:70%;">
|
||||
<input type="hidden" id="node-input-propertyType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label data-i18n="join.create"></label>
|
||||
<select id="node-input-build" style="width:70%;">
|
||||
<option value="string" data-i18n="join.type.string"></option>
|
||||
<option value="buffer" data-i18n="join.type.buffer"></option>
|
||||
<option value="array" data-i18n="join.type.array"></option>
|
||||
<option value="object" data-i18n="join.type.object"></option>
|
||||
<option value="merged" data-i18n="join.type.merged"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-row-key">
|
||||
<label style="vertical-align:top; margin-top:7px; width:auto; margin-right: 5px;" data-i18n="join.using"></label>
|
||||
<div style="display:inline-block">
|
||||
<input type="text" id="node-input-key" style="width:220px;"> <span data-i18n="join.key"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row node-row-joiner">
|
||||
<label for="node-input-joiner" data-i18n="join.joinedUsing"></label>
|
||||
<input type="text" id="node-input-joiner" style="width:70%">
|
||||
<input type="hidden" id="node-input-joinerType">
|
||||
</div>
|
||||
<div class="form-row node-row-trigger" id="trigger-row">
|
||||
<label style="width:auto;" data-i18n="join.send"></label>
|
||||
<ul>
|
||||
<li>
|
||||
<label style="width:280px;" for="node-input-count" data-i18n="join.afterCount"></label> <input id="node-input-count" data-i18n="[placeholder]join.count" type="text" style="width:75px;">
|
||||
</li>
|
||||
<li class="node-row-accumulate" style="list-style-type:none;">
|
||||
<input type="checkbox" id="node-input-accumulate" style="display:inline-block; width:20px; margin-left:20px; vertical-align:top;"> <label style="width: auto" for="node-input-accumulate" data-i18n="join.subsequent"></label>
|
||||
</li>
|
||||
<li>
|
||||
<label style="width:280px;" for="node-input-timeout" data-i18n="join.afterTimeout"></label> <input id="node-input-timeout" data-i18n="[placeholder]join.seconds" type="text" style="width:75px;">
|
||||
</li>
|
||||
<li>
|
||||
<label style="width:auto; padding-top:6px;" data-i18n="[html]join.complete"></label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-row-reduce">
|
||||
<div class="form-row">
|
||||
<label for="node-input-reduceExp" data-i18n="join.reduce.exp" style="margin-left:10px;"></label>
|
||||
<input type="text" id="node-input-reduceExp" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reduceInit" data-i18n="join.reduce.init" style="margin-left:10px;"></label>
|
||||
<input type="text" id="node-input-reduceInit" data-i18n="[placeholder]join.reduce.init" style="width:65%">
|
||||
<input type="hidden" id="node-input-reduceInitType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reduceFixup" data-i18n="join.reduce.fixup" style="margin-left:10px;"></label>
|
||||
<input type="text" id="node-input-reduceFixup" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-reduceRight" style="display:inline-block; width:auto; vertical-align:top; margin-left:10px;">
|
||||
<label for="node-input-reduceRight" style="width:70%;" data-i18n="join.reduce.right" style="margin-left:10px;"/>
|
||||
</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">
|
||||
</div>
|
||||
<div class="form-tips form-tips-auto hide" data-i18n="[html]join.tip"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('join',{
|
||||
category: 'sequence',
|
||||
color:"#E2D96E",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
mode: {value:"auto"},
|
||||
build: { value:"object"},
|
||||
property: { value:"payload", validate:RED.validators.typedInput("propertyType")},
|
||||
propertyType: { value:"msg"},
|
||||
key: {value:"topic"},
|
||||
joiner: { value:"\\n"},
|
||||
joinerType: { value:"str"},
|
||||
accumulate: { value:"false" },
|
||||
timeout: {value:""},
|
||||
count: {value:""},
|
||||
reduceRight: {value:false},
|
||||
reduceExp: {value:undefined},
|
||||
reduceInit: {value:undefined},
|
||||
reduceInitType: {value:undefined},
|
||||
reduceFixup: {value:undefined}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "join.svg",
|
||||
label: function() {
|
||||
return this.name||this._("join.join");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
|
||||
$("#node-input-mode").on("change", function(e) {
|
||||
var val = $(this).val();
|
||||
$(".node-row-custom").toggle(val==='custom');
|
||||
$(".node-row-reduce").toggle(val==='reduce');
|
||||
$(".form-tips-auto").toggle((val==='auto') || (val==='reduce'));
|
||||
if (val === "auto") {
|
||||
$("#node-input-accumulate").attr('checked', false);
|
||||
}
|
||||
else if (val === "custom") {
|
||||
$("#node-input-build").change();
|
||||
}
|
||||
else if (val === "reduce") {
|
||||
var jsonata_or_empty = {
|
||||
value: "jsonata",
|
||||
label: "expression",
|
||||
icon: "red/images/typedInput/expr.png",
|
||||
validate: function(v) {
|
||||
try{
|
||||
if(v !== "") {
|
||||
jsonata(v);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch(e){
|
||||
return false;
|
||||
}
|
||||
},
|
||||
expand:function() {
|
||||
var that = this;
|
||||
RED.editor.editExpression({
|
||||
value: this.value().replace(/\t/g,"\n"),
|
||||
complete: function(v) {
|
||||
that.value(v.replace(/\n/g,"\t"));
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
$("#node-input-reduceExp").typedInput({types:[jsonata_or_empty]});
|
||||
$("#node-input-reduceInit").typedInput({
|
||||
default: 'num',
|
||||
types:['flow','global','str','num','bool','json','bin','date','jsonata','env'],
|
||||
typeField: $("#node-input-reduceInitType")
|
||||
});
|
||||
$("#node-input-reduceFixup").typedInput({types:[jsonata_or_empty]});
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-build").on("change", function(e) {
|
||||
var val = $(this).val();
|
||||
$(".node-row-key").toggle(val==='object');
|
||||
$(".node-row-accumulate").toggle(val==='object' || val==='merged');
|
||||
$(".node-row-joiner").toggle(val==='string' || val==='buffer');
|
||||
$(".node-row-trigger").toggle(val!=='auto');
|
||||
if (val === 'string' || val==='buffer') {
|
||||
$("#node-input-property").typedInput('types',['msg']);
|
||||
$("#node-input-joiner").typedInput("show");
|
||||
} else {
|
||||
$("#node-input-property").typedInput('types', ['msg', {
|
||||
value: "full",
|
||||
label: RED._("node-red:join.completeMessage"),
|
||||
hasValue: false
|
||||
}]);
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-joiner").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-joinerType"),
|
||||
types:['str', 'bin']
|
||||
});
|
||||
|
||||
$("#node-input-property").typedInput({
|
||||
typeField: $("#node-input-propertyType"),
|
||||
types: ['msg', {
|
||||
value: "full",
|
||||
label: RED._("node-red:join.completeMessage"),
|
||||
hasValue: false
|
||||
}]
|
||||
});
|
||||
|
||||
$("#node-input-key").typedInput({
|
||||
types:['msg']
|
||||
});
|
||||
|
||||
$("#node-input-build").change();
|
||||
$("#node-input-mode").change();
|
||||
},
|
||||
oneditsave: function() {
|
||||
var build = $("#node-input-build").val();
|
||||
if (build !== 'object' && build !== 'merged') {
|
||||
$("#node-input-accumulate").prop("checked",false);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,785 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function sendArray(node,msg,array,send) {
|
||||
for (var i = 0; i < array.length-1; i++) {
|
||||
msg.payload = array[i];
|
||||
msg.parts.index = node.c++;
|
||||
if (node.stream !== true) { msg.parts.count = array.length; }
|
||||
send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
if (node.stream !== true) {
|
||||
msg.payload = array[i];
|
||||
msg.parts.index = node.c++;
|
||||
msg.parts.count = array.length;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
node.c = 0;
|
||||
}
|
||||
else { node.remainder = array[i]; }
|
||||
}
|
||||
|
||||
function SplitNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
node.stream = n.stream;
|
||||
node.spltType = n.spltType || "str";
|
||||
node.addname = n.addname || "";
|
||||
try {
|
||||
if (node.spltType === "str") {
|
||||
this.splt = (n.splt || "\\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
|
||||
} else if (node.spltType === "bin") {
|
||||
var spltArray = JSON.parse(n.splt);
|
||||
if (Array.isArray(spltArray)) {
|
||||
this.splt = Buffer.from(spltArray);
|
||||
} else {
|
||||
throw new Error("not an array");
|
||||
}
|
||||
this.spltBuffer = spltArray;
|
||||
} else if (node.spltType === "len") {
|
||||
this.splt = parseInt(n.splt);
|
||||
if (isNaN(this.splt) || this.splt < 1) {
|
||||
throw new Error("invalid split length: "+n.splt);
|
||||
}
|
||||
}
|
||||
this.arraySplt = (n.arraySplt === undefined)?1:parseInt(n.arraySplt);
|
||||
if (isNaN(this.arraySplt) || this.arraySplt < 1) {
|
||||
throw new Error("invalid array split length: "+n.arraySplt);
|
||||
}
|
||||
} catch(err) {
|
||||
this.error("Invalid split property: "+err.toString());
|
||||
return;
|
||||
}
|
||||
node.c = 0;
|
||||
node.buffer = Buffer.from([]);
|
||||
node.pendingDones = [];
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
|
||||
else { msg.parts = {}; }
|
||||
msg.parts.id = RED.util.generateId(); // generate a random id
|
||||
delete msg._msgid;
|
||||
if (typeof msg.payload === "string") { // Split String into array
|
||||
msg.payload = (node.remainder || "") + msg.payload;
|
||||
msg.parts.type = "string";
|
||||
if (node.spltType === "len") {
|
||||
msg.parts.ch = "";
|
||||
msg.parts.len = node.splt;
|
||||
var count = msg.payload.length/node.splt;
|
||||
if (Math.floor(count) !== count) {
|
||||
count = Math.ceil(count);
|
||||
}
|
||||
if (node.stream !== true) {
|
||||
msg.parts.count = count;
|
||||
node.c = 0;
|
||||
}
|
||||
var pos = 0;
|
||||
var data = msg.payload;
|
||||
for (var i=0; i<count-1; i++) {
|
||||
msg.payload = data.substring(pos,pos+node.splt);
|
||||
msg.parts.index = node.c++;
|
||||
pos += node.splt;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
if (count > 1) {
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
}
|
||||
node.remainder = data.substring(pos);
|
||||
if ((node.stream !== true) || (node.remainder.length === node.splt)) {
|
||||
msg.payload = node.remainder;
|
||||
msg.parts.index = node.c++;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
done();
|
||||
node.remainder = "";
|
||||
} else {
|
||||
node.pendingDones.push(done);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var a = [];
|
||||
if (node.spltType === "bin") {
|
||||
if (!node.spltBufferString) {
|
||||
node.spltBufferString = node.splt.toString();
|
||||
}
|
||||
a = msg.payload.split(node.spltBufferString);
|
||||
msg.parts.ch = node.spltBuffer; // pass the split char to other end for rejoin
|
||||
} else if (node.spltType === "str") {
|
||||
a = msg.payload.split(node.splt);
|
||||
msg.parts.ch = node.splt; // pass the split char to other end for rejoin
|
||||
}
|
||||
sendArray(node,msg,a,send);
|
||||
done();
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(msg.payload)) { // then split array into messages
|
||||
msg.parts.type = "array";
|
||||
var count = msg.payload.length/node.arraySplt;
|
||||
if (Math.floor(count) !== count) {
|
||||
count = Math.ceil(count);
|
||||
}
|
||||
msg.parts.count = count;
|
||||
var pos = 0;
|
||||
var data = msg.payload;
|
||||
msg.parts.len = node.arraySplt;
|
||||
for (var i=0; i<count; i++) {
|
||||
msg.payload = data.slice(pos,pos+node.arraySplt);
|
||||
if (node.arraySplt === 1) {
|
||||
msg.payload = msg.payload[0];
|
||||
}
|
||||
msg.parts.index = i;
|
||||
pos += node.arraySplt;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
done();
|
||||
}
|
||||
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
|
||||
var j = 0;
|
||||
var l = Object.keys(msg.payload).length;
|
||||
var pay = msg.payload;
|
||||
msg.parts.type = "object";
|
||||
for (var p in pay) {
|
||||
if (pay.hasOwnProperty(p)) {
|
||||
msg.payload = pay[p];
|
||||
if (node.addname !== "") {
|
||||
msg[node.addname] = p;
|
||||
}
|
||||
msg.parts.key = p;
|
||||
msg.parts.index = j;
|
||||
msg.parts.count = l;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
else if (Buffer.isBuffer(msg.payload)) {
|
||||
var len = node.buffer.length + msg.payload.length;
|
||||
var buff = Buffer.concat([node.buffer, msg.payload], len);
|
||||
msg.parts.type = "buffer";
|
||||
if (node.spltType === "len") {
|
||||
var count = buff.length/node.splt;
|
||||
if (Math.floor(count) !== count) {
|
||||
count = Math.ceil(count);
|
||||
}
|
||||
if (node.stream !== true) {
|
||||
msg.parts.count = count;
|
||||
node.c = 0;
|
||||
}
|
||||
var pos = 0;
|
||||
msg.parts.len = node.splt;
|
||||
for (var i=0; i<count-1; i++) {
|
||||
msg.payload = buff.slice(pos,pos+node.splt);
|
||||
msg.parts.index = node.c++;
|
||||
pos += node.splt;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
if (count > 1) {
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
}
|
||||
node.buffer = buff.slice(pos);
|
||||
if ((node.stream !== true) || (node.buffer.length === node.splt)) {
|
||||
msg.payload = node.buffer;
|
||||
msg.parts.index = node.c++;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
done();
|
||||
node.buffer = Buffer.from([]);
|
||||
} else {
|
||||
node.pendingDones.push(done);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var count = 0;
|
||||
if (node.spltType === "bin") {
|
||||
msg.parts.ch = node.spltBuffer;
|
||||
} else if (node.spltType === "str") {
|
||||
msg.parts.ch = node.splt;
|
||||
}
|
||||
var pos = buff.indexOf(node.splt);
|
||||
var end;
|
||||
while (pos > -1) {
|
||||
count++;
|
||||
end = pos+node.splt.length;
|
||||
pos = buff.indexOf(node.splt,end);
|
||||
}
|
||||
count++;
|
||||
if (node.stream !== true) {
|
||||
msg.parts.count = count;
|
||||
node.c = 0;
|
||||
}
|
||||
var i = 0, p = 0;
|
||||
pos = buff.indexOf(node.splt);
|
||||
while (pos > -1) {
|
||||
msg.payload = buff.slice(p,pos);
|
||||
msg.parts.index = node.c++;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
i++;
|
||||
p = pos+node.splt.length;
|
||||
pos = buff.indexOf(node.splt,p);
|
||||
}
|
||||
if (count > 1) {
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
}
|
||||
if ((node.stream !== true) && (p < buff.length)) {
|
||||
msg.payload = buff.slice(p,buff.length);
|
||||
msg.parts.index = node.c++;
|
||||
msg.parts.count = node.c++;
|
||||
send(RED.util.cloneMessage(msg));
|
||||
node.pendingDones.forEach(d => d());
|
||||
node.pendingDones = [];
|
||||
}
|
||||
else {
|
||||
node.buffer = buff.slice(p,buff.length);
|
||||
node.pendingDones.push(done);
|
||||
}
|
||||
if (node.buffer.length == 0) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
} else { // otherwise drop the message.
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("split",SplitNode);
|
||||
|
||||
|
||||
var _maxKeptMsgsCount;
|
||||
|
||||
function maxKeptMsgsCount(node) {
|
||||
if (_maxKeptMsgsCount === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_maxKeptMsgsCount = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_maxKeptMsgsCount = 0;
|
||||
}
|
||||
}
|
||||
return _maxKeptMsgsCount;
|
||||
}
|
||||
|
||||
function applyReduce(exp, accum, msg, index, count, done) {
|
||||
exp.assign("I", index);
|
||||
exp.assign("N", count);
|
||||
exp.assign("A", accum);
|
||||
RED.util.evaluateJSONataExpression(exp, msg, done);
|
||||
}
|
||||
|
||||
function exp_or_undefined(exp) {
|
||||
if((exp === "") ||
|
||||
(exp === null)) {
|
||||
return undefined;
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
|
||||
function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) {
|
||||
var msgInfo = msgInfos.shift();
|
||||
exp.assign("I", msgInfo.msg.parts.index);
|
||||
exp.assign("N", count);
|
||||
exp.assign("A", accumulator);
|
||||
RED.util.evaluateJSONataExpression(exp, msgInfo.msg, (err,result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (msgInfos.length === 0) {
|
||||
if (fixup) {
|
||||
fixup.assign("N", count);
|
||||
fixup.assign("A", result);
|
||||
RED.util.evaluateJSONataExpression(fixup, {}, (err, result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
msgInfo.send({payload: result});
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
msgInfo.send({payload: result});
|
||||
done();
|
||||
}
|
||||
} else {
|
||||
reduceMessageGroup(node,msgInfos,exp,fixup,count,result,done);
|
||||
}
|
||||
});
|
||||
}
|
||||
function reduceAndSendGroup(node, group, done) {
|
||||
var is_right = node.reduce_right;
|
||||
var flag = is_right ? -1 : 1;
|
||||
var msgInfos = group.msgs;
|
||||
const preservedMsgInfos = [...msgInfos];
|
||||
try {
|
||||
RED.util.evaluateNodeProperty(node.exp_init, node.exp_init_type, node, {}, (err,accum) => {
|
||||
var reduceExpression = node.reduceExpression;
|
||||
var fixupExpression = node.fixupExpression;
|
||||
var count = group.count;
|
||||
msgInfos.sort(function(x,y) {
|
||||
var ix = x.msg.parts.index;
|
||||
var iy = y.msg.parts.index;
|
||||
if (ix < iy) {return -flag;}
|
||||
if (ix > iy) {return flag;}
|
||||
return 0;
|
||||
});
|
||||
reduceMessageGroup(node, msgInfos,reduceExpression,fixupExpression,count,accum,(err,result) => {
|
||||
if (err) {
|
||||
preservedMsgInfos.pop(); // omit last message to emit error message
|
||||
preservedMsgInfos.forEach(mInfo => mInfo.done());
|
||||
done(err);
|
||||
return;
|
||||
} else {
|
||||
preservedMsgInfos.forEach(mInfo => mInfo.done());
|
||||
done();
|
||||
}
|
||||
})
|
||||
});
|
||||
} catch(err) {
|
||||
done(new Error(RED._("join.errors.invalid-expr",{error:err.message})));
|
||||
}
|
||||
}
|
||||
|
||||
function reduceMessage(node, msgInfo, done) {
|
||||
let msg = msgInfo.msg;
|
||||
if (msg.hasOwnProperty('parts')) {
|
||||
var parts = msg.parts;
|
||||
var pending = node.pending;
|
||||
var pending_count = node.pending_count;
|
||||
var gid = msg.parts.id;
|
||||
var count;
|
||||
if (!pending.hasOwnProperty(gid)) {
|
||||
if(parts.hasOwnProperty('count')) {
|
||||
count = msg.parts.count;
|
||||
}
|
||||
pending[gid] = {
|
||||
count: count,
|
||||
msgs: []
|
||||
};
|
||||
}
|
||||
var group = pending[gid];
|
||||
var msgs = group.msgs;
|
||||
if (parts.hasOwnProperty('count') && (group.count === undefined)) {
|
||||
group.count = parts.count;
|
||||
}
|
||||
msgs.push(msgInfo);
|
||||
pending_count++;
|
||||
var completeProcess = function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
node.pending_count = pending_count;
|
||||
var max_msgs = maxKeptMsgsCount(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
Object.values(node.pending).forEach(group => {
|
||||
group.msgs.forEach(mInfo => {
|
||||
if (mInfo.msg._msgid !== msgInfo.msg._msgid) {
|
||||
mInfo.done();
|
||||
}
|
||||
});
|
||||
});
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
done(RED._("join.too-many"));
|
||||
return;
|
||||
}
|
||||
return done();
|
||||
}
|
||||
if (msgs.length === group.count) {
|
||||
delete pending[gid];
|
||||
pending_count -= msgs.length;
|
||||
reduceAndSendGroup(node, group, completeProcess)
|
||||
} else {
|
||||
completeProcess();
|
||||
}
|
||||
} else {
|
||||
msgInfo.send(msg);
|
||||
msgInfo.done();
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function JoinNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.mode = n.mode||"auto";
|
||||
this.property = n.property||"payload";
|
||||
this.propertyType = n.propertyType||"msg";
|
||||
if (this.propertyType === 'full') {
|
||||
this.property = "payload";
|
||||
}
|
||||
this.key = n.key||"topic";
|
||||
this.timer = (this.mode === "auto") ? 0 : Number(n.timeout || 0)*1000;
|
||||
this.count = Number(n.count || 0);
|
||||
this.joiner = n.joiner||"";
|
||||
this.joinerType = n.joinerType||"str";
|
||||
|
||||
this.reduce = (this.mode === "reduce");
|
||||
if (this.reduce) {
|
||||
this.exp_init = n.reduceInit;
|
||||
this.exp_init_type = n.reduceInitType;
|
||||
var exp_reduce = n.reduceExp;
|
||||
var exp_fixup = exp_or_undefined(n.reduceFixup);
|
||||
this.reduce_right = n.reduceRight;
|
||||
try {
|
||||
this.reduceExpression = RED.util.prepareJSONataExpression(exp_reduce, this);
|
||||
this.fixupExpression = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
|
||||
} catch(e) {
|
||||
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.joinerType === "str") {
|
||||
this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
|
||||
} else if (this.joinerType === "bin") {
|
||||
var joinArray = JSON.parse(n.joiner || "[]");
|
||||
if (Array.isArray(joinArray)) {
|
||||
this.joiner = Buffer.from(joinArray);
|
||||
} else {
|
||||
throw new Error("not an array");
|
||||
}
|
||||
}
|
||||
|
||||
this.build = n.build || "array";
|
||||
this.accumulate = n.accumulate || "false";
|
||||
|
||||
this.output = n.output || "stream";
|
||||
this.pending = {};
|
||||
this.pending_count = 0;
|
||||
|
||||
//this.topic = n.topic;
|
||||
var node = this;
|
||||
var inflight = {};
|
||||
|
||||
var completeSend = function(partId) {
|
||||
var group = inflight[partId];
|
||||
if (group.timeout) { clearTimeout(group.timeout); }
|
||||
if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
|
||||
if (group.type === 'array' && group.arrayLen > 1) {
|
||||
var newArray = [];
|
||||
group.payload.forEach(function(n) {
|
||||
newArray = newArray.concat(n);
|
||||
})
|
||||
group.payload = newArray;
|
||||
}
|
||||
else if (group.type === 'buffer') {
|
||||
var buffers = [];
|
||||
var bufferLen = 0;
|
||||
if (group.joinChar !== undefined) {
|
||||
var joinBuffer = Buffer.from(group.joinChar);
|
||||
for (var i=0; i<group.payload.length; i++) {
|
||||
if (i > 0) {
|
||||
buffers.push(joinBuffer);
|
||||
bufferLen += joinBuffer.length;
|
||||
}
|
||||
if (!Buffer.isBuffer(group.payload[i])) {
|
||||
group.payload[i] = Buffer.from(group.payload[i]);
|
||||
}
|
||||
buffers.push(group.payload[i]);
|
||||
bufferLen += group.payload[i].length;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bufferLen = group.bufferLen;
|
||||
buffers = group.payload;
|
||||
}
|
||||
group.payload = Buffer.concat(buffers,bufferLen);
|
||||
}
|
||||
|
||||
if (group.type === 'string') {
|
||||
var groupJoinChar = group.joinChar;
|
||||
if (typeof group.joinChar !== 'string') {
|
||||
groupJoinChar = group.joinChar.toString();
|
||||
}
|
||||
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
|
||||
}
|
||||
else {
|
||||
if (node.propertyType === 'full') {
|
||||
group.msg = RED.util.cloneMessage(group.msg);
|
||||
}
|
||||
RED.util.setMessageProperty(group.msg,node.property,group.payload);
|
||||
}
|
||||
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
|
||||
group.msg.parts = group.msg.parts.parts;
|
||||
}
|
||||
else {
|
||||
delete group.msg.parts;
|
||||
}
|
||||
delete group.msg.complete;
|
||||
group.send(RED.util.cloneMessage(group.msg));
|
||||
group.dones.forEach(f => f());
|
||||
group.dones = [];
|
||||
}
|
||||
|
||||
var pendingMessages = [];
|
||||
var activeMessage = null;
|
||||
// In reduce mode, we must process messages fully in order otherwise
|
||||
// groups may overlap and cause unexpected results. The use of JSONata
|
||||
// means some async processing *might* occur if flow/global context is
|
||||
// accessed.
|
||||
var processReduceMessageQueue = function(msgInfo) {
|
||||
if (msgInfo) {
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msgInfo);
|
||||
if (activeMessage !== null) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
activeMessage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsgInfo = pendingMessages.shift();
|
||||
activeMessage = true;
|
||||
reduceMessage(node, nextMsgInfo, err => {
|
||||
if (err) {
|
||||
nextMsgInfo.done(err);//.error(err,nextMsg);
|
||||
}
|
||||
activeMessage = null;
|
||||
processReduceMessageQueue();
|
||||
})
|
||||
}
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
try {
|
||||
var property;
|
||||
var partId = "_";
|
||||
if (node.propertyType == "full") {
|
||||
property = msg;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
property = RED.util.getMessageProperty(msg,node.property);
|
||||
} catch(err) {
|
||||
node.warn("Message property "+node.property+" not found");
|
||||
done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) {
|
||||
// if a blank reset messag erest it all.
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (inflight && inflight.hasOwnProperty("partId") && inflight[partId].timeout) {
|
||||
clearTimeout(inflight[partId].timeout);
|
||||
}
|
||||
inflight = {};
|
||||
}
|
||||
else {
|
||||
node.warn("Message missing msg.parts property - cannot join in 'auto' mode")
|
||||
}
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
var payloadType;
|
||||
var propertyKey;
|
||||
var targetCount;
|
||||
var joinChar;
|
||||
var arrayLen;
|
||||
var propertyIndex;
|
||||
if (node.mode === "auto") {
|
||||
// Use msg.parts to identify all of the group information
|
||||
partId = msg.parts.id;
|
||||
payloadType = msg.parts.type;
|
||||
targetCount = msg.parts.count;
|
||||
joinChar = msg.parts.ch;
|
||||
propertyKey = msg.parts.key;
|
||||
arrayLen = msg.parts.len;
|
||||
propertyIndex = msg.parts.index;
|
||||
}
|
||||
else if (node.mode === 'reduce') {
|
||||
return processReduceMessageQueue({msg, send, done});
|
||||
}
|
||||
else {
|
||||
// Use the node configuration to identify all of the group information
|
||||
payloadType = node.build;
|
||||
targetCount = node.count;
|
||||
joinChar = node.joiner;
|
||||
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
||||
targetCount = msg.parts.count || 0;
|
||||
}
|
||||
if (node.build === 'object') {
|
||||
propertyKey = RED.util.getMessageProperty(msg,node.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.hasOwnProperty("restartTimeout")) {
|
||||
if (inflight[partId]) {
|
||||
if (inflight[partId].timeout) {
|
||||
clearTimeout(inflight[partId].timeout);
|
||||
}
|
||||
if (node.timer > 0) {
|
||||
inflight[partId].timeout = setTimeout(function() {
|
||||
completeSend(partId)
|
||||
}, node.timer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (inflight[partId]) {
|
||||
if (inflight[partId].timeout) {
|
||||
clearTimeout(inflight[partId].timeout);
|
||||
}
|
||||
inflight[partId].dones.forEach(f => f());
|
||||
delete inflight[partId]
|
||||
}
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((payloadType === 'object') && (propertyKey === null || propertyKey === undefined || propertyKey === "")) {
|
||||
if (node.mode === "auto") {
|
||||
node.warn("Message missing 'msg.parts.key' property - cannot add to object");
|
||||
}
|
||||
else {
|
||||
if (msg.hasOwnProperty('complete')) {
|
||||
if (inflight[partId]) {
|
||||
inflight[partId].msg.complete = msg.complete;
|
||||
inflight[partId].send = send;
|
||||
completeSend(partId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
|
||||
}
|
||||
}
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inflight.hasOwnProperty(partId)) {
|
||||
if (payloadType === 'object' || payloadType === 'merged') {
|
||||
inflight[partId] = {
|
||||
currentCount:0,
|
||||
payload:{},
|
||||
targetCount:targetCount,
|
||||
type:"object",
|
||||
msg:RED.util.cloneMessage(msg),
|
||||
send: send,
|
||||
dones: []
|
||||
};
|
||||
}
|
||||
else {
|
||||
inflight[partId] = {
|
||||
currentCount:0,
|
||||
payload:[],
|
||||
targetCount:targetCount,
|
||||
type:payloadType,
|
||||
msg:RED.util.cloneMessage(msg),
|
||||
send: send,
|
||||
dones: []
|
||||
};
|
||||
if (payloadType === 'string') {
|
||||
inflight[partId].joinChar = joinChar;
|
||||
} else if (payloadType === 'array') {
|
||||
inflight[partId].arrayLen = arrayLen;
|
||||
} else if (payloadType === 'buffer') {
|
||||
inflight[partId].bufferLen = 0;
|
||||
inflight[partId].joinChar = joinChar;
|
||||
}
|
||||
}
|
||||
if (node.timer > 0) {
|
||||
inflight[partId].timeout = setTimeout(function() {
|
||||
completeSend(partId)
|
||||
}, node.timer)
|
||||
}
|
||||
}
|
||||
inflight[partId].dones.push(done);
|
||||
|
||||
var group = inflight[partId];
|
||||
if (payloadType === 'buffer') {
|
||||
if (property !== undefined) {
|
||||
if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) {
|
||||
inflight[partId].bufferLen += property.length;
|
||||
}
|
||||
else {
|
||||
done(RED._("join.errors.invalid-type",{error:(typeof property)}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payloadType === 'object') {
|
||||
group.payload[propertyKey] = property;
|
||||
group.currentCount = Object.keys(group.payload).length;
|
||||
} else if (payloadType === 'merged') {
|
||||
if (Array.isArray(property) || typeof property !== 'object') {
|
||||
if (!msg.hasOwnProperty("complete")) {
|
||||
node.warn("Cannot merge non-object types");
|
||||
}
|
||||
} else {
|
||||
for (propertyKey in property) {
|
||||
if (property.hasOwnProperty(propertyKey) && propertyKey !== '_msgid') {
|
||||
group.payload[propertyKey] = property[propertyKey];
|
||||
}
|
||||
}
|
||||
group.currentCount = Object.keys(group.payload).length;
|
||||
//group.currentCount++;
|
||||
}
|
||||
} else {
|
||||
if (!isNaN(propertyIndex)) {
|
||||
if (group.payload[propertyIndex] == undefined) { group.currentCount++; }
|
||||
group.payload[propertyIndex] = property;
|
||||
} else {
|
||||
if (property !== undefined) {
|
||||
group.payload.push(property);
|
||||
group.currentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
group.msg = Object.assign(group.msg, msg);
|
||||
group.send = send;
|
||||
var tcnt = group.targetCount;
|
||||
if (msg.hasOwnProperty("parts")) {
|
||||
tcnt = group.targetCount || msg.parts.count;
|
||||
group.targetCount = tcnt;
|
||||
}
|
||||
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
|
||||
completeSend(partId);
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
done(err);
|
||||
console.log(err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
for (var i in inflight) {
|
||||
if (inflight.hasOwnProperty(i)) {
|
||||
clearTimeout(inflight[i].timeout);
|
||||
inflight[i].dones.forEach(d => d());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("join",JoinNode);
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="sort">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-target"><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.target"></span></label>
|
||||
<input type="text" id="node-input-target" style="width:70%;">
|
||||
<input type="hidden" id="node-input-targetType">
|
||||
</div>
|
||||
|
||||
<div class="node-row-sort-msg-key">
|
||||
<div class="form-row">
|
||||
<label for="node-input-msgKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
|
||||
<input type="text" id="node-input-msgKey" style="width:70%;">
|
||||
<input type="hidden" id="node-input-msgKeyType">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-row-sort-seq-key">
|
||||
<div class="form-row">
|
||||
<label for="node-input-seqKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
|
||||
<input type="text" id="node-input-seqKey" style="width:70%;">
|
||||
<input type="hidden" id="node-input-seqKeyType">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-random"></i> <span data-i18n="sort.order"></span></label>
|
||||
<select id="node-input-order" style="width:200px;">
|
||||
<option value="ascending" data-i18n="sort.ascending"></option>
|
||||
<option value="descending" data-i18n="sort.descending"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-as_num">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-as_num" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-as_num" style="width: 70%;" data-i18n="sort.as-number"></label>
|
||||
</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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('sort',{
|
||||
category: 'sequence',
|
||||
color:"#E2D96E",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
order: { value:"ascending" },
|
||||
as_num: { value:false },
|
||||
target: { value:"payload" },
|
||||
targetType: { value:"msg" },
|
||||
msgKey: { value:"payload" },
|
||||
msgKeyType: { value:"elem" },
|
||||
seqKey: { value:"payload" },
|
||||
seqKeyType: { value:"msg" }
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "sort.svg",
|
||||
label: function() {
|
||||
return this.name||this._("sort.sort");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var seq = {
|
||||
value: "seq",
|
||||
label: RED._("node-red:sort.seq"),
|
||||
hasValue: false
|
||||
};
|
||||
var elem = {
|
||||
value: "elem",
|
||||
label: RED._("node-red:sort.elem"),
|
||||
hasValue: false
|
||||
};
|
||||
$("#node-input-target").typedInput({
|
||||
default:'msg',
|
||||
typeField: $("#node-input-targetType"),
|
||||
types:['msg', seq]
|
||||
});
|
||||
$("#node-input-msgKey").typedInput({
|
||||
default:'elem',
|
||||
typeField: $("#node-input-msgKeyType"),
|
||||
types:[elem, 'jsonata']
|
||||
});
|
||||
$("#node-input-seqKey").typedInput({
|
||||
default:'msg',
|
||||
typeField: $("#node-input-seqKeyType"),
|
||||
types:['msg', 'jsonata']
|
||||
});
|
||||
$("#node-input-target").on("change", function(e) {
|
||||
var val = $("#node-input-target").typedInput('type');
|
||||
$(".node-row-sort-msg-key").toggle(val === "msg");
|
||||
$(".node-row-sort-seq-key").toggle(val === "seq");
|
||||
});
|
||||
$("#node-input-target").change();
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,266 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var _max_kept_msgs_count;
|
||||
|
||||
function max_kept_msgs_count(node) {
|
||||
if (_max_kept_msgs_count === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_max_kept_msgs_count = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_max_kept_msgs_count = 0;
|
||||
}
|
||||
}
|
||||
return _max_kept_msgs_count;
|
||||
}
|
||||
|
||||
// function get_context_val(node, name, dval) {
|
||||
// var context = node.context();
|
||||
// var val = context.get(name);
|
||||
// if (val === undefined) {
|
||||
// context.set(name, dval);
|
||||
// return dval;
|
||||
// }
|
||||
// return val;
|
||||
// }
|
||||
|
||||
function SortNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
var node = this;
|
||||
var pending = {};//get_context_val(node, 'pending', {})
|
||||
var pending_count = 0;
|
||||
var pending_id = 0;
|
||||
var order = n.order || "ascending";
|
||||
var as_num = n.as_num || false;
|
||||
var target_prop = n.target || "payload";
|
||||
var target_is_prop = (n.targetType === 'msg');
|
||||
var key_is_exp = target_is_prop ? (n.msgKeyType === "jsonata") : (n.seqKeyType === "jsonata");
|
||||
var key_prop = n.seqKey || "payload";
|
||||
var key_exp = target_is_prop ? n.msgKey : n.seqKey;
|
||||
|
||||
if (key_is_exp) {
|
||||
try {
|
||||
key_exp = RED.util.prepareJSONataExpression(key_exp, this);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("sort.invalid-exp",{message:e.toString()}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
var dir = (order === "ascending") ? 1 : -1;
|
||||
var conv = as_num ? function(x) { return Number(x); }
|
||||
: function(x) { return x; };
|
||||
|
||||
function generateComparisonFunction(key) {
|
||||
return function(x, y) {
|
||||
var xp = conv(key(x));
|
||||
var yp = conv(key(y));
|
||||
if (xp === yp) { return 0; }
|
||||
if (xp > yp) { return dir; }
|
||||
return -dir;
|
||||
};
|
||||
}
|
||||
|
||||
function sortMessageGroup(group) {
|
||||
var promise;
|
||||
var msgInfos = group.msgInfos;
|
||||
if (key_is_exp) {
|
||||
var evaluatedDataPromises = msgInfos.map(mInfo => {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(key_exp, mInfo.msg, (err, result) => {
|
||||
if (err) {
|
||||
reject(RED._("sort.invalid-exp",{message:err.toString()}));
|
||||
} else {
|
||||
resolve({
|
||||
item: mInfo,
|
||||
sortValue: result
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
promise = Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||
// Once all of the sort keys are evaluated, sort by them
|
||||
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||
return evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||
});
|
||||
} else {
|
||||
var key = function(msg) {
|
||||
return ;
|
||||
}
|
||||
var comp = generateComparisonFunction(mInfo => RED.util.getMessageProperty(mInfo.msg, key_prop));
|
||||
try {
|
||||
msgInfos.sort(comp);
|
||||
}
|
||||
catch (e) {
|
||||
return; // not send when error
|
||||
}
|
||||
promise = Promise.resolve(msgInfos);
|
||||
}
|
||||
return promise.then(msgInfos => {
|
||||
for (let i = 0; i < msgInfos.length; i++) {
|
||||
const msg = msgInfos[i].msg;
|
||||
msg.parts.index = i;
|
||||
msgInfos[i].send(msg);
|
||||
msgInfos[i].done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sortMessageProperty(msg) {
|
||||
var data = RED.util.getMessageProperty(msg, target_prop);
|
||||
if (Array.isArray(data)) {
|
||||
if (key_is_exp) {
|
||||
// key is an expression. Evaluated the expression for each item
|
||||
// to get its sort value. As this could be async, need to do
|
||||
// it first.
|
||||
var evaluatedDataPromises = data.map(elem => {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(key_exp, elem, (err, result) => {
|
||||
if (err) {
|
||||
reject(RED._("sort.invalid-exp",{message:err.toString()}));
|
||||
} else {
|
||||
resolve({
|
||||
item: elem,
|
||||
sortValue: result
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
return Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||
// Once all of the sort keys are evaluated, sort by them
|
||||
// and reconstruct the original message item with the newly
|
||||
// sorted values.
|
||||
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||
data = evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||
RED.util.setMessageProperty(msg, target_prop,data);
|
||||
return true;
|
||||
})
|
||||
} else {
|
||||
var comp = generateComparisonFunction(elem=>elem);
|
||||
try {
|
||||
data.sort(comp);
|
||||
} catch (e) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
function removeOldestPending() {
|
||||
var oldest;
|
||||
var oldest_key;
|
||||
for(var key in pending) {
|
||||
if (pending.hasOwnProperty(key)) {
|
||||
var item = pending[key];
|
||||
if((oldest === undefined) ||
|
||||
(oldest.seq_no > item.seq_no)) {
|
||||
oldest = item;
|
||||
oldest_key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(oldest !== undefined) {
|
||||
oldest.msgInfos[oldest.msgInfos.length - 1].done(RED._("sort.too-many"));
|
||||
for (let i = 0; i < oldest.msgInfos.length - 1; i++) {
|
||||
oldest.msgInfos[i].done();
|
||||
}
|
||||
delete pending[oldest_key];
|
||||
return oldest.msgInfos.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function processMessage(msgInfo) {
|
||||
const msg = msgInfo.msg;
|
||||
if (target_is_prop) {
|
||||
sortMessageProperty(msg).then(send => {
|
||||
if (send) {
|
||||
msgInfo.send(msg);
|
||||
}
|
||||
msgInfo.done();
|
||||
}).catch(err => {
|
||||
msgInfo.done(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
var parts = msg.parts;
|
||||
if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
|
||||
msgInfo.done();
|
||||
return;
|
||||
}
|
||||
var gid = parts.id;
|
||||
if (!pending.hasOwnProperty(gid)) {
|
||||
pending[gid] = {
|
||||
count: undefined,
|
||||
msgInfos: [],
|
||||
seq_no: pending_id++
|
||||
};
|
||||
}
|
||||
var group = pending[gid];
|
||||
var msgInfos = group.msgInfos;
|
||||
msgInfos.push(msgInfo);
|
||||
if (parts.hasOwnProperty("count")) {
|
||||
group.count = parts.count;
|
||||
}
|
||||
pending_count++;
|
||||
if (group.count === msgInfos.length) {
|
||||
delete pending[gid]
|
||||
sortMessageGroup(group).catch(err => {
|
||||
// throw an error for last message, and just call done() for remaining messages
|
||||
msgInfos[msgInfos.length-1].done(err);
|
||||
for (let i = 0; i < msgInfos.length - 1; i++) {
|
||||
msgInfos[i].done()
|
||||
};
|
||||
});
|
||||
pending_count -= msgInfos.length;
|
||||
} else {
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
pending_count -= removeOldestPending();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
processMessage({msg, send, done});
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
for(var key in pending) {
|
||||
if (pending.hasOwnProperty(key)) {
|
||||
node.log(RED._("sort.clear"), pending[key].msgInfos[0]);
|
||||
const group = pending[key];
|
||||
group.msgInfos.forEach(mInfo => {
|
||||
mInfo.done();
|
||||
});
|
||||
delete pending[key];
|
||||
}
|
||||
}
|
||||
pending_count = 0;
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("sort", SortNode);
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="batch">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mode"><span data-i18n="batch.mode.label"></span></label>
|
||||
<select type="text" id="node-input-mode" style="width: 300px;">
|
||||
<option value="count" data-i18n="batch.mode.num-msgs"></option>
|
||||
<option value="interval" data-i18n="batch.mode.interval"></option>
|
||||
<option value="concat" data-i18n="batch.mode.concat"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="node-row-msg-count">
|
||||
<div class="form-row node-row-count">
|
||||
<label style="margin-left: 10px; width: 175px;" for="node-input-count" data-i18n="batch.count.label"></label>
|
||||
<input type="text" id="node-input-count" style="width: 50px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-row-msg-overlap">
|
||||
<div class="form-row node-row-overlap">
|
||||
<label style="margin-left: 10px; width: 175px;" for="node-input-overlap" data-i18n="batch.count.overlap"></label>
|
||||
<input type="text" id="node-input-overlap" style="width: 50px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-row-msg-interval">
|
||||
<div class="form-row node-row-interval">
|
||||
<label style="margin-left: 10px; width: 175px;" for="node-input-interval"> <span data-i18n="batch.interval.label"></span></label>
|
||||
<input type="text" id="node-input-interval" style="width: 50px;">
|
||||
<span data-i18n="batch.interval.seconds"></span>
|
||||
</div>
|
||||
<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 class="node-row-msg-concat">
|
||||
<div class="form-row">
|
||||
<label data-i18n="batch.concat.topics-label"></label>
|
||||
<div class="form-row node-input-topics-container-row">
|
||||
<ol id="node-input-topics-container"></ol>
|
||||
</div>
|
||||
</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">
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType("batch",{
|
||||
category: "sequence",
|
||||
color:"#E2D96E",
|
||||
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); }},
|
||||
allowEmptySequence: {value:false},
|
||||
topics: {value:[{topic:""}]}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "batch.svg",
|
||||
label: function() {
|
||||
return this.name||this._("batch.batch");;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var topic_str = node._("batch.concat.topic");
|
||||
|
||||
function resizeTopics(topic) {
|
||||
var newWidth = topic.width();
|
||||
topic.find('.red-ui-typedInput')
|
||||
.typedInput("width",newWidth-15);
|
||||
}
|
||||
|
||||
$("#node-input-topics-container")
|
||||
.css('min-height','150px').css('min-width','430px')
|
||||
.editableList({
|
||||
addItem: function(container, i, opt) {
|
||||
if (!opt.hasOwnProperty('topic')) {
|
||||
opt.topic = "";
|
||||
}
|
||||
var row = $('<div/>').appendTo(container);
|
||||
var valueField = $('<input/>',{
|
||||
class:"node-input-topic-value",
|
||||
type:"text",
|
||||
style:"margin-left: 5px;"
|
||||
}).appendTo(row)
|
||||
.typedInput({default:'str', types:['str']});
|
||||
valueField.typedInput('value', opt.topic);
|
||||
valueField.typedInput('type', 'str');
|
||||
valueField.attr('placeholder', topic_str);
|
||||
resizeTopics(container);
|
||||
},
|
||||
resizeItem: resizeTopics,
|
||||
sortable: true,
|
||||
removable: true
|
||||
});
|
||||
|
||||
$("#node-input-count").spinner({min:1});
|
||||
$("#node-input-overlap").spinner({min:0});
|
||||
$("#node-input-interval").spinner({min:1});
|
||||
$("#node-input-mode").on("change", function(e) {
|
||||
var val = $(this).val();
|
||||
$(".node-row-msg-count").toggle(val==="count");
|
||||
$(".node-row-msg-overlap").toggle(val==="count");
|
||||
$(".node-row-msg-interval").toggle(val==="interval");
|
||||
$(".node-row-msg-concat").toggle(val==="concat");
|
||||
if (val==="concat") {
|
||||
var topics = node.topics;
|
||||
var container = $("#node-input-topics-container");
|
||||
container.editableList('empty');
|
||||
for (var i = 0; i < topics.length; i++) {
|
||||
var topic = topics[i];
|
||||
container.editableList('addItem', topic);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var topics = $("#node-input-topics-container").editableList('items');
|
||||
var node = this;
|
||||
node.topics = [];
|
||||
topics.each(function(i) {
|
||||
var topicData = $(this).data('data');
|
||||
var topic = $(this);
|
||||
var vf = topic.find(".node-input-topic-value");
|
||||
var value = vf.typedInput('value');
|
||||
var type = vf.typedInput('type');
|
||||
var r = {topic:value};
|
||||
node.topics.push(r);
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-topics-container-row)");
|
||||
var height = size.height;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-topics-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-topics-container").editableList('height',height);
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,311 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var _max_kept_msgs_count = undefined;
|
||||
|
||||
function max_kept_msgs_count(node) {
|
||||
if (_max_kept_msgs_count === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_max_kept_msgs_count = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_max_kept_msgs_count = 0;
|
||||
}
|
||||
}
|
||||
return _max_kept_msgs_count;
|
||||
}
|
||||
|
||||
function send_msgs(node, msgInfos, clone_msg) {
|
||||
var count = msgInfos.length;
|
||||
var msg_id = msgInfos[0].msg._msgid;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var msg = clone_msg ? RED.util.cloneMessage(msgInfos[i].msg) : msgInfos[i].msg;
|
||||
if (!msg.hasOwnProperty("parts")) {
|
||||
msg.parts = {};
|
||||
}
|
||||
var parts = msg.parts;
|
||||
parts.id = msg_id;
|
||||
parts.index = i;
|
||||
parts.count = count;
|
||||
msgInfos[i].send(msg);
|
||||
//msgInfos[i].done();
|
||||
}
|
||||
}
|
||||
|
||||
function send_interval(node, allow_empty_seq) {
|
||||
let msgInfos = node.pending;
|
||||
if (msgInfos.length > 0) {
|
||||
send_msgs(node, msgInfos, false);
|
||||
msgInfos.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
}
|
||||
else {
|
||||
if (allow_empty_seq) {
|
||||
let mid = RED.util.generateId();
|
||||
let msg = {
|
||||
payload: null,
|
||||
parts: {
|
||||
id: mid,
|
||||
index: 0,
|
||||
count: 1
|
||||
}
|
||||
};
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_complete(pending, topic) {
|
||||
if (pending.hasOwnProperty(topic)) {
|
||||
var p_topic = pending[topic];
|
||||
var gids = p_topic.gids;
|
||||
if (gids.length > 0) {
|
||||
var gid = gids[0];
|
||||
var groups = p_topic.groups;
|
||||
var group = groups[gid];
|
||||
return (group.count === group.msgs.length);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_msgs_of_topic(pending, topic) {
|
||||
var p_topic = pending[topic];
|
||||
var groups = p_topic.groups;
|
||||
var gids = p_topic.gids;
|
||||
var gid = gids[0];
|
||||
var group = groups[gid];
|
||||
return group.msgs;
|
||||
}
|
||||
|
||||
function remove_topic(pending, topic) {
|
||||
var p_topic = pending[topic];
|
||||
var groups = p_topic.groups;
|
||||
var gids = p_topic.gids;
|
||||
var gid = gids.shift();
|
||||
delete groups[gid];
|
||||
}
|
||||
|
||||
function try_concat(node, pending) {
|
||||
var topics = node.topics;
|
||||
for (var topic of topics) {
|
||||
if (!is_complete(pending, topic)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var msgInfos = [];
|
||||
for (var topic of topics) {
|
||||
var t_msgInfos = get_msgs_of_topic(pending, topic);
|
||||
msgInfos = msgInfos.concat(t_msgInfos);
|
||||
}
|
||||
for (var topic of topics) {
|
||||
remove_topic(pending, topic);
|
||||
}
|
||||
send_msgs(node, msgInfos, true);
|
||||
msgInfos.forEach(e => e.done() );
|
||||
node.pending_count -= msgInfos.length;
|
||||
}
|
||||
|
||||
function add_to_topic_group(pending, topic, gid, msgInfo) {
|
||||
if (!pending.hasOwnProperty(topic)) {
|
||||
pending[topic] = { groups: {}, gids: [] };
|
||||
}
|
||||
var p_topic = pending[topic];
|
||||
var groups = p_topic.groups;
|
||||
var gids = p_topic.gids;
|
||||
if (!groups.hasOwnProperty(gid)) {
|
||||
groups[gid] = { msgs: [], count: undefined };
|
||||
gids.push(gid);
|
||||
}
|
||||
var group = groups[gid];
|
||||
group.msgs.push(msgInfo);
|
||||
if ((group.count === undefined) &&
|
||||
msgInfo.msg.parts.hasOwnProperty('count')) {
|
||||
group.count = msgInfo.msg.parts.count;
|
||||
}
|
||||
}
|
||||
|
||||
function concat_msg(node, msg, send, done) {
|
||||
var topic = msg.topic;
|
||||
if(node.topics.indexOf(topic) >= 0) {
|
||||
if (!msg.hasOwnProperty("parts") ||
|
||||
!msg.parts.hasOwnProperty("id") ||
|
||||
!msg.parts.hasOwnProperty("index") ||
|
||||
!msg.parts.hasOwnProperty("count")) {
|
||||
done(RED._("batch.no-parts"));
|
||||
return;
|
||||
}
|
||||
var gid = msg.parts.id;
|
||||
var pending = node.pending;
|
||||
add_to_topic_group(pending, topic, gid, {msg, send, done});
|
||||
node.pending_count++;
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
|
||||
Object.values(node.pending).forEach(p_topic => {
|
||||
Object.values(p_topic.groups).forEach(group => {
|
||||
group.msgs.forEach(msgInfo => {
|
||||
if (msgInfo.msg.id === msg.id) {
|
||||
// the message that caused the overflow
|
||||
msgInfo.done(RED._("batch.too-many"));
|
||||
} else {
|
||||
msgInfo.done();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
}
|
||||
try_concat(node, pending);
|
||||
}
|
||||
}
|
||||
|
||||
function BatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
var mode = n.mode || "count";
|
||||
|
||||
node.pending_count = 0;
|
||||
if (mode === "count") {
|
||||
var count = Number(n.count || 1);
|
||||
var overlap = Number(n.overlap || 0);
|
||||
var is_overlap = (overlap > 0);
|
||||
if (count <= overlap) {
|
||||
node.error(RED._("batch.count.invalid"));
|
||||
return;
|
||||
}
|
||||
node.pending = [];
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
node.pending.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
node.pending_count = 0;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
var queue = node.pending;
|
||||
queue.push({msg, send, done});
|
||||
node.pending_count++;
|
||||
if (queue.length === count) {
|
||||
send_msgs(node, queue, is_overlap);
|
||||
for (let i = 0; i < queue.length-overlap; i++) {
|
||||
queue[i].done();
|
||||
}
|
||||
node.pending =
|
||||
(overlap === 0) ? [] : queue.slice(-overlap);
|
||||
node.pending_count = 0;
|
||||
}
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
|
||||
let lastMInfo = node.pending.pop();
|
||||
lastMInfo.done(RED._("batch.too-many"));
|
||||
node.pending.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
node.pending_count = 0;
|
||||
}
|
||||
});
|
||||
this.on("close", function() {
|
||||
node.pending.forEach(e=> e.done());
|
||||
node.pending_count = 0;
|
||||
node.pending = [];
|
||||
});
|
||||
}
|
||||
else if (mode === "interval") {
|
||||
var interval = Number(n.interval || "0") *1000;
|
||||
var allow_empty_seq = n.allowEmptySequence;
|
||||
node.pending = []
|
||||
function msgHandler() {
|
||||
send_interval(node, allow_empty_seq);
|
||||
node.pending_count = 0;
|
||||
}
|
||||
var timer = undefined;
|
||||
if (interval > 0) {
|
||||
timer = setInterval(msgHandler, interval);
|
||||
}
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (timer !== undefined) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
node.pending.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
node.pending_count = 0;
|
||||
done();
|
||||
if (interval > 0) {
|
||||
timer = setInterval(msgHandler, interval);
|
||||
}
|
||||
return;
|
||||
}
|
||||
node.pending.push({msg, send, done});
|
||||
node.pending_count++;
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
|
||||
let lastMInfo = node.pending.pop();
|
||||
lastMInfo.done(RED._("batch.too-many"));
|
||||
node.pending.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
node.pending_count = 0;
|
||||
}
|
||||
});
|
||||
this.on("close", function() {
|
||||
if (timer !== undefined) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
node.pending.forEach(e => e.done());
|
||||
node.pending = [];
|
||||
node.pending_count = 0;
|
||||
});
|
||||
}
|
||||
else if(mode === "concat") {
|
||||
node.topics = (n.topics || []).map(function(x) {
|
||||
return x.topic;
|
||||
});
|
||||
node.pending = {};
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
Object.values(node.pending).forEach(p_topic => {
|
||||
Object.values(p_topic.groups).forEach(group => {
|
||||
group.msgs.forEach(e => e.done());
|
||||
});
|
||||
});
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
concat_msg(node, msg, send, done);
|
||||
});
|
||||
this.on("close", function() {
|
||||
Object.values(node.pending).forEach(p_topic => {
|
||||
Object.values(p_topic.groups).forEach(group => {
|
||||
group.msgs.forEach(e => e.done());
|
||||
});
|
||||
});
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.error(RED._("batch.unexpected"));
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("batch", BatchNode);
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
|
||||
<script type="text/html" data-template-name="file">
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
|
||||
<select type="text" id="node-input-overwriteFile" style="width: 250px;">
|
||||
<option value="false" data-i18n="file.action.append"></option>
|
||||
<option value="true" data-i18n="file.action.overwrite"></option>
|
||||
<option value="delete" data-i18n="file.action.delete"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-file-write-options">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-appendNewline" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label>
|
||||
</div>
|
||||
<div class="form-row form-row-file-write-options">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-createDir" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-createDir" style="width: 70%;"><span data-i18n="file.label.createdir"></span></label>
|
||||
</div>
|
||||
<div class="form-row form-row-file-encoding">
|
||||
<label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
|
||||
<select type="text" id="node-input-encoding" style="width: 250px;">
|
||||
</select>
|
||||
</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"><span data-i18n="file.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<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">
|
||||
</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>
|
||||
<select id="node-input-format" style="width: 250px;">
|
||||
<option value="utf8" data-i18n="file.output.utf8"></option>
|
||||
<option value="lines" data-i18n="file.output.lines"></option>
|
||||
<option value="" data-i18n="file.output.buffer"></option>
|
||||
<option value="stream" data-i18n="file.output.stream"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="file-allprops">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-allProps" style="display:inline-block; width:auto; vertical-align:top;">
|
||||
<label for="node-input-allProps" style="width: 70%;"><span data-i18n="file.label.allProps"></span></label>
|
||||
</div>
|
||||
<div class="form-row" id="encoding-spec">
|
||||
<label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
|
||||
<select type="text" id="node-input-encoding" style="width:250px;">
|
||||
</select>
|
||||
</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"><span data-i18n="file.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
var encodings = [
|
||||
[ "file.encoding.native",
|
||||
"utf8",
|
||||
"ucs2",
|
||||
"utf-16le",
|
||||
"ascii",
|
||||
"binary",
|
||||
"base64",
|
||||
"hex"
|
||||
],
|
||||
[ "file.encoding.unicode",
|
||||
"utf-16be",
|
||||
],
|
||||
[ "file.encoding.japanese",
|
||||
"Shift_JIS",
|
||||
"Windows-31j",
|
||||
"Windows932",
|
||||
"EUC-JP"
|
||||
],
|
||||
[ "file.encoding.chinese",
|
||||
"GB2312",
|
||||
"GBK",
|
||||
"GB18030",
|
||||
"Windows936",
|
||||
"EUC-CN"
|
||||
],
|
||||
[ "file.encoding.korean",
|
||||
"KS_C_5601",
|
||||
"Windows949",
|
||||
"EUC-KR"
|
||||
],
|
||||
[ "file.encoding.taiwan",
|
||||
"Big5",
|
||||
"Big5-HKSCS",
|
||||
"Windows950"
|
||||
],
|
||||
[ "file.encoding.windows",
|
||||
"cp874",
|
||||
"cp1250",
|
||||
"cp1251",
|
||||
"cp1252",
|
||||
"cp1253",
|
||||
"cp1254",
|
||||
"cp1255",
|
||||
"cp1256",
|
||||
"cp1257",
|
||||
"cp1258"
|
||||
],
|
||||
[ "file.encoding.iso",
|
||||
"ISO-8859-1",
|
||||
"ISO-8859-2",
|
||||
"ISO-8859-3",
|
||||
"ISO-8859-4",
|
||||
"ISO-8859-5",
|
||||
"ISO-8859-6",
|
||||
"ISO-8859-7",
|
||||
"ISO-8859-8",
|
||||
"ISO-8859-9",
|
||||
"ISO-8859-10",
|
||||
"ISO-8859-11",
|
||||
"ISO-8859-12",
|
||||
"ISO-8859-13",
|
||||
"ISO-8859-14",
|
||||
"ISO-8859-15",
|
||||
"ISO-8859-16"
|
||||
],
|
||||
[ "file.encoding.ibm",
|
||||
"cp437",
|
||||
"cp737",
|
||||
"cp775",
|
||||
"cp808",
|
||||
"cp850",
|
||||
"cp852",
|
||||
"cp855",
|
||||
"cp856",
|
||||
"cp857",
|
||||
"cp858",
|
||||
"cp860",
|
||||
"cp861",
|
||||
"cp866",
|
||||
"cp869",
|
||||
"cp922",
|
||||
"cp1046",
|
||||
"cp1124",
|
||||
"cp1125",
|
||||
"cp1129",
|
||||
"cp1133",
|
||||
"cp1161",
|
||||
"cp1162",
|
||||
"cp1163"
|
||||
],
|
||||
[ "file.encoding.mac",
|
||||
"maccroatian",
|
||||
"maccyrillic",
|
||||
"macgreek",
|
||||
"maciceland",
|
||||
"macroman",
|
||||
"macromania",
|
||||
"macthai",
|
||||
"macturkish",
|
||||
"macukraine",
|
||||
"maccenteuro",
|
||||
"macintosh"
|
||||
],
|
||||
[ "file.encoding.koi8",
|
||||
"koi8-r",
|
||||
"koi8-u",
|
||||
"koi8-ru",
|
||||
"koi8-t"
|
||||
],
|
||||
[ "file.encoding.misc",
|
||||
"armscii8",
|
||||
"rk1048",
|
||||
"tcvn",
|
||||
"georgianacademy",
|
||||
"georgianps",
|
||||
"pt154",
|
||||
"viscii",
|
||||
"iso646cn",
|
||||
"iso646jp",
|
||||
"hproman8",
|
||||
"tis620"
|
||||
]
|
||||
];
|
||||
|
||||
RED.nodes.registerType('file',{
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
appendNewline: {value:true},
|
||||
createDir: {value:false},
|
||||
overwriteFile: {value:"false"},
|
||||
encoding: {value:"none"}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "file-out.svg",
|
||||
label: function() {
|
||||
if (this.overwriteFile === "delete") {
|
||||
return this.name||this._("file.label.deletelabel",{file:this.filename});
|
||||
} else {
|
||||
return this.name||this.filename||this._("file.label.write");
|
||||
}
|
||||
},
|
||||
paletteLabel: RED._("node-red:file.label.write"),
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var encSel = $("#node-input-encoding");
|
||||
var label = node._("file.encoding.none");
|
||||
$("<option/>", {
|
||||
value: "none",
|
||||
label: label
|
||||
}).text(label).appendTo(encSel);
|
||||
$("<option/>", {
|
||||
value: "setbymsg",
|
||||
label: node._("file.encoding.setbymsg")
|
||||
}).text(label).appendTo(encSel);
|
||||
encodings.forEach(function(item) {
|
||||
if(Array.isArray(item)) {
|
||||
var group = $("<optgroup/>", {
|
||||
label: node._(item[0])
|
||||
}).appendTo(encSel);
|
||||
for (var i = 1; i < item.length; i++) {
|
||||
var enc = item[i];
|
||||
$("<option/>", {
|
||||
value: enc,
|
||||
label: enc
|
||||
}).text(enc).appendTo(group);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("<option/>", {
|
||||
value: item,
|
||||
label: item
|
||||
}).text(item).appendTo(encSel);
|
||||
}
|
||||
});
|
||||
encSel.val(node.encoding);
|
||||
$("#node-input-overwriteFile").on("change",function() {
|
||||
if (this.value === "delete") {
|
||||
$(".form-row-file-write-options").hide();
|
||||
$(".form-row-file-encoding").hide();
|
||||
} else {
|
||||
$(".form-row-file-write-options").show();
|
||||
$(".form-row-file-encoding").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('file in',{
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
format: {value:"utf8"},
|
||||
chunk: {value:false},
|
||||
sendError: {value: false},
|
||||
encoding: {value: "none"},
|
||||
allProps: {value: false}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
outputLabels: function(i) {
|
||||
var l;
|
||||
if (this.format === "utf8") {
|
||||
l = "file.label.utf8String";
|
||||
} else if (this.format === "lines") {
|
||||
l = "file.label.utf8String_plural";
|
||||
} else if (this.format === "stream") {
|
||||
l = "file.label.binaryBuffer_plural";
|
||||
} else {
|
||||
l = "file.label.binaryBuffer";
|
||||
}
|
||||
return this._(l);
|
||||
},
|
||||
icon: "file-in.svg",
|
||||
label: function() {
|
||||
return this.name||this.filename||this._("file.label.read");
|
||||
},
|
||||
paletteLabel: RED._("node-red:file.label.read"),
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var encSel = $("#node-input-encoding");
|
||||
var label = node._("file.encoding.none");
|
||||
$("<option/>", {
|
||||
value: "none",
|
||||
label: label
|
||||
}).text(label).appendTo(encSel);
|
||||
encodings.forEach(function(item) {
|
||||
if(Array.isArray(item)) {
|
||||
var group = $("<optgroup/>", {
|
||||
label: node._(item[0])
|
||||
}).appendTo(encSel);
|
||||
for (var i = 1; i < item.length; i++) {
|
||||
var enc = item[i];
|
||||
$("<option/>", {
|
||||
value: enc,
|
||||
label: enc
|
||||
}).text(enc).appendTo(group);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("<option/>", {
|
||||
value: item,
|
||||
label: item
|
||||
}).text(item).appendTo(encSel);
|
||||
}
|
||||
});
|
||||
encSel.val(node.encoding);
|
||||
$("#node-input-format").on("change",function() {
|
||||
var format = $("#node-input-format").val();
|
||||
if ((format === "utf8") || (format === "lines")) {
|
||||
$("#encoding-spec").show();
|
||||
}
|
||||
else {
|
||||
$("#encoding-spec").hide();
|
||||
}
|
||||
if ((format === "lines") || (format === "stream")) {
|
||||
$("#file-allprops").show();
|
||||
}
|
||||
else {
|
||||
$("#file-allprops").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
@ -1,401 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var fs = require("fs-extra");
|
||||
var os = require("os");
|
||||
var path = require("path");
|
||||
var iconv = require("iconv-lite")
|
||||
|
||||
function encode(data, enc) {
|
||||
if (enc !== "none") {
|
||||
return iconv.encode(data, enc);
|
||||
}
|
||||
return Buffer.from(data);
|
||||
}
|
||||
|
||||
function decode(data, enc) {
|
||||
if (enc !== "none") {
|
||||
return iconv.decode(data, enc);
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
function FileNode(n) {
|
||||
// Write/delete a file
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename;
|
||||
this.appendNewline = n.appendNewline;
|
||||
this.overwriteFile = n.overwriteFile.toString();
|
||||
this.createDir = n.createDir || false;
|
||||
this.encoding = n.encoding || "none";
|
||||
var node = this;
|
||||
node.wstream = null;
|
||||
node.msgQueue = [];
|
||||
node.closing = false;
|
||||
node.closeCallback = null;
|
||||
|
||||
function processMsg(msg,nodeSend, done) {
|
||||
var filename = node.filename || msg.filename || "";
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
}
|
||||
if ((!node.filename) && (!node.tout)) {
|
||||
node.tout = setTimeout(function() {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
clearTimeout(node.tout);
|
||||
node.tout = null;
|
||||
},333);
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn(RED._("file.errors.nofilename"));
|
||||
done();
|
||||
} else if (node.overwriteFile === "delete") {
|
||||
fs.unlink(fullFilename, function (err) {
|
||||
if (err) {
|
||||
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
|
||||
}
|
||||
else {
|
||||
if (RED.settings.verbose) {
|
||||
node.log(RED._("file.status.deletedfile",{file:filename}));
|
||||
}
|
||||
nodeSend(msg);
|
||||
}
|
||||
done();
|
||||
});
|
||||
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
|
||||
var dir = path.dirname(fullFilename);
|
||||
if (node.createDir) {
|
||||
try {
|
||||
fs.ensureDirSync(dir);
|
||||
}
|
||||
catch(err) {
|
||||
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var data = msg.payload;
|
||||
if ((typeof data === "object") && (!Buffer.isBuffer(data))) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
if (typeof data === "boolean") { data = data.toString(); }
|
||||
if (typeof data === "number") { data = data.toString(); }
|
||||
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
|
||||
var buf;
|
||||
if (node.encoding === "setbymsg") {
|
||||
buf = encode(data, msg.encoding || "none");
|
||||
}
|
||||
else { buf = encode(data, node.encoding); }
|
||||
if (node.overwriteFile === "true") {
|
||||
var wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'w', autoClose:true });
|
||||
node.wstream = wstream;
|
||||
wstream.on("error", function(err) {
|
||||
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
|
||||
done();
|
||||
});
|
||||
wstream.on("open", function() {
|
||||
wstream.once("close", function() {
|
||||
nodeSend(msg);
|
||||
done();
|
||||
});
|
||||
wstream.end(buf);
|
||||
})
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Append mode
|
||||
var recreateStream = !node.wstream || !node.filename;
|
||||
if (node.wstream && node.wstreamIno) {
|
||||
// There is already a stream open and we have the inode
|
||||
// of the file. Check the file hasn't been deleted
|
||||
// or deleted and recreated.
|
||||
try {
|
||||
var stat = fs.statSync(fullFilename);
|
||||
// File exists - check the inode matches
|
||||
if (stat.ino !== node.wstreamIno) {
|
||||
// The file has been recreated. Close the current
|
||||
// stream and recreate it
|
||||
recreateStream = true;
|
||||
node.wstream.end();
|
||||
delete node.wstream;
|
||||
delete node.wstreamIno;
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
// File does not exist
|
||||
recreateStream = true;
|
||||
node.wstream.end();
|
||||
delete node.wstream;
|
||||
delete node.wstreamIno;
|
||||
}
|
||||
}
|
||||
if (recreateStream) {
|
||||
node.wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'a', autoClose:true });
|
||||
node.wstream.on("open", function(fd) {
|
||||
try {
|
||||
var stat = fs.statSync(fullFilename);
|
||||
node.wstreamIno = stat.ino;
|
||||
} catch(err) {
|
||||
}
|
||||
});
|
||||
node.wstream.on("error", function(err) {
|
||||
node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
if (node.filename) {
|
||||
// Static filename - write and reuse the stream next time
|
||||
node.wstream.write(buf, function() {
|
||||
nodeSend(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Dynamic filename - write and close the stream
|
||||
node.wstream.once("close", function() {
|
||||
nodeSend(msg);
|
||||
delete node.wstream;
|
||||
delete node.wstreamIno;
|
||||
done();
|
||||
});
|
||||
node.wstream.end(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function processQueue(queue) {
|
||||
var event = queue[0];
|
||||
processMsg(event.msg, event.send, function() {
|
||||
event.done();
|
||||
queue.shift();
|
||||
if (queue.length > 0) {
|
||||
processQueue(queue);
|
||||
}
|
||||
else if (node.closing) {
|
||||
closeNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on("input", function(msg,nodeSend,nodeDone) {
|
||||
var msgQueue = node.msgQueue;
|
||||
msgQueue.push({
|
||||
msg: msg,
|
||||
send: nodeSend,
|
||||
done: nodeDone
|
||||
})
|
||||
if (msgQueue.length > 1) {
|
||||
// pending write exists
|
||||
return;
|
||||
}
|
||||
try {
|
||||
processQueue(msgQueue);
|
||||
}
|
||||
catch (e) {
|
||||
node.msgQueue = [];
|
||||
if (node.closing) {
|
||||
closeNode();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
function closeNode() {
|
||||
if (node.wstream) { node.wstream.end(); }
|
||||
if (node.tout) { clearTimeout(node.tout); }
|
||||
node.status({});
|
||||
var cb = node.closeCallback;
|
||||
node.closeCallback = null;
|
||||
node.closing = false;
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
this.on('close', function(done) {
|
||||
if (node.closing) {
|
||||
// already closing
|
||||
return;
|
||||
}
|
||||
node.closing = true;
|
||||
if (done) {
|
||||
node.closeCallback = done;
|
||||
}
|
||||
if (node.msgQueue.length > 0) {
|
||||
// close after queue processed
|
||||
return;
|
||||
}
|
||||
else {
|
||||
closeNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("file",FileNode);
|
||||
|
||||
|
||||
function FileInNode(n) {
|
||||
// Read a file
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename;
|
||||
this.format = n.format;
|
||||
this.chunk = false;
|
||||
this.encoding = n.encoding || "none";
|
||||
this.allProps = n.allProps || false;
|
||||
if (n.sendError === undefined) {
|
||||
this.sendError = true;
|
||||
} else {
|
||||
this.sendError = n.sendError;
|
||||
}
|
||||
if (this.format === "lines") { this.chunk = true; }
|
||||
if (this.format === "stream") { this.chunk = true; }
|
||||
var node = this;
|
||||
|
||||
this.on("input",function(msg, nodeSend, nodeDone) {
|
||||
var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
}
|
||||
if (!node.filename) {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn(RED._("file.errors.nofilename"));
|
||||
nodeDone();
|
||||
}
|
||||
else {
|
||||
msg.filename = filename;
|
||||
var lines = Buffer.from([]);
|
||||
var spare = "";
|
||||
var count = 0;
|
||||
var type = "buffer";
|
||||
var ch = "";
|
||||
if (node.format === "lines") {
|
||||
ch = "\n";
|
||||
type = "string";
|
||||
}
|
||||
var getout = false;
|
||||
|
||||
var rs = fs.createReadStream(fullFilename)
|
||||
.on('readable', function () {
|
||||
var chunk;
|
||||
var m;
|
||||
var hwm = rs._readableState.highWaterMark;
|
||||
while (null !== (chunk = rs.read())) {
|
||||
if (node.chunk === true) {
|
||||
getout = true;
|
||||
if (node.format === "lines") {
|
||||
spare += decode(chunk, node.encoding);
|
||||
var bits = spare.split("\n");
|
||||
for (var i=0; i < bits.length - 1; i++) {
|
||||
m = {};
|
||||
if (node.allProps == true) {
|
||||
m = RED.util.cloneMessage(msg);
|
||||
}
|
||||
else {
|
||||
m.topic = msg.topic;
|
||||
m.filename = msg.filename;
|
||||
}
|
||||
m.payload = bits[i];
|
||||
m.parts= {index:count, ch:ch, type:type, id:msg._msgid}
|
||||
count += 1;
|
||||
nodeSend(m);
|
||||
}
|
||||
spare = bits[i];
|
||||
}
|
||||
if (node.format === "stream") {
|
||||
m = {};
|
||||
if (node.allProps == true) {
|
||||
m = RED.util.cloneMessage(msg);
|
||||
}
|
||||
else {
|
||||
m.topic = msg.topic;
|
||||
m.filename = msg.filename;
|
||||
}
|
||||
m.payload = chunk;
|
||||
m.parts = {index:count, ch:ch, type:type, id:msg._msgid}
|
||||
count += 1;
|
||||
if (chunk.length < hwm) { // last chunk is smaller that high water mark = eof
|
||||
getout = false;
|
||||
m.parts.count = count;
|
||||
}
|
||||
nodeSend(m);
|
||||
}
|
||||
}
|
||||
else {
|
||||
lines = Buffer.concat([lines,chunk]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('error', function(err) {
|
||||
node.error(err, msg);
|
||||
if (node.sendError) {
|
||||
var sendMessage = RED.util.cloneMessage(msg);
|
||||
delete sendMessage.payload;
|
||||
sendMessage.error = err;
|
||||
nodeSend(sendMessage);
|
||||
}
|
||||
nodeDone();
|
||||
})
|
||||
.on('end', function() {
|
||||
if (node.chunk === false) {
|
||||
if (node.format === "utf8") {
|
||||
msg.payload = decode(lines, node.encoding);
|
||||
}
|
||||
else { msg.payload = lines; }
|
||||
nodeSend(msg);
|
||||
}
|
||||
else if (node.format === "lines") {
|
||||
var m = {};
|
||||
if (node.allProps) {
|
||||
m = RED.util.cloneMessage(msg);
|
||||
}
|
||||
else {
|
||||
m.topic = msg.topic;
|
||||
m.filename = msg.filename;
|
||||
}
|
||||
m.payload = spare;
|
||||
m.parts = {
|
||||
index: count,
|
||||
count: count + 1,
|
||||
ch: ch,
|
||||
type: type,
|
||||
id: msg._msgid
|
||||
}
|
||||
nodeSend(m);
|
||||
}
|
||||
else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.
|
||||
var m = { parts:{index:count, count:count, ch:ch, type:type, id:msg._msgid} };
|
||||
nodeSend(m);
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
}
|
||||
});
|
||||
this.on('close', function() {
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("file in",FileInNode);
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="watch">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-files"><i class="fa fa-file"></i> <span data-i18n="watch.label.files"></span></label>
|
||||
<input id="node-input-files" type="text" tabindex="1" data-i18n="[placeholder]watch.placeholder.files">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-recursive" style="display:inline-block; width:auto; vertical-align:top;">
|
||||
<label for="node-input-recursive" style="width:70%;"> <span data-i18n="watch.label.recursive"></span></label>
|
||||
</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 id="node-input-tip" class="form-tips"><span data-i18n="watch.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('watch',{
|
||||
category: 'storage',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
files: {value:"",required:true},
|
||||
recursive: {value:""}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "watch.svg",
|
||||
label: function() {
|
||||
return this.name||this.files||this._("watch.watch");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function WatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.recursive = n.recursive || false;
|
||||
this.files = (n.files || "").split(",");
|
||||
for (var f=0; f < this.files.length; f++) {
|
||||
this.files[f] = this.files[f].trim();
|
||||
}
|
||||
this.p = (this.files.length === 1) ? this.files[0] : JSON.stringify(this.files);
|
||||
var 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var notifications = new Notify(node.files);
|
||||
notifications.on('change', function (file, event, fpath) {
|
||||
var 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 };
|
||||
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 { type = "n/a"; }
|
||||
}
|
||||
msg.type = type;
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
notifications.on('error', function (error, fpath) {
|
||||
var msg = { payload:fpath };
|
||||
node.error(error,msg);
|
||||
});
|
||||
|
||||
this.close = function() {
|
||||
notifications.close();
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("watch",WatchNode);
|
||||
}
|
@ -1 +0,0 @@
|
||||
[{"id":"87bd706a.aec93","type":"comment","z":"3ae4b3d9.1f77bc","name":"Output payload value to debug sidebar","info":"Debug node can be used to output payload value to debug sidebar.","x":230,"y":60,"wires":[]},{"id":"8035b07f.7547e","type":"inject","z":"3ae4b3d9.1f77bc","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":210,"y":100,"wires":[["20d1344f.931e3c"]]},{"id":"20d1344f.931e3c","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":450,"y":100,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"8c66d039.44465","type":"comment","z":"e6956267.5d174","name":"Output complete object","info":"Debug node can be used to output whole object value to debug sidebar.","x":160,"y":60,"wires":[]},{"id":"dac87e40.90376","type":"inject","z":"e6956267.5d174","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"Sample","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Sample","payload":"Hello, World!","payloadType":"str","x":220,"y":100,"wires":[["a77fa5e3.fac248"]]},{"id":"a77fa5e3.fac248","type":"debug","z":"e6956267.5d174","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":410,"y":100,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"fb1c3ce9.c29ee","type":"comment","z":"395f4b0d.8a8774","name":"Output to console","info":"Debug node can be used to output values to console.","x":130,"y":60,"wires":[]},{"id":"3c24e746.9ff6a8","type":"inject","z":"395f4b0d.8a8774","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":170,"y":100,"wires":[["66cc7b44.82ba74"]]},{"id":"66cc7b44.82ba74","type":"debug","z":"395f4b0d.8a8774","name":"","active":true,"tosidebar":false,"console":true,"tostatus":false,"complete":"payload","targetType":"msg","x":420,"y":100,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"33791e6a.973502","type":"comment","z":"55587092.1f2b4","name":"Output to node status area","info":"Debug node can be used to output values to status area below the node.","x":170,"y":60,"wires":[]},{"id":"a5d8e744.a034e8","type":"inject","z":"55587092.1f2b4","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":190,"y":100,"wires":[["b0646a4d.db4bc8"]]},{"id":"b0646a4d.db4bc8","type":"debug","z":"55587092.1f2b4","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","x":430,"y":100,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"30ed0a73.fcef86","type":"comment","z":"61feb619.5e3d68","name":"Formatting output using JSONata","info":"Debug node can format output value using JSONata expression.","x":200,"y":60,"wires":[]},{"id":"6f477e7d.3a8da","type":"inject","z":"61feb619.5e3d68","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"Sample","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Sample","payload":"Hello, World!","payloadType":"str","x":220,"y":100,"wires":[["19c9408d.ac6d4f"]]},{"id":"19c9408d.ac6d4f","type":"debug","z":"61feb619.5e3d68","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"[\" & topic & \"] \" & payload","targetType":"jsonata","x":420,"y":100,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"a30d20e6.ec6dc","type":"link in","z":"2f2d9fa4.be6fc","name":"","links":["70d6f012.8fe6d"],"x":235,"y":240,"wires":[["6bf52c5c.d301c4"]]},{"id":"70d6f012.8fe6d","type":"link out","z":"2f2d9fa4.be6fc","name":"","links":["a30d20e6.ec6dc"],"x":315,"y":180,"wires":[]},{"id":"353c85ce.993d0a","type":"inject","z":"2f2d9fa4.be6fc","name":"","topic":"","payload":"Hello, World!","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":180,"wires":[["70d6f012.8fe6d"]]},{"id":"6bf52c5c.d301c4","type":"debug","z":"2f2d9fa4.be6fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":370,"y":240,"wires":[]},{"id":"62ea32aa.d73aac","type":"comment","z":"2f2d9fa4.be6fc","name":"Example: Link Node","info":"Output of link out node can be connected to input of link in node. The connection between links in/out is not shown, so the flow representation can be simplified.","x":130,"y":40,"wires":[]},{"id":"85133fcc.e482","type":"comment","z":"2f2d9fa4.be6fc","name":"Link output of inject node to input of debug node","info":"","x":260,"y":100,"wires":[]},{"id":"c588bc36.87fec","type":"comment","z":"2f2d9fa4.be6fc","name":"↓ connect to link in node","info":"","x":410,"y":140,"wires":[]},{"id":"8abca900.6dfe78","type":"comment","z":"2f2d9fa4.be6fc","name":"↑ connect from link out node","info":"","x":340,"y":280,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"26e7643f.13ebcc","type":"comment","z":"f4cb1920.4d58c8","name":"Set any property value","info":"Change node can set value to any message property.","x":160,"y":60,"wires":[]},{"id":"4da2494d.9aff68","type":"inject","z":"f4cb1920.4d58c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["5111e689.62e838"]]},{"id":"58ea5868.0596e8","type":"debug","z":"f4cb1920.4d58c8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"5111e689.62e838","type":"change","z":"f4cb1920.4d58c8","name":"set payload & topic","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"Title","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":120,"wires":[["58ea5868.0596e8"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"a9039cda.3649e","type":"comment","z":"a808932c.4ca77","name":"Set value using JSONata","info":"Change node can set value to using JSONata expression.","x":170,"y":60,"wires":[]},{"id":"bdcdd579.cfe668","type":"inject","z":"a808932c.4ca77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["28d110a7.ce3e2"]]},{"id":"c6677fa5.8c111","type":"debug","z":"a808932c.4ca77","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"28d110a7.ce3e2","type":"change","z":"a808932c.4ca77","name":"use JSONata","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"payload & \", World!\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":120,"wires":[["c6677fa5.8c111"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"e9143349.a64f7","type":"comment","z":"a32e6d69.1b13b","name":"Set value from environment variable","info":"Change node can set value from environment variable.","x":200,"y":60,"wires":[]},{"id":"a7c2725.a631f9","type":"inject","z":"a32e6d69.1b13b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["e455c302.2f795"]]},{"id":"6f203119.21895","type":"debug","z":"a32e6d69.1b13b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"e455c302.2f795","type":"change","z":"a32e6d69.1b13b","name":"set env var","rules":[{"t":"set","p":"payload","pt":"msg","to":"HOME","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":120,"wires":[["6f203119.21895"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"6ecac54d.c43ffc","type":"comment","z":"87ace6c0.f01da8","name":"Set flow context","info":"Change node can set flow context.","x":140,"y":60,"wires":[]},{"id":"80e966d3.9d7a78","type":"inject","z":"87ace6c0.f01da8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":234,"wires":[["abaee298.2d77e"]]},{"id":"60ab671d.b0bbf8","type":"debug","z":"87ace6c0.f01da8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":234,"wires":[]},{"id":"abaee298.2d77e","type":"change","z":"87ace6c0.f01da8","name":"increment count","rules":[{"t":"set","p":"count","pt":"flow","to":"$flowContext(\"count\")+1\t","tot":"jsonata"},{"t":"set","p":"payload","pt":"msg","to":"count","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":234,"wires":[["60ab671d.b0bbf8"]]},{"id":"2de2bb38.f20ff4","type":"inject","z":"87ace6c0.f01da8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":140,"wires":[["7b96521e.a3cb0c"]]},{"id":"597b63cd.b3218c","type":"debug","z":"87ace6c0.f01da8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":140,"wires":[]},{"id":"7b96521e.a3cb0c","type":"change","z":"87ace6c0.f01da8","name":"set count to 0","rules":[{"t":"set","p":"count","pt":"flow","to":"0","tot":"num"},{"t":"set","p":"payload","pt":"msg","to":"count","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":140,"wires":[["597b63cd.b3218c"]]},{"id":"3d8cdc6d.2620e4","type":"comment","z":"87ace6c0.f01da8","name":"↓ Initialize","info":"","x":200,"y":100,"wires":[]},{"id":"d8069121.80de7","type":"comment","z":"87ace6c0.f01da8","name":"↓ Count up","info":"","x":200,"y":194,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"87bd706a.aec93","type":"comment","z":"3ae4b3d9.1f77bc","name":"Delay message","info":"Delay node can delay sending input message to output port by a specified amount of time.","x":160,"y":60,"wires":[]},{"id":"1d17715c.34170f","type":"inject","z":"3ae4b3d9.1f77bc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":210,"y":120,"wires":[["26b43de5.4df8f2","9930fecd.ee0c8"]]},{"id":"9930fecd.ee0c8","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":410,"y":120,"wires":[]},{"id":"26b43de5.4df8f2","type":"delay","z":"3ae4b3d9.1f77bc","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":400,"y":180,"wires":[["c8c2796c.dcb9f8"]]},{"id":"c8c2796c.dcb9f8","type":"change","z":"3ae4b3d9.1f77bc","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":180,"wires":[["c58a290e.2fa438"]]},{"id":"c58a290e.2fa438","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":790,"y":180,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"c15b8c3e.955ed","type":"comment","z":"6f1773ed.b7c2fc","name":"Delay message by message property","info":"Delay node can delay sending input message to output port by a specified amount of time by `msg.delay` property.","x":210,"y":60,"wires":[]},{"id":"a5ed5817.9df448","type":"inject","z":"6f1773ed.b7c2fc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"delay","v":"1000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"delay 1s","payloadType":"str","x":180,"y":120,"wires":[["5cf53f4.25b7ec","59b7b67a.a8e888"]]},{"id":"59b7b67a.a8e888","type":"debug","z":"6f1773ed.b7c2fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":120,"wires":[]},{"id":"5cf53f4.25b7ec","type":"delay","z":"6f1773ed.b7c2fc","name":"","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":380,"y":180,"wires":[["fc989f41.c4114"]]},{"id":"fc989f41.c4114","type":"change","z":"6f1773ed.b7c2fc","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":180,"wires":[["74ba3d1c.666034"]]},{"id":"74ba3d1c.666034","type":"debug","z":"6f1773ed.b7c2fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":770,"y":180,"wires":[]},{"id":"6cdf7297.bf5a8c","type":"inject","z":"6f1773ed.b7c2fc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"delay","v":"10000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"delay 10s","payloadType":"str","x":180,"y":180,"wires":[["59b7b67a.a8e888","5cf53f4.25b7ec"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"b0200f61.5efa5","type":"comment","z":"86a4fcf3.9f442","name":"Reset or flush pending message","info":"Delay node can reset or flush delayed message by sending it a message with `reset` or `flush` property.","x":170,"y":60,"wires":[]},{"id":"d5cd8991.e6d2e8","type":"inject","z":"86a4fcf3.9f442","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":170,"y":120,"wires":[["607f556b.3ec5fc","fd14cb.2044db38"]]},{"id":"fd14cb.2044db38","type":"debug","z":"86a4fcf3.9f442","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":370,"y":120,"wires":[]},{"id":"607f556b.3ec5fc","type":"delay","z":"86a4fcf3.9f442","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":360,"y":180,"wires":[["d1fc6763.2a30c8"]]},{"id":"d1fc6763.2a30c8","type":"change","z":"86a4fcf3.9f442","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":180,"wires":[["15486d4a.80c6f3"]]},{"id":"15486d4a.80c6f3","type":"debug","z":"86a4fcf3.9f442","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":180,"wires":[]},{"id":"2b8b28c7.4c8978","type":"inject","z":"86a4fcf3.9f442","name":"reset","props":[{"p":"topic","vt":"str"},{"p":"reset","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":180,"wires":[["607f556b.3ec5fc"]]},{"id":"3a7e1bec.8bc3d4","type":"inject","z":"86a4fcf3.9f442","name":"flush","props":[{"p":"topic","vt":"str"},{"p":"flush","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":240,"wires":[["607f556b.3ec5fc"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"3dc5015b.96c97e","type":"comment","z":"73c00795.a13908","name":"Limit rate of message transfer for each topic","info":"Delay node can limit of message transmission from input to output port by a specified number of message per a specified time.\nIf `For each topic` is selected, messages are grouped by `msg.topic` value. When grouping messages by topic, intermediate messages are dropped and the last messages received sent.","x":210,"y":60,"wires":[]},{"id":"bdafe4c6.4d5658","type":"inject","z":"73c00795.a13908","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"topic\":\"apple\",\"payload\":1},{\"topic\":\"apple\",\"payload\":2},{\"topic\":\"apple\",\"payload\":3},{\"topic\":\"orange\",\"payload\":1},{\"topic\":\"orange\",\"payload\":2},{\"topic\":\"orange\",\"payload\":3},{\"topic\":\"banana\",\"payload\":1},{\"topic\":\"banana\",\"payload\":2},{\"topic\":\"banana\",\"payload\":3}]","payloadType":"json","x":150,"y":120,"wires":[["f86dc462.195818"]]},{"id":"e0bdfcc1.cdf48","type":"delay","z":"73c00795.a13908","name":"","pauseType":"timed","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":690,"y":120,"wires":[["29d8beea.6b37f2"]]},{"id":"29d8beea.6b37f2","type":"debug","z":"73c00795.a13908","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":890,"y":120,"wires":[]},{"id":"f86dc462.195818","type":"split","z":"73c00795.a13908","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":290,"y":120,"wires":[["9feb3aac.616c38"]]},{"id":"9feb3aac.616c38","type":"change","z":"73c00795.a13908","name":"set topic&payload","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload.topic","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"payload.payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":120,"wires":[["e0bdfcc1.cdf48"]]},{"id":"949199e8.89bc88","type":"comment","z":"73c00795.a13908","name":"↑ pass last message of each topic","info":"","x":740,"y":160,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"6a5f26a9.0cc2d8","type":"inject","z":"835cc8cc.b8cca8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello World!","payloadType":"str","x":190,"y":160,"wires":[["fc2b343c.bbe2f8"]]},{"id":"fc2b343c.bbe2f8","type":"exec","z":"835cc8cc.b8cca8","command":"echo","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":330,"y":160,"wires":[["2f3bcd73.6fedf2"],[],["3280586e.4e3d28"]]},{"id":"2f3bcd73.6fedf2","type":"debug","z":"835cc8cc.b8cca8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":160,"wires":[]},{"id":"2b7bc9f1.ab36a6","type":"comment","z":"835cc8cc.b8cca8","name":"Execute external command appending additional args","info":"Exec node can execute external command and can receive its standard output as a payload of first message. Standard error output can be received from second message. The exit code of the command can be obtained from `code` property of third message payload.\n\nIf `Append msg.payload` checkbox is selected, payload value of the input message is appended to command string.\n","x":260,"y":60,"wires":[]},{"id":"3280586e.4e3d28","type":"debug","z":"835cc8cc.b8cca8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":220,"wires":[]},{"id":"feba83da.8f227","type":"comment","z":"835cc8cc.b8cca8","name":"↓ execute echo command","info":"","x":390,"y":115,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"f507b27c.fff1","type":"inject","z":"462f83a6.d3c3cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":180,"wires":[["28b4f75a.eae548"]]},{"id":"28b4f75a.eae548","type":"exec","z":"462f83a6.d3c3cc","command":"/non/existing/command","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":350,"y":180,"wires":[[],["27992c1f.a1c964"],["6e7ff001.2412d"]]},{"id":"6e7ff001.2412d","type":"debug","z":"462f83a6.d3c3cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":240,"wires":[]},{"id":"27992c1f.a1c964","type":"debug","z":"462f83a6.d3c3cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":180,"wires":[]},{"id":"77e85c7c.a560d4","type":"comment","z":"462f83a6.d3c3cc","name":"Execute external command and get error output","info":"Exec node can execute external command and can receive its standard output as a payload of first message. Standard error output can be received from second message. The exit code of the command can be obtained from `code` property of third message payload.\n","x":220,"y":80,"wires":[]},{"id":"c188c28c.f7d34","type":"comment","z":"462f83a6.d3c3cc","name":"↓ try to execute non-existing command","info":"","x":390,"y":134,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"990f33d3.3ffe2","type":"comment","z":"db89ae0c.f52b5","name":"Scale input value & round result to integer","info":"Range node can map input value to output value according to mapping specification.\nThe result value is rounded to nearest integer if `Round result to the nearest integer?` checkbox is checked.","x":240,"y":60,"wires":[]},{"id":"b2639501.5ad638","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":190,"y":120,"wires":[["dc4509ee.773038"]]},{"id":"dc4509ee.773038","type":"range","z":"db89ae0c.f52b5","minin":"0","maxin":"9","minout":"0","maxout":"128","action":"scale","round":true,"property":"payload","name":"","x":360,"y":120,"wires":[["50991e7e.2ed24"]]},{"id":"50991e7e.2ed24","type":"debug","z":"db89ae0c.f52b5","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":520,"y":120,"wires":[]},{"id":"335e877f.d24e98","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":190,"y":160,"wires":[["dc4509ee.773038"]]},{"id":"5d88207d.4189","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"99","payloadType":"num","x":190,"y":200,"wires":[["dc4509ee.773038"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"f0bc6a80.d52578","type":"comment","z":"bdb79f11.23e1d","name":"Limit input value","info":"Range node can map input value to output value according to mapping specification.\nThe result value is limited to specified range if `Scale and limit to the target range` is selected.","x":140,"y":60,"wires":[]},{"id":"69652849.749198","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":120,"wires":[["104f6f14.1c72e1"]]},{"id":"104f6f14.1c72e1","type":"range","z":"bdb79f11.23e1d","minin":"0","maxin":"100","minout":"0","maxout":"90","action":"clamp","round":false,"property":"payload","name":"","x":330,"y":120,"wires":[["10fcbed4.9c8d71"]]},{"id":"10fcbed4.9c8d71","type":"debug","z":"bdb79f11.23e1d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":120,"wires":[]},{"id":"93bb4526.7d6e28","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":160,"wires":[["104f6f14.1c72e1"]]},{"id":"6892dfd8.42386","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":170,"y":200,"wires":[["104f6f14.1c72e1"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"808e354d.8de148","type":"comment","z":"b1446352.d689e","name":"Scale and wrap input value","info":"Range node can map input value to output value according to mapping specification.\nThe result value is wrapped if `Scale and wrap within the target range` is selected.","x":170,"y":60,"wires":[]},{"id":"2f033c72.51dcc4","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":120,"wires":[["cdaa82c4.820de"]]},{"id":"cdaa82c4.820de","type":"range","z":"b1446352.d689e","minin":"0","maxin":"9","minout":"0","maxout":"90","action":"roll","round":false,"property":"payload","name":"","x":330,"y":120,"wires":[["77f8872b.2a9968"]]},{"id":"77f8872b.2a9968","type":"debug","z":"b1446352.d689e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":120,"wires":[]},{"id":"84ac6a7e.77b8d8","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":160,"wires":[["cdaa82c4.820de"]]},{"id":"92f554c3.b08ad8","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":170,"y":200,"wires":[["cdaa82c4.820de"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"6ec19fc7.a32ae","type":"comment","z":"e482b0ab.b1b43","name":"Check all rules","info":"Switch node apply all rules if `checking all rules` is selected in settings panel.","x":120,"y":60,"wires":[]},{"id":"1644e138.f8d1ef","type":"switch","z":"e482b0ab.b1b43","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"10","vt":"num"},{"t":"gt","v":"-10","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":290,"y":140,"wires":[["624b4e9f.37fee"],["a89d6432.b68318"]]},{"id":"a7f64dbf.3e27b","type":"debug","z":"e482b0ab.b1b43","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":100,"wires":[]},{"id":"a89d6432.b68318","type":"change","z":"e482b0ab.b1b43","name":">-10","rules":[{"t":"set","p":"payload","pt":"msg","to":">-10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":180,"wires":[["f1136da2.23516"]]},{"id":"624b4e9f.37fee","type":"change","z":"e482b0ab.b1b43","name":"<10","rules":[{"t":"set","p":"payload","pt":"msg","to":"<10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":100,"wires":[["a7f64dbf.3e27b"]]},{"id":"f1136da2.23516","type":"debug","z":"e482b0ab.b1b43","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":180,"wires":[]},{"id":"c7e952e9.88e5e","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-10","payloadType":"num","x":150,"y":100,"wires":[["1644e138.f8d1ef"]]},{"id":"8cf8babd.b43db8","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":150,"y":180,"wires":[["1644e138.f8d1ef"]]},{"id":"6a43ae86.b92ed","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":150,"y":140,"wires":[["1644e138.f8d1ef"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"a12a5708.195688","type":"comment","z":"74b5d6de.372b18","name":"Stop after first match","info":"Switch node stops application of rules if `stopping after first match` is selected in settings panel and a rule evaluates to `true`.","x":160,"y":60,"wires":[]},{"id":"8aebdebf.5d7f2","type":"switch","z":"74b5d6de.372b18","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"10","vt":"num"},{"t":"gt","v":"-10","vt":"num"}],"checkall":"false","repair":false,"outputs":2,"x":310,"y":140,"wires":[["5d1851c.a9c5db"],["e8228605.f32018"]]},{"id":"60248c98.69fd44","type":"debug","z":"74b5d6de.372b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]},{"id":"e8228605.f32018","type":"change","z":"74b5d6de.372b18","name":">-10","rules":[{"t":"set","p":"payload","pt":"msg","to":">-10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":180,"wires":[["e9a51608.c322b8"]]},{"id":"5d1851c.a9c5db","type":"change","z":"74b5d6de.372b18","name":"<10","rules":[{"t":"set","p":"payload","pt":"msg","to":"<10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":100,"wires":[["60248c98.69fd44"]]},{"id":"e9a51608.c322b8","type":"debug","z":"74b5d6de.372b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":180,"wires":[]},{"id":"49222b4b.647a84","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-10","payloadType":"num","x":170,"y":100,"wires":[["8aebdebf.5d7f2"]]},{"id":"39e8c133.a56f7e","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":180,"wires":[["8aebdebf.5d7f2"]]},{"id":"3a22ec96.965a14","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":140,"wires":[["8aebdebf.5d7f2"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"6dfd4381.eae2ec","type":"comment","z":"7d002985.82f928","name":"Select output based on type of payload value","info":"Switch node can route input message based on type of payload value.","x":210,"y":40,"wires":[]},{"id":"b139dacf.a0d818","type":"switch","z":"7d002985.82f928","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"string","vt":"string"},{"t":"istype","v":"number","vt":"number"}],"checkall":"false","repair":false,"outputs":2,"x":330,"y":120,"wires":[["7c5cd60c.e66b28"],["86cf5262.20f18"]]},{"id":"ca403f12.477a8","type":"debug","z":"7d002985.82f928","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":160,"wires":[]},{"id":"7c5cd60c.e66b28","type":"change","z":"7d002985.82f928","name":"String","rules":[{"t":"set","p":"payload","pt":"msg","to":"string","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":80,"wires":[["d9669648.1177e8"]]},{"id":"86cf5262.20f18","type":"change","z":"7d002985.82f928","name":"Number","rules":[{"t":"set","p":"payload","pt":"msg","to":"number","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":160,"wires":[["ca403f12.477a8"]]},{"id":"d9669648.1177e8","type":"debug","z":"7d002985.82f928","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":80,"wires":[]},{"id":"d3f1c718.dc67e8","type":"inject","z":"7d002985.82f928","name":"Number:128","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"128","payloadType":"num","x":170,"y":80,"wires":[["b139dacf.a0d818"]]},{"id":"a59e275e.6d48c8","type":"inject","z":"7d002985.82f928","name":"String:128","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"128","payloadType":"str","x":160,"y":160,"wires":[["b139dacf.a0d818"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"175ceb0d.8dbe45","type":"comment","z":"17f634f4.e4bc9b","name":"Use JSONata expression for rules","info":"Switch node can use JSONata expression for calculating complex conditions.","x":200,"y":60,"wires":[]},{"id":"d89491c3.793a3","type":"switch","z":"17f634f4.e4bc9b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"jsonata_exp","v":"(payload % 2) = 0","vt":"jsonata"},{"t":"jsonata_exp","v":"(payload % 2) = 1","vt":"jsonata"}],"checkall":"false","repair":false,"outputs":2,"x":310,"y":140,"wires":[["d6cb78a6.872908"],["1f0c62bb.c3d52d"]]},{"id":"9ae9a2aa.a895c","type":"debug","z":"17f634f4.e4bc9b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]},{"id":"1f0c62bb.c3d52d","type":"change","z":"17f634f4.e4bc9b","name":"Odd","rules":[{"t":"set","p":"payload","pt":"msg","to":"odd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":180,"wires":[["34b0a9fb.bafdb6"]]},{"id":"d6cb78a6.872908","type":"change","z":"17f634f4.e4bc9b","name":"Even","rules":[{"t":"set","p":"payload","pt":"msg","to":"even","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":100,"wires":[["9ae9a2aa.a895c"]]},{"id":"34b0a9fb.bafdb6","type":"debug","z":"17f634f4.e4bc9b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":180,"wires":[]},{"id":"7beb0333.a55bac","type":"inject","z":"17f634f4.e4bc9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"7","payloadType":"num","x":170,"y":100,"wires":[["d89491c3.793a3"]]},{"id":"a8db47cf.58ba18","type":"inject","z":"17f634f4.e4bc9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"8","payloadType":"num","x":170,"y":180,"wires":[["d89491c3.793a3"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"1c09dc33.934504","type":"comment","z":"166069bc.648516","name":"Use JSONata expression for switch value","info":"Switch node can use JSONata expression for calculating complex switch value.","x":200,"y":60,"wires":[]},{"id":"c7ca4974.d638f8","type":"switch","z":"166069bc.648516","name":"","property":"(payload % 2) = 0","propertyType":"jsonata","rules":[{"t":"true"},{"t":"false"}],"checkall":"false","repair":false,"outputs":2,"x":290,"y":140,"wires":[["ac921b1d.c0dbe8"],["89adcfcc.53d6d"]]},{"id":"ab8972e0.e98b7","type":"debug","z":"166069bc.648516","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":100,"wires":[]},{"id":"89adcfcc.53d6d","type":"change","z":"166069bc.648516","name":"Odd","rules":[{"t":"set","p":"payload","pt":"msg","to":"odd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":180,"wires":[["426c68cf.64f0e8"]]},{"id":"ac921b1d.c0dbe8","type":"change","z":"166069bc.648516","name":"Even","rules":[{"t":"set","p":"payload","pt":"msg","to":"even","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":100,"wires":[["ab8972e0.e98b7"]]},{"id":"426c68cf.64f0e8","type":"debug","z":"166069bc.648516","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":180,"wires":[]},{"id":"63377f1c.2fbfc","type":"inject","z":"166069bc.648516","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"7","payloadType":"num","x":150,"y":100,"wires":[["c7ca4974.d638f8"]]},{"id":"ea2fa596.ff1638","type":"inject","z":"166069bc.648516","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"8","payloadType":"num","x":150,"y":180,"wires":[["c7ca4974.d638f8"]]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"eaf91a6b.a55da8","type":"comment","z":"73a69428.bf4fec","name":"Advanced mustache example","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.","x":200,"y":80,"wires":[]},{"id":"61fbfe34.14a02","type":"inject","z":"73a69428.bf4fec","name":"Price of fruits","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Fruits","payload":"[{\"name\":\"apple\",\"price\":100},{\"name\":\"orange\",\"price\":80},{\"name\":\"banana\",\"price\":210}]","payloadType":"json","x":210,"y":140,"wires":[["bf0cb02.d8e4b5"]]},{"id":"bf0cb02.d8e4b5","type":"template","z":"73a69428.bf4fec","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"# Price List of {{topic}}\n\n{{! outputs list of prices }}\n{{#payload}}\n- {{name}}: {{price}}\n{{/payload}}\n","output":"str","x":380,"y":140,"wires":[["153eb0ff.5622df"]]},{"id":"153eb0ff.5622df","type":"debug","z":"73a69428.bf4fec","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":550,"y":140,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"fe821493.2e0e28","type":"comment","z":"1e6bd604.afc8fa","name":"Parse result as JSON","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.\nIf `Partsed JSON` output is selected, the created string is parsed as JSON format and JavaScript object is send as an output payload value.","x":160,"y":60,"wires":[]},{"id":"931f94e8.592cd8","type":"inject","z":"1e6bd604.afc8fa","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"message","payload":"Hello, World!","payloadType":"str","x":220,"y":120,"wires":[["bb2b0dad.b24b5"]]},{"id":"bb2b0dad.b24b5","type":"template","z":"1e6bd604.afc8fa","name":"JSON template","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n \"key\" : \"{{topic}}\",\n \"value\": \"{{payload}}\"\n}\n","output":"json","x":440,"y":120,"wires":[["baf2e48.2b97418"]]},{"id":"baf2e48.2b97418","type":"debug","z":"1e6bd604.afc8fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":120,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"6ad06659.a1e4e8","type":"comment","z":"369312e8.ba755e","name":"Parse result as YAML","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.\nIf `Partsed YAML` output is selected, the created string is parsed as YAML format and JavaScript object is send as an output payload value.","x":180,"y":60,"wires":[]},{"id":"8d6be9a2.c3fa58","type":"inject","z":"369312e8.ba755e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"message","payload":"Hello, World!","payloadType":"str","x":240,"y":120,"wires":[["69369c3.4a98164"]]},{"id":"69369c3.4a98164","type":"template","z":"369312e8.ba755e","name":"YAML template","field":"payload","fieldType":"msg","format":"yaml","syntax":"mustache","template":"key: {{topic}}\nvalue: {{payload}}","output":"yaml","x":460,"y":120,"wires":[["11fb2934.f5de27"]]},{"id":"11fb2934.f5de27","type":"debug","z":"369312e8.ba755e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":120,"wires":[]}]
|
@ -1 +0,0 @@
|
||||
[{"id":"ec5a531b.68b65","type":"inject","z":"90acd374.2feda","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":100,"wires":[["cb5e0c78.4bf3d"]]},{"id":"1b0f8c3e.1fd7e4","type":"debug","z":"90acd374.2feda","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":490,"y":100,"wires":[]},{"id":"cb5e0c78.4bf3d","type":"trigger","z":"90acd374.2feda","name":"","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"2","extend":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":320,"y":100,"wires":[["1b0f8c3e.1fd7e4"]]},{"id":"4e5bf6b2.b4dd58","type":"comment","z":"90acd374.2feda","name":"Oputputs two values with interval","info":"Outputs 1. Then output 0 after a certain period of time.\n\n*This could be used, for example, to blink an LED attached to a Raspberry Pi GPIO pin.*","x":170,"y":40,"wires":[]}]
|
@ -1,135 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "9b2d7459.8dd598",
|
||||
"type": "http in",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "",
|
||||
"url": "/hello-param/:name",
|
||||
"method": "get",
|
||||
"upload": false,
|
||||
"swaggerDoc": "",
|
||||
"x": 290,
|
||||
"y": 900,
|
||||
"wires": [
|
||||
[
|
||||
"83753c80.5e271"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7fe50f46.46209",
|
||||
"type": "http response",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "",
|
||||
"statusCode": "",
|
||||
"headers": {},
|
||||
"x": 590,
|
||||
"y": 900,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6e88d2b.828a92c",
|
||||
"type": "comment",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "Handle URL parameters in an HTTP endpoint",
|
||||
"info": "Named path parameters (e.g. `:name`) in the URL property can be used to identify parts of the path that can vary between requests.\n\nThe `msg.req.params` property is an object of key/value pairs for each path parameter.",
|
||||
"x": 290,
|
||||
"y": 860,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "214bc398.b3482c",
|
||||
"type": "http request",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "",
|
||||
"method": "GET",
|
||||
"ret": "txt",
|
||||
"paytoqs": "ignore",
|
||||
"url": "http://localhost:1880/hello-param/Nick",
|
||||
"tls": "",
|
||||
"persist": false,
|
||||
"proxy": "",
|
||||
"authType": "",
|
||||
"x": 390,
|
||||
"y": 1000,
|
||||
"wires": [
|
||||
[
|
||||
"70c0eba4.5f0dc4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "70c0eba4.5f0dc4",
|
||||
"type": "debug",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 550,
|
||||
"y": 1000,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "83753c80.5e271",
|
||||
"type": "template",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "page",
|
||||
"field": "payload",
|
||||
"fieldType": "msg",
|
||||
"format": "html",
|
||||
"syntax": "mustache",
|
||||
"template": "<html>\n <head></head>\n <body>\n <h1>Hello {{req.params.name}}!</h1>\n </body>\n</html>",
|
||||
"output": "str",
|
||||
"x": 470,
|
||||
"y": 900,
|
||||
"wires": [
|
||||
[
|
||||
"7fe50f46.46209"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "89523bfe.6e5c18",
|
||||
"type": "inject",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 240,
|
||||
"y": 1000,
|
||||
"wires": [
|
||||
[
|
||||
"214bc398.b3482c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6276a6cd.5c3b18",
|
||||
"type": "comment",
|
||||
"z": "d41b4dd3.ecd6a",
|
||||
"name": "Send HTTP GET request: http://localhost:1880/hello-param/Nick",
|
||||
"info": "`http request` node can be used to send **HTTP GET** request.",
|
||||
"x": 350,
|
||||
"y": 960,
|
||||
"wires": []
|
||||
}
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user