new features: Add Triggered Mode to Ping Node (#643)

This commit is contained in:
Stephen McLaughlin 2020-04-03 16:07:41 +01:00 committed by GitHub
parent 3087e8e2a1
commit 437c29525d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 336 additions and 60 deletions

View File

@ -1,10 +1,17 @@
<script type="text/x-red" data-template-name="ping">
<div class="form-row">
<script type="text/html" data-template-name="ping">
<div class="form-row" id="div-node-input-host">
<label for="node-input-host"><i class="fa fa-dot-circle-o"></i> <span data-i18n="ping.label.target"></span></label>
<input type="text" id="node-input-host" placeholder="www.google.com">
<input type="text" id="node-input-host" placeholder="192.168.0.1, www.google.com">
</div>
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-wrench"></i> <span data-i18n="ping.label.mode"></label>
<select type="text" id="node-input-mode" style="width: 70%">
<option value="timed" data-i18n="ping.label.mode_option.timed"></option>
<option value="triggered" data-i18n="ping.label.mode_option.triggered"></option>
</select>
</div>
<div class="form-row" id="div-node-input-timer">
<label for="node-input-timer"><i class="fa fa-repeat"></i> <span data-i18n="ping.label.ping"></label>
<input type="text" id="node-input-timer" placeholder="20">
</div>
@ -15,13 +22,48 @@
</script>
<script type="text/javascript">
RED.nodes.registerType('ping',{
category: 'network-input',
var timerParameterValidator = function(node,v){
var mode = getMode(node)
if(mode === "triggered"){
return true;
}
if(v == ""){
return false;
}
return RED.validators.number()(v);
}
var hostParameterValidator = function(node,v){
var mode = getMode(node)
if(mode === "triggered"){
return true;
}
return v != ""
}
var getMode = function(node){
if(node){
return node.mode
} else {
let $mode = $( "#node-input-mode" );
return $mode.val();
}
}
RED.nodes.registerType("ping",{
category: "network-input",
color:"#fdf0c2",
defaults: {
mode: {value:"timed"},
name: {value:""},
host: {value:"",required:true},
timer: {value:"20", required:true, validate:RED.validators.number()}
host: {value:"", validate: function(v){
return hostParameterValidator(this,v) ;
}
},
timer: {value:"20", validate: function(v){
return timerParameterValidator(this,v) ;
}
},
inputs: {value:0}
},
inputs:0,
outputs:1,
@ -30,10 +72,37 @@
return this._("ping.ping");
},
label: function() {
return this.name||this.host;
let lbl = this.name||this.host||this._("ping.ping");
if(lbl.length > 20){
lbl = lbl.substring(0,17) + "..."
}
return lbl;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function () {
let node = this;
let $timer = $("#div-node-input-timer");
$("#node-input-mode").val(node.mode);
let $mode = $( "#node-input-mode" );
function updateControlsVisibility(){
node.mode = $mode.val();
switch (node.mode) {
case "triggered":
node.inputs = 1;
node._def.inputs = 1
$timer.hide();
break;
default:
node.inputs = 0;
node._def.inputs = 0;
$timer.show();
break;
}
}
$mode.change(updateControlsVisibility);
updateControlsVisibility();
}
});
</script>

View File

@ -1,61 +1,134 @@
module.exports = function(RED) {
"use strict";
var spawn = require('child_process').spawn;
var plat = require('os').platform();
var spawn = require("child_process").spawn;
var plat = require("os").platform();
function doPing(node, host, arrayMode){
const defTimeout = 5000;
var ex, hostOptions, commandLineOptions;
if(typeof host === "string"){
hostOptions = {
host: host,
timeout: defTimeout
}
} else {
hostOptions = host;
hostOptions.timeout = isNaN(parseInt(hostOptions.timeout)) ? defTimeout : parseInt(hostOptions.timeout);
}
//clamp timeout between 1 and 30 sec
hostOptions.timeout = hostOptions.timeout < 1000 ? 1000 : hostOptions.timeout;
hostOptions.timeout = hostOptions.timeout > 30000 ? 30000 : hostOptions.timeout;
var timeoutS = Math.round(hostOptions.timeout / 1000); //whole numbers only
var msg = { payload:false, topic:hostOptions.host };
//only include the extra msg object if operating in advance/array mode.
if(arrayMode){
msg.ping = hostOptions
}
if (plat == "linux" || plat == "android") {
commandLineOptions = ["-n", "-w", timeoutS, "-c", "1"]
} else if (plat.match(/^win/)) {
commandLineOptions = ["-n", "1", "-w", hostOptions.timeout]
} else if (plat == "darwin" || plat == "freebsd") {
commandLineOptions = ["-n", "-t", timeoutS, "-c", "1"]
} else {
node.error("Sorry - your platform - "+plat+" - is not recognised.", msg);
return; //dont pass go - just return!
}
//spawn with timeout in case of os issue
ex = spawn("ping", [...commandLineOptions, hostOptions.host]);
//monitor every spawned process & SIGINT if too long
var spawnTout = setTimeout(() => {
node.log(`ping - Host '${hostOptions.host}' process timeout - sending SIGINT`)
try{if(ex && ex.pid){ ex.kill("SIGINT"); }} catch(e){console.warn(e)}
}, hostOptions.timeout+1000); //add 1s for grace
var res = false;
var line = "";
var fail = false;
//var regex = /from.*time.(.*)ms/;
var regex = /=.*[<|=]([0-9]*).*TTL|ttl..*=([0-9\.]*)/;
ex.stdout.on("data", function (data) {
line += data.toString();
});
ex.on("exit", function (err) {
clearTimeout(spawnTout);
});
ex.on("error", function (err) {
fail = true;
if (err.code === "ENOENT") {
node.error(err.code + " ping command not found", msg);
}
else if (err.code === "EACCES") {
node.error(err.code + " can't run ping command", msg);
}
else {
node.error(err.code, msg);
}
});
ex.on("close", function (code) {
if (fail) { fail = false; return; }
var m = regex.exec(line)||"";
if (m !== "") {
if (m[1]) { res = Number(m[1]); }
if (m[2]) { res = Number(m[2]); }
}
if (code === 0) { msg.payload = res }
try { node.send(msg); }
catch(e) {console.warn(e)}
});
}
function PingNode(n) {
RED.nodes.createNode(this,n);
this.mode = n.mode;
this.host = n.host;
this.timer = n.timer * 1000;
var node = this;
node.tout = setInterval(function() {
var ex;
if (plat == "linux" || plat == "android") { ex = spawn('ping', ['-n', '-w', '5', '-c', '1', node.host]); }
else if (plat.match(/^win/)) { ex = spawn('ping', ['-n', '1', '-w', '5000', node.host]); }
else if (plat == "darwin" || plat == "freebsd") { ex = spawn('ping', ['-n', '-t', '5', '-c', '1', node.host]); }
else { node.error("Sorry - your platform - "+plat+" - is not recognised."); }
var res = false;
var line = "";
var fail = false;
//var regex = /from.*time.(.*)ms/;
var regex = /=.*[<|=]([0-9]*).*TTL|ttl..*=([0-9\.]*)/;
ex.stdout.on('data', function (data) {
line += data.toString();
});
//ex.stderr.on('data', function (data) {
//console.log('[ping] stderr: ' + data);
//});
ex.on('error', function (err) {
fail = true;
if (err.code === "ENOENT") {
node.error(err.code + " ping command not found");
function generatePingList(str) {
return (str + "").split(",").map((e) => (e + "").trim()).filter((e) => e != "");
}
function clearPingInterval(){
if (node.tout) { clearInterval(node.tout); }
}
if(node.mode === "triggered"){
clearPingInterval();
} else if(node.timer){
node.tout = setInterval(function() {
let pingables = generatePingList(node.host);
for (let index = 0; index < pingables.length; index++) {
const element = pingables[index];
if(element){ doPing(node, element, false); }
}
else if (err.code === "EACCES") {
node.error(err.code + " can't run ping command");
}, node.timer);
}
this.on("input", function (msg) {
let node = this;
let payload = node.host || msg.payload;
if(typeof payload == "string"){
let pingables = generatePingList(payload)
for (let index = 0; index < pingables.length; index++) {
const element = pingables[index];
if(element){ doPing(node, element, false); }
}
else {
node.error(err.code);
} else if (Array.isArray(payload) ) {
for (let index = 0; index < payload.length; index++) {
const element = payload[index];
if(element){ doPing(node, element, true); }
}
});
ex.on('close', function (code) {
if (fail) { fail = false; return; }
var m = regex.exec(line)||"";
if (m !== '') {
if (m[1]) { res = Number(m[1]); }
if (m[2]) { res = Number(m[2]); }
}
var msg = { payload:false, topic:node.host };
if (code === 0) { msg = { payload:res, topic:node.host }; }
try { node.send(msg); }
catch(e) {}
});
}, node.timer);
}
});
this.on("close", function() {
if (this.tout) { clearInterval(this.tout); }
clearPingInterval();
});
}
RED.nodes.registerType("ping",PingNode);
}
}

View File

@ -27,7 +27,7 @@ The fix is to allow it as follows
Usage
-----
Pings a machine and returns the trip time in mS as `msg.payload`.
Pings 1 or more devices and returns the trip time in mS as `msg.payload`.
Returns boolean `false` if no response received, or if the host is unresolveable.
@ -35,4 +35,9 @@ Returns boolean `false` if no response received, or if the host is unresolveable
`msg.topic` contains the ip address of the target host.
Default ping is every 20 seconds but can be configured.
There are 2 modes - `Timed` and `Triggered`.
* Timed mode - this is the default mode that pings your devices on a timed basis. Default ping is every 20 seconds but can be configured.
* Triggered mode - this mode permits you to trigger the ping by an input message. If the `Target` is left blank and `msg.payload` is a string or array, you can ping 1 or more devices on demand.
Refer to the built in help on the side-bar info panel for more details.

View File

@ -1,8 +1,53 @@
<script type="text/x-red" data-help-name="ping">
<script type="text/html" data-help-name="ping">
<p>Pings a machine and returns the trip time in mS as <code>msg.payload</code>.</p>
<p>Returns <b>false</b> if no response received within 5 seconds, or if the host is unresolveable.</p>
<h3>Output</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd> the trip time in mS.</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd> the target host/ip</dd>
<dt>ping <span class="property-type">object</span></dt>
<dd> an object containing <code>host</code> and any other properties sent in the array object. <br>NOTE: This object is only appended when using triggered mode and an array for input payload. It is intended for adavanced users and permits scenarios where you need additional properties to be tagged into your result for use downstream.</dd>
</dl>
<h3>Details</h3>
<p>Returns <b>false</b> if no response received, or if the host is unresolveable.</p>
<p>Default ping is every 20 seconds but can be configured.</p>
<p><code>msg.topic</code> contains the target host ip.</p>
<h4>Mode...</h4>
<ul>
<li><b>Timed</b><br>
<P>In <code>Timed</code> mode, the fields <code>Target</code> and <code>Ping (S)</code> must be populated.</P>
<p><code>Target</code> must be a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code></p>
<p><code>Ping (S)</code> is the number of seconds between pings</p>
</li>
<li><b>Triggered</b><br>
<p>In <code>Triggered</code> mode, you must connect an input wire and pass a <code>msg</code> in to trigger the ping operation.</p>
<p>If <code>Target</code> is populated, this will be used as the host/ip. The Target must be is a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code></p>
<p>If <code>Target</code> is left empty, you can pass a CSV string or an array of hosts in `msg.payload`
<ul>
<li><code>string</code> - a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code> </li>
<li><code>array</code> - an array of hosts as string or object. NOTE: The object must contain at minimum <code>.host</code>. Optionally, you can add a <code>timeout</code> property between 1000 & 30000 (default is 5000 / 5 seconds). Additionally, you can add whatever other properties you wish to this object and when the ping result is returned, it will be passed to the next node in <code>msg.ping</code> for use downstream</li>
<li>Example array payload input: <pre>[
"192.168.0.99",
{
"host":"192.168.0.1",
"name":"The router"
},
{
"host":"myapiserver.com",
"name":"external API",
"timeout": 20000,
"support":"support@myapiserver.com"
}
]</pre> </li>
</ul>
</p>
</li>
</ul>
<p>Note: if running inside Ubuntu Snap you will need to manually start the network-observe interface.
`snap connect node-red:network-observe`</p>
</script>

View File

@ -3,7 +3,12 @@
"ping": "ping",
"label": {
"target": "Target",
"ping": "Ping (S)"
"ping": "Ping (S)",
"mode": "Mode",
"mode_option": {
"timed": "Timed",
"triggered": "Triggered"
}
}
}
}

View File

@ -1,8 +1,82 @@
<script type="text/x-red" data-help-name="ping">
<!-- <script type="text/html" data-help-name="ping">
<p>pingを送信し、<code>msg.payload</code>にラウンドトリップタイムをミリ秒で返します。</p>
<p>5秒以内にホスト名が解決できなかったり、レスポンスが得られなければ、<b>false</b>を返します。</p>
<p>デフォルトでは20秒ごとにpingを送りますが、設定で変更できます。</p>
<p>Additionally, <code>msg.ping</code> will contain details and options you sent in the input <code>msg</code> (see below for details) </p>
<p><code>msg.topic</code>には対象に設定したホスト名やIPアドレスが含まれます。</p>
<p>デフォルトでは20秒ごとにpingを送りますが、設定で変更できます。</p>
<h3>モード...</h3>
<ul>
<li><b>時限</b><br>
In <code>時限</code> mode, the fields <code>Target</code> and <code>Ping (S)</code> must be populated.
</li>
<li><b>引き金になった</b><br>
In <code>引き金になった</code> mode, you must connect an input wire and pass a <code>msg</code> in to triger the ping operation.
If <code>対象</code> is populated, this will be used as the host/ip.
Alternatively, If <code>対象</code> is left empty, you can pass a string or an array in
<ul>
<li><code>string</code> - a host name or IP address</li>
<li><code>array</code> - an array of host names (string) or objects. NOTE: The object must contain at minimum <code>.host</code>. You can add whatever properties you wish to this object and when the ping result is returned, it will be passed to the next node in <code>msg.ping</code> for use downstream</li>
<li>Example payload input: <code>["192.168.0.99", {"host":"192.168.0.1", "name":"The router"}, {"host":"myapiserver.com", "name":"external API", "support":"support@myapiserver.com"}]</code></li>
</ul>
</li>
</ul>
<p>補足: Ubuntu Snap内で実行している場合は、手動でnetwork-observeを開始する必要があります。
`snap connect node-red:network-observe`</p>
</script> -->
<script type="text/html" data-help-name="ping">
<p>pingを送信し、<code>msg.payload</code>にラウンドトリップタイムをミリ秒で返します。</p>
<h3>Output</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd> the trip time in mS.</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd> the target host/ip</dd>
<dt>ping <span class="property-type">object</span></dt>
<dd> an object containing <code>host</code> and any other properties sent in the array object. <br>NOTE: This object is only appended when using triggered mode and an array for input payload. It is intended for adavanced users and permits scenarios where you need additional properties to be tagged into your result for use downstream.</dd>
</dl>
<h3>Details</h3>
<p>5秒以内にホスト名が解決できなかったり、レスポンスが得られなければ、<b>false</b>を返します。</p>
<p>デフォルトでは20秒ごとにpingを送りますが、設定で変更できます。</p>
<h4>Mode...</h4>
<ul>
<li><b>Timed</b><br>
<P>In <code>Timed</code> mode, the fields <code>Target</code> and <code>Ping (S)</code> must be populated.</P>
<p><code>Target</code> must be a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code></p>
<p><code>Ping (S)</code> is the number of seconds between pings</p>
</li>
<li><b>Triggered</b><br>
<p>In <code>Triggered</code> mode, you must connect an input wire and pass a <code>msg</code> in to trigger the ping operation.</p>
<p>If <code>Target</code> is populated, this will be used as the host/ip. The Target must be is a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code></p>
<p>If <code>Target</code> is left empty, you can pass a CSV string or an array of hosts in `msg.payload`
<ul>
<li><code>string</code> - a CSV list of hosts / IPs e.g. <code>"192.168.0.1"</code> or <code>"192.168.0.1, www.google.com"</code> </li>
<li><code>array</code> - an array of hosts as string or object. NOTE: The object must contain at minimum <code>.host</code>. Optionally, you can add a <code>timeout</code> property between 1000 & 30000 (default is 5000 / 5 seconds). Additionally, you can add whatever other properties you wish to this object and when the ping result is returned, it will be passed to the next node in <code>msg.ping</code> for use downstream</li>
<li>Example array payload input: <pre>[
"192.168.0.99",
{
"host":"192.168.0.1",
"name":"The router"
},
{
"host":"myapiserver.com",
"name":"external API",
"timeout": 20000,
"support":"support@myapiserver.com"
}
]</pre> </li>
</ul>
</p>
</li>
</ul>
<p>補足: Ubuntu Snap内で実行している場合は、手動でnetwork-observeを開始する必要があります。
`snap connect node-red:network-observe`</p>
</script>

View File

@ -3,7 +3,12 @@
"ping": "ping",
"label": {
"target": "対象",
"ping": "Ping (秒)"
"ping": "Ping (秒)",
"mode": "モード",
"mode_option": {
"timed": "時限",
"triggered": "引き金になった"
}
}
}
}