Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced ping monitor with advanced options (count, timeout, numeric) #5588

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
10c81bb
Added new columns to monitor table for configuring advanced ping beha…
filippolauria Jan 31, 2025
ffd0157
added ping options patch to patchList
filippolauria Jan 31, 2025
55075f1
build: added constants for ping advanced options and rebuilt util.js
filippolauria Jan 31, 2025
4bd775e
added advanced ping options to server and monitor and also added vali…
filippolauria Jan 31, 2025
e1bdc53
refactor: enhanced ping function with advanced options and docs
filippolauria Jan 31, 2025
7371cef
added UI for advanced ping configuration
filippolauria Jan 31, 2025
e661b9f
fixed ESLint issues and regenerated util.js
filippolauria Jan 31, 2025
c58c76c
Fixed check-linters errors
filippolauria Jan 31, 2025
41a4dbc
Merge branch 'master' into master
filippolauria Feb 1, 2025
003ffc2
Merge branch 'master' into master
filippolauria Feb 10, 2025
e0166f5
migrated from old to new migration system based on knex
filippolauria Mar 4, 2025
0bb2756
added/fixed translations
filippolauria Mar 4, 2025
f275d56
fix: numeric flag now properly passed to the underlying function
filippolauria Mar 4, 2025
32ed245
Merge branch 'master' into master
filippolauria Mar 4, 2025
d12a0b3
inline ping validation constants
filippolauria Mar 6, 2025
1b2b9d4
Merge branch 'master' of https://github.com/filippolauria/uptime-kuma
filippolauria Mar 6, 2025
3c07c79
Merge branch 'master' into master
filippolauria Mar 11, 2025
7110c6d
added binding between ping model parameters and the new ping implemen…
filippolauria Mar 11, 2025
e8207fe
add ping to timeout field and alphabetically sort monitor types
filippolauria Mar 11, 2025
a769a2d
removed ping_timeout field in favor of common timeout field
filippolauria Mar 12, 2025
16e3b05
Merge branch 'master' of https://github.com/filippolauria/uptime-kuma
filippolauria Mar 12, 2025
59c379a
clarify ping timeout labels and descriptions
filippolauria Mar 14, 2025
a59331e
Merge branch 'master' into master
filippolauria Mar 14, 2025
ab8d6dd
remove deadline field and repurpose timeout semantics
filippolauria Mar 17, 2025
5026ab0
Merge branch 'master' into master
filippolauria Mar 18, 2025
693af34
Merge branch 'master' into master
filippolauria Mar 21, 2025
fb499c8
Merge branch 'master' into master
filippolauria Mar 22, 2025
6eeb61f
Merge branch 'master' into master
filippolauria Mar 24, 2025
e284ed7
Merge branch 'master' into master
filippolauria Mar 26, 2025
9935311
Merge branch 'master' into master
filippolauria Mar 29, 2025
420cd73
Merge branch 'master' into master
filippolauria Apr 7, 2025
16158e0
Merge branch 'master' into master
filippolauria Apr 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions db/knex_migrations/2025-03-04-0000-ping-advanced-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* SQL:
ALTER TABLE monitor ADD ping_count INTEGER default 1 not null;
ALTER TABLE monitor ADD ping_numeric BOOLEAN default true not null;
ALTER TABLE monitor ADD ping_per_request_timeout INTEGER default 2 not null;
*/
exports.up = function (knex) {
// Add new columns to table monitor
return knex.schema
.alterTable("monitor", function (table) {
table.integer("ping_count").defaultTo(1).notNullable();
table.boolean("ping_numeric").defaultTo(true).notNullable();
table.integer("ping_per_request_timeout").defaultTo(2).notNullable();
});

};

exports.down = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.dropColumn("ping_count");
table.dropColumn("ping_numeric");
table.dropColumn("ping_per_request_timeout");
});
};
48 changes: 45 additions & 3 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ const dayjs = require("dayjs");
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT, evaluateJsonQuery
SQL_DATETIME_FORMAT, evaluateJsonQuery,
PING_PACKET_SIZE_MIN, PING_PACKET_SIZE_MAX, PING_PACKET_SIZE_DEFAULT,
PING_GLOBAL_TIMEOUT_MIN, PING_GLOBAL_TIMEOUT_MAX, PING_GLOBAL_TIMEOUT_DEFAULT,
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
} = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
Expand Down Expand Up @@ -155,6 +159,11 @@ class Monitor extends BeanModel {
snmpVersion: this.snmpVersion,
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
conditions: JSON.parse(this.conditions),

// ping advanced options
ping_numeric: this.isPingNumeric(),
ping_count: this.ping_count,
ping_per_request_timeout: this.ping_per_request_timeout,
};

if (includeSensitiveData) {
Expand Down Expand Up @@ -247,6 +256,14 @@ class Monitor extends BeanModel {
return Boolean(this.expiryNotification);
}

/**
* Check if ping should use numeric output only
* @returns {boolean} True if IP addresses will be output instead of symbolic hostnames
*/
isPingNumeric() {
return Boolean(this.ping_numeric);
}

/**
* Parse to boolean
* @returns {boolean} Should TLS errors be ignored?
Expand Down Expand Up @@ -617,7 +634,7 @@ class Monitor extends BeanModel {
bean.status = UP;

} else if (this.type === "ping") {
bean.ping = await ping(this.hostname, this.packetSize);
bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.timeout, this.ping_per_request_timeout);
bean.msg = "";
bean.status = UP;
} else if (this.type === "push") { // Type: Push
Expand Down Expand Up @@ -689,7 +706,7 @@ class Monitor extends BeanModel {
bean.msg = res.data.response.servers[0].name;

try {
bean.ping = await ping(this.hostname, this.packetSize);
bean.ping = await ping(this.hostname, PING_COUNT_DEFAULT, "", true, this.packetSize, PING_GLOBAL_TIMEOUT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT);
} catch (_) { }
} else {
throw new Error("Server not found on Steam");
Expand Down Expand Up @@ -1500,6 +1517,31 @@ class Monitor extends BeanModel {
if (this.interval < MIN_INTERVAL_SECOND) {
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
}

if (this.type === "ping") {
// ping parameters validation
if (this.packetSize && (this.packetSize < PING_PACKET_SIZE_MIN || this.packetSize > PING_PACKET_SIZE_MAX)) {
throw new Error(`Packet size must be between ${PING_PACKET_SIZE_MIN} and ${PING_PACKET_SIZE_MAX} (default: ${PING_PACKET_SIZE_DEFAULT})`);
}

if (this.ping_per_request_timeout && (this.ping_per_request_timeout < PING_PER_REQUEST_TIMEOUT_MIN || this.ping_per_request_timeout > PING_PER_REQUEST_TIMEOUT_MAX)) {
throw new Error(`Per-ping timeout must be between ${PING_PER_REQUEST_TIMEOUT_MIN} and ${PING_PER_REQUEST_TIMEOUT_MAX} seconds (default: ${PING_PER_REQUEST_TIMEOUT_DEFAULT})`);
}

if (this.ping_count && (this.ping_count < PING_COUNT_MIN || this.ping_count > PING_COUNT_MAX)) {
throw new Error(`Echo requests count must be between ${PING_COUNT_MIN} and ${PING_COUNT_MAX} (default: ${PING_COUNT_DEFAULT})`);
}

if (this.timeout) {
const pingGlobalTimeout = Math.round(Number(this.timeout));

if (pingGlobalTimeout < this.ping_per_request_timeout || pingGlobalTimeout < PING_GLOBAL_TIMEOUT_MIN || pingGlobalTimeout > PING_GLOBAL_TIMEOUT_MAX) {
throw new Error(`Timeout must be between ${PING_GLOBAL_TIMEOUT_MIN} and ${PING_GLOBAL_TIMEOUT_MAX} seconds (default: ${PING_GLOBAL_TIMEOUT_DEFAULT})`);
}

this.timeout = pingGlobalTimeout;
}
}
}

/**
Expand Down
5 changes: 5 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,11 @@ let needSetup = false;
bean.rabbitmqPassword = monitor.rabbitmqPassword;
bean.conditions = JSON.stringify(monitor.conditions);

// ping advanced options
bean.ping_numeric = monitor.ping_numeric;
bean.ping_count = monitor.ping_count;
bean.ping_per_request_timeout = monitor.ping_per_request_timeout;

bean.validate();

await R.store(bean);
Expand Down
58 changes: 46 additions & 12 deletions server/util-server.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const tcpp = require("tcp-ping");
const ping = require("@louislam/ping");
const { R } = require("redbean-node");
const { log, genSecret, badgeConstants } = require("../src/util");
const {
log, genSecret, badgeConstants,
PING_PACKET_SIZE_DEFAULT, PING_GLOBAL_TIMEOUT_DEFAULT,
PING_COUNT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT
} = require("../src/util");
const passwordHash = require("./password-hash");
const { Resolver } = require("dns");
const iconv = require("iconv-lite");
Expand Down Expand Up @@ -118,20 +122,33 @@ exports.tcping = function (hostname, port) {

/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine
* @param {number} size Size of packet to send
* @param {string} destAddr Hostname / IP address of machine to ping
* @param {number} count Number of packets to send before stopping
* @param {string} sourceAddr Source address for sending/receiving echo requests
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
* @param {number} size Size (in bytes) of echo request to send
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
* @param {number} timeout Maximum time in seconds to wait for each response
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.ping = async (hostname, size = 56) => {
exports.ping = async (
destAddr,
count = PING_COUNT_DEFAULT,
sourceAddr = "",
numeric = true,
size = PING_PACKET_SIZE_DEFAULT,
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
) => {
try {
return await exports.pingAsync(hostname, false, size);
return await exports.pingAsync(destAddr, false, count, sourceAddr, numeric, size, deadline, timeout);
} catch (e) {
// If the host cannot be resolved, try again with ipv6
log.debug("ping", "IPv6 error message: " + e.message);

// As node-ping does not report a specific error for this, try again if it is an empty message with ipv6 no matter what.
if (!e.message) {
return await exports.pingAsync(hostname, true, size);
return await exports.pingAsync(destAddr, true, count, sourceAddr, numeric, size, deadline, timeout);
} else {
throw e;
}
Expand All @@ -140,18 +157,35 @@ exports.ping = async (hostname, size = 56) => {

/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine to ping
* @param {string} destAddr Hostname / IP address of machine to ping
* @param {boolean} ipv6 Should IPv6 be used?
* @param {number} size Size of ping packet to send
* @param {number} count Number of packets to send before stopping
* @param {string} sourceAddr Source address for sending/receiving echo requests
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
* @param {number} size Size (in bytes) of echo request to send
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
* @param {number} timeout Maximum time in seconds to wait for each response
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
exports.pingAsync = function (
destAddr,
ipv6 = false,
count = PING_COUNT_DEFAULT,
sourceAddr = "",
numeric = true,
size = PING_PACKET_SIZE_DEFAULT,
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
) {
return new Promise((resolve, reject) => {
ping.promise.probe(hostname, {
ping.promise.probe(destAddr, {
v6: ipv6,
min_reply: 1,
deadline: 10,
min_reply: count,
sourceAddr: sourceAddr,
numeric: numeric,
packetSize: size,
deadline: deadline,
timeout: timeout
}).then((res) => {
// If ping failed, it will set field to unknown
if (res.alive) {
Expand Down
9 changes: 9 additions & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,15 @@
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
"pingCountLabel": "Max Packets",
"pingCountDescription": "Number of packets to send before stopping",
"pingNumericLabel": "Numeric Output",
"pingNumericDescription": "If checked, IP addresses will be output instead of symbolic hostnames",
"pingGlobalTimeoutLabel": "Global Timeout",
"pingGlobalTimeoutDescription": "Total time in seconds before ping stops, regardless of packets sent",
"pingPerRequestTimeoutLabel": "Per-Ping Timeout",
"pingPerRequestTimeoutDescription": "This is the maximum waiting time (in seconds) before considering a single ping packet lost",
"pingIntervalAdjustedInfo": "Interval adjusted based on packet count, global timeout and per-ping timeout",
"wahaSession": "Session",
"wahaChatId": "Chat ID (Phone Number / Contact ID / Group ID)",
"wayToGetWahaApiUrl": "Your WAHA Instance URL.",
Expand Down
Loading
Loading