From 2202e09ee947c9845137240bdf9f3595b8ab66ac Mon Sep 17 00:00:00 2001 From: "oleksandr.shostak@consultic.com" Date: Wed, 14 Jan 2026 09:52:12 +0200 Subject: [PATCH 1/4] Add recovery check interval and build frontend in Docker --- compose.yaml | 5 +++- ...2026-01-16-0000-add-down-retry-interval.js | 11 +++++++ docker/dockerfile | 6 ++-- server/model/monitor.js | 11 +++++++ server/server.js | 1 + src/lang/en.json | 2 ++ src/pages/EditMonitor.vue | 29 ++++++++++++++++++- 7 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 db/knex_migrations/2026-01-16-0000-add-down-retry-interval.js diff --git a/compose.yaml b/compose.yaml index 914e8603d..e170f85ba 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,6 +1,9 @@ services: uptime-kuma: - image: louislam/uptime-kuma:2 + image: uptime-kuma:consultic-2.1.0-beta.1 + build: + context: . + dockerfile: docker/dockerfile restart: unless-stopped volumes: - ./data:/app/data diff --git a/db/knex_migrations/2026-01-16-0000-add-down-retry-interval.js b/db/knex_migrations/2026-01-16-0000-add-down-retry-interval.js new file mode 100644 index 000000000..41bbdb836 --- /dev/null +++ b/db/knex_migrations/2026-01-16-0000-add-down-retry-interval.js @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.integer("down_retry_interval").notNullable().defaultTo(0); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("down_retry_interval"); + }); +}; diff --git a/docker/dockerfile b/docker/dockerfile index e2a301e7b..275cb6ef7 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -15,12 +15,15 @@ USER node WORKDIR /app ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 COPY --chown=node:node .npmrc .npmrc COPY --chown=node:node package.json package.json COPY --chown=node:node package-lock.json package-lock.json -RUN npm ci --omit=dev +RUN npm ci COPY . . COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck +RUN npm run build +RUN npm prune --omit=dev RUN mkdir ./data ############################################ @@ -115,4 +118,3 @@ RUN chmod +x /app/extra/upload-github-release-asset.sh # Dist only RUN cd /app && tar -zcvf $DIST dist RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=/app/$DIST - diff --git a/server/model/monitor.js b/server/model/monitor.js index 01d655f23..0e8c9d29d 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -150,6 +150,7 @@ class Monitor extends BeanModel { interval: this.interval, retryInterval: this.retryInterval, retryOnlyOnStatusCodeFailure: Boolean(this.retry_only_on_status_code_failure), + downRetryInterval: this.downRetryInterval, resendInterval: this.resendInterval, keyword: this.keyword, invertKeyword: this.isInvertKeyword(), @@ -1079,6 +1080,9 @@ class Monitor extends BeanModel { } else if (bean.status === MAINTENANCE) { log.warn("monitor", `Monitor #${this.id} '${this.name}': Under Maintenance | Type: ${this.type}`); } else { + if (this.downRetryInterval > 0) { + beatInterval = this.downRetryInterval; + } log.warn( "monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}` @@ -1706,6 +1710,13 @@ class Monitor extends BeanModel { throw new Error(`Retry interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`); } + if (this.downRetryInterval > MAX_INTERVAL_SECOND) { + throw new Error(`Down retry interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`); + } + if (this.downRetryInterval < MIN_INTERVAL_SECOND) { + throw new Error(`Down retry interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`); + } + if (this.response_max_length !== undefined) { if (this.response_max_length < 0) { throw new Error(`Response max length cannot be less than 0`); diff --git a/server/server.js b/server/server.js index 885e88340..4ffd038a9 100644 --- a/server/server.js +++ b/server/server.js @@ -844,6 +844,7 @@ let needSetup = false; bean.tlsKey = monitor.tlsKey; bean.interval = monitor.interval; bean.retryInterval = monitor.retryInterval; + bean.downRetryInterval = monitor.downRetryInterval; bean.resendInterval = monitor.resendInterval; bean.hostname = monitor.hostname; bean.game = monitor.game; diff --git a/src/lang/en.json b/src/lang/en.json index 342e8267e..f4f075786 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -79,10 +79,12 @@ "timeoutAfter": "Timeout after {0} seconds", "Retries": "Retries", "Heartbeat Retry Interval": "Heartbeat Retry Interval", + "Recovery Check Interval": "Recovery Check Interval", "Resend Notification if Down X times consecutively": "Resend Notification if Down X times consecutively", "Advanced": "Advanced", "checkEverySecond": "Check every {0} seconds", "retryCheckEverySecond": "Retry every {0} seconds", + "downCheckEverySecond": "Check every {0} seconds while down", "resendEveryXTimes": "Resend every {0} times", "resendDisabled": "Resend disabled", "retriesDescription": "Maximum retries before the service is marked as down and a notification is sent", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index ec0676514..fd0b43468 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1185,6 +1185,26 @@ +
+ + +
+ {{ $t("minimumIntervalWarning") }} +
+
+
Date: Wed, 14 Jan 2026 09:55:07 +0200 Subject: [PATCH 2/4] Fix ownership for Docker build copy --- docker/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/dockerfile b/docker/dockerfile index 275cb6ef7..4f831e5e9 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -20,7 +20,7 @@ COPY --chown=node:node .npmrc .npmrc COPY --chown=node:node package.json package.json COPY --chown=node:node package-lock.json package-lock.json RUN npm ci -COPY . . +COPY --chown=node:node . . COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck RUN npm run build RUN npm prune --omit=dev From 01b57242244fc3d179a5dd94104cbb2bca8637cc Mon Sep 17 00:00:00 2001 From: "oleksandr.shostak@consultic.com" Date: Wed, 14 Jan 2026 10:11:56 +0200 Subject: [PATCH 3/4] Allow disabling recovery interval --- server/model/monitor.js | 16 ++++++++++------ src/lang/en.json | 2 ++ src/pages/EditMonitor.vue | 22 +++++++++++++++------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 0e8c9d29d..f95b906ea 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1083,9 +1083,10 @@ class Monitor extends BeanModel { if (this.downRetryInterval > 0) { beatInterval = this.downRetryInterval; } + const intervalNote = this.downRetryInterval > 0 ? " (recovery)" : ""; log.warn( "monitor", - `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}` + `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds${intervalNote} | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}` ); } @@ -1710,11 +1711,14 @@ class Monitor extends BeanModel { throw new Error(`Retry interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`); } - if (this.downRetryInterval > MAX_INTERVAL_SECOND) { - throw new Error(`Down retry interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`); - } - if (this.downRetryInterval < MIN_INTERVAL_SECOND) { - throw new Error(`Down retry interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`); + const downRetryInterval = Number(this.downRetryInterval); + if (downRetryInterval !== 0) { + if (downRetryInterval > MAX_INTERVAL_SECOND) { + throw new Error(`Down retry interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`); + } + if (downRetryInterval < MIN_INTERVAL_SECOND) { + throw new Error(`Down retry interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`); + } } if (this.response_max_length !== undefined) { diff --git a/src/lang/en.json b/src/lang/en.json index f4f075786..97de6e1fe 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -85,6 +85,8 @@ "checkEverySecond": "Check every {0} seconds", "retryCheckEverySecond": "Retry every {0} seconds", "downCheckEverySecond": "Check every {0} seconds while down", + "downRetryIntervalDisabled": "Use heartbeat interval when down", + "downRetryIntervalDescription": "Set to 0 to use the heartbeat interval while the monitor is down.", "resendEveryXTimes": "Resend every {0} times", "resendDisabled": "Resend disabled", "retriesDescription": "Maximum retries before the service is marked as down and a notification is sent", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index fd0b43468..530616d2b 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1188,7 +1188,10 @@
-
+
+ {{ $t("downRetryIntervalDescription") }} +
+
{{ $t("minimumIntervalWarning") }}
@@ -2277,7 +2283,7 @@ const monitorDefaults = { interval: 60, humanReadableInterval: timeDurationFormatter.secondsToHumanReadableFormat(60), retryInterval: 60, - downRetryInterval: 60, + downRetryInterval: 0, resendInterval: 0, maxretries: 0, retryOnlyOnStatusCodeFailure: false, @@ -2927,8 +2933,8 @@ message HealthCheckResponse { if (this.monitor.retryInterval === 0) { this.monitor.retryInterval = this.monitor.interval; } - if (!this.monitor.downRetryInterval) { - this.monitor.downRetryInterval = this.monitor.interval; + if (this.monitor.downRetryInterval === undefined || this.monitor.downRetryInterval === null) { + this.monitor.downRetryInterval = 0; } // Handling for monitors that are missing/zeroed timeout if (!this.monitor.timeout) { @@ -3108,7 +3114,9 @@ message HealthCheckResponse { // do this if the interval value has changed since last save. if ( this.lowIntervalConfirmation.editedValue && - (this.monitor.interval < 20 || this.monitor.retryInterval < 20 || this.monitor.downRetryInterval < 20) && + (this.monitor.interval < 20 || + this.monitor.retryInterval < 20 || + (this.monitor.downRetryInterval > 0 && this.monitor.downRetryInterval < 20)) && !this.lowIntervalConfirmation.confirmed ) { // The dialog will then re-call submit From 7c3f7fcd9f4f8ed6da60653213b3e568c8313371 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 08:21:39 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- src/pages/EditMonitor.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 530616d2b..e2ac11ac8 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1206,7 +1206,10 @@
{{ $t("downRetryIntervalDescription") }}
-
+
{{ $t("minimumIntervalWarning") }}