diff --git a/db/knex_migrations/2025-03-04-0000-ping-advanced-options.js b/db/knex_migrations/2025-03-04-0000-ping-advanced-options.js index 319fb18ef..58b86036a 100644 --- a/db/knex_migrations/2025-03-04-0000-ping-advanced-options.js +++ b/db/knex_migrations/2025-03-04-0000-ping-advanced-options.js @@ -2,7 +2,6 @@ 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_deadline INTEGER default 10 not null; -ALTER TABLE monitor ADD ping_timeout INTEGER default 2 not null; */ exports.up = function (knex) { // Add new columns to table monitor @@ -11,7 +10,6 @@ exports.up = function (knex) { table.integer("ping_count").defaultTo(1).notNullable(); table.boolean("ping_numeric").defaultTo(true).notNullable(); table.integer("ping_deadline").defaultTo(10).notNullable(); - table.integer("ping_timeout").defaultTo(2).notNullable(); }); }; @@ -22,6 +20,5 @@ exports.down = function (knex) { table.dropColumn("ping_count"); table.dropColumn("ping_numeric"); table.dropColumn("ping_deadline"); - table.dropColumn("ping_timeout"); }); }; diff --git a/server/model/monitor.js b/server/model/monitor.js index 40ca4e384..5f9d08786 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -164,7 +164,6 @@ class Monitor extends BeanModel { ping_numeric: this.isPingNumeric(), ping_count: this.ping_count, ping_deadline: this.ping_deadline, - ping_timeout: this.ping_timeout, }; if (includeSensitiveData) { @@ -635,7 +634,7 @@ class Monitor extends BeanModel { bean.status = UP; } else if (this.type === "ping") { - bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.ping_deadline, this.ping_timeout); + bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.ping_deadline, this.timeout); bean.msg = ""; bean.status = UP; } else if (this.type === "push") { // Type: Push @@ -1523,16 +1522,25 @@ class Monitor extends BeanModel { 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_deadline && (this.ping_deadline < PING_DEADLINE_MIN || this.ping_deadline > PING_DEADLINE_MAX)) { - throw new Error(`Deadline must be between ${PING_DEADLINE_MIN} and ${PING_DEADLINE_MAX} seconds (default: ${PING_DEADLINE_DEFAULT})`); - } + if (this.type === "ping") { + // ping parameters validation + if (this.ping_deadline && (this.ping_deadline < PING_DEADLINE_MIN || this.ping_deadline > PING_DEADLINE_MAX)) { + throw new Error(`Deadline must be between ${PING_DEADLINE_MIN} and ${PING_DEADLINE_MAX} seconds (default: ${PING_DEADLINE_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.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.ping_timeout && (this.ping_timeout < PING_TIMEOUT_MIN || this.ping_timeout > PING_TIMEOUT_MAX)) { - throw new Error(`Timeout must be between ${PING_TIMEOUT_MIN} and ${PING_TIMEOUT_MAX} seconds (default: ${PING_TIMEOUT_DEFAULT})`); + if (this.timeout) { + const pingTimeout = Math.round(Number(this.timeout)); + + if (pingTimeout < PING_TIMEOUT_MIN || pingTimeout > PING_TIMEOUT_MAX) { + throw new Error(`Timeout must be between ${PING_TIMEOUT_MIN} and ${PING_TIMEOUT_MAX} seconds (default: ${PING_TIMEOUT_DEFAULT})`); + } + + this.timeout = pingTimeout; + } } } diff --git a/server/server.js b/server/server.js index 38b93acf9..41a730b68 100644 --- a/server/server.js +++ b/server/server.js @@ -879,7 +879,6 @@ let needSetup = false; bean.ping_numeric = monitor.ping_numeric; bean.ping_count = monitor.ping_count; bean.ping_deadline = monitor.ping_deadline; - bean.ping_timeout = monitor.ping_timeout; bean.validate(); diff --git a/src/lang/en.json b/src/lang/en.json index 28bca7d87..9a01491c4 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1059,5 +1059,6 @@ "pingDeadlineLabel": "Max Duration", "pingDeadlineDescription": "Total time in seconds before ping stops, regardless of packets sent", "pingTimeoutLabel": "Response Timeout", - "pingTimeoutDescription": "Maximum time in seconds to wait for each response" + "pingTimeoutDescription": "Maximum time in seconds to wait for each response", + "pingIntervalAdjusted": "Interval has been adjusted according to deadline, timeout and packet count" } diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index abc96bdf7..fdd87ff26 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -597,8 +597,11 @@ <!-- Timeout: HTTP / JSON query / Keyword / Ping / RabbitMQ / SNMP only --> <div v-if="monitor.type === 'http' || monitor.type === 'json-query' || monitor.type === 'keyword' || monitor.type === 'ping' || monitor.type === 'rabbitmq' || monitor.type === 'snmp'" class="my-3"> - <label for="timeout" class="form-label">{{ $t("Request Timeout") }} ({{ $t("timeoutAfter", [ monitor.timeout || clampTimeout(monitor.interval) ]) }})</label> - <input id="timeout" v-model="monitor.timeout" type="number" class="form-control" required min="0" step="0.1"> + <label for="timeout" class="form-label"> + {{ timeoutLabel }} ({{ monitor.type === 'ping' ? $t("timeoutAfter", [monitor.timeout]) : $t("timeoutAfter", [monitor.timeout || clampTimeout(monitor.interval)]) }}) + </label> + <input id="timeout" v-model="monitor.timeout" type="number" class="form-control" :min="timeoutMin" :max="timeoutMax" :step="timeoutStep" required> + <div class="form-text">{{ timeoutDescription }}</div> </div> <div class="my-3"> @@ -695,15 +698,6 @@ </div> </div> - <!-- Response Timeout --> - <div v-if="monitor.type === 'ping'" class="my-3"> - <label for="ping_timeout" class="form-label">{{ $t("pingTimeoutLabel") }}</label> - <input id="ping_timeout" v-model="monitor.ping_timeout" type="number" class="form-control" required min="1" max="60" step="1"> - <div class="form-text"> - {{ $t("pingTimeoutDescription") }} - </div> - </div> - <!-- HTTP / Keyword only --> <template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'grpc-keyword' "> <div class="my-3"> @@ -1200,6 +1194,29 @@ export default { }, computed: { + timeoutStep() { + return this.monitor.type === "ping" ? 1 : 0.1; + }, + + timeoutMin() { + return this.monitor.type === "ping" ? 1 : 0; + }, + + timeoutMax() { + return this.monitor.type === "ping" ? 60 : undefined; + }, + + timeoutLabel() { + return this.monitor.type === "ping" ? this.$t("pingTimeoutLabel") : this.$t("Request Timeout"); + }, + + timeoutDescription() { + if (this.monitor.type === "ping") { + return this.$t("pingTimeoutDescription"); + } + return ""; + }, + ipRegex() { // Allow to test with simple dns server with port (127.0.0.1:5300) @@ -1458,9 +1475,25 @@ message HealthCheckResponse { }, "monitor.timeout"(value, oldValue) { - // keep timeout within 80% range - if (value && value !== oldValue) { - this.monitor.timeout = this.clampTimeout(value); + if (this.monitor.type === "ping") { + this.finishUpdateInterval(); + } else { + // keep timeout within 80% range + if (value && value !== oldValue) { + this.monitor.timeout = this.clampTimeout(value); + } + } + }, + + "monitor.ping_count"() { + if (this.monitor.type === "ping") { + this.finishUpdateInterval(); + } + }, + + "monitor.ping_deadline"() { + if (this.monitor.type === "ping") { + this.finishUpdateInterval(); } }, @@ -1489,8 +1522,10 @@ message HealthCheckResponse { // Set a default timeout if the monitor type has changed or if it's a new monitor if (oldType || this.isAdd) { if (this.monitor.type === "snmp") { - // snmp is not expected to be executed via the internet => we can choose a lower default timeout + // snmp is not expected to be executed via the internet => we can choose a lower default timeout this.monitor.timeout = 5; + } else if (this.monitor.type === "ping") { + this.monitor.timeout = 2; } else { this.monitor.timeout = 48; } @@ -1612,7 +1647,6 @@ message HealthCheckResponse { ping_numeric: true, packetSize: 56, ping_deadline: 10, - ping_timeout: 2, }; if (this.$root.proxyList && !this.monitor.proxyId) { @@ -1675,7 +1709,12 @@ message HealthCheckResponse { } // Handling for monitors that are missing/zeroed timeout if (!this.monitor.timeout) { - this.monitor.timeout = ~~(this.monitor.interval * 8) / 10; + if (this.monitor.type === "ping") { + // set to default + this.monitor.timeout = 2; + } else { + this.monitor.timeout = ~~(this.monitor.interval * 8) / 10; + } } } else { this.$root.toastError(res.msg); @@ -1888,11 +1927,49 @@ message HealthCheckResponse { return Number.isFinite(clamped) ? clamped : maxTimeout; }, + calculatePingInterval() { + // If monitor.type is not "ping", simply return the configured interval + if (this.monitor.type !== "ping") { + return this.monitor.interval; + } + + // Calculate the maximum theoretical time needed if all ping requests time out + const theoreticalTotal = this.monitor.ping_count * this.monitor.timeout; + + // The deadline forces ping to terminate, so the effective limit + // is the smaller value between deadline and theoreticalTotal + const effectiveLimit = Math.min(this.monitor.ping_deadline, theoreticalTotal); + + // Add a 10% margin to the effective limit to ensure proper handling + const adjustedLimit = Math.ceil(effectiveLimit * 1.1); + + // If the calculated limit is less than the minimum allowed interval, + // use the minimum interval to ensure stability + if (adjustedLimit < this.minInterval) { + return this.minInterval; + } + + return adjustedLimit; + }, + finishUpdateInterval() { - // Update timeout if it is greater than the clamp timeout - let clampedValue = this.clampTimeout(this.monitor.interval); - if (this.monitor.timeout > clampedValue) { - this.monitor.timeout = clampedValue; + if (this.monitor.type === "ping") { + // Calculate the minimum required interval based on ping configuration + const calculatedPingInterval = this.calculatePingInterval(); + + // If the configured interval is too small, adjust it to the minimum required value + if (this.monitor.interval < calculatedPingInterval) { + this.monitor.interval = calculatedPingInterval; + + // Notify the user that the interval has been automatically adjusted + toast.info(this.$t("pingIntervalAdjusted")); + } + } else { + // Update timeout if it is greater than the clamp timeout + let clampedValue = this.clampTimeout(this.monitor.interval); + if (this.monitor.timeout > clampedValue) { + this.monitor.timeout = clampedValue; + } } },