From 2d9e6c39114200f6382212c7c8cc1621de26be13 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:32:12 +0100 Subject: [PATCH 1/2] fix: MariaDB datetime format error when pausing maintenance (#6513) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: louislam <1336778+louislam@users.noreply.github.com> Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> --- server/model/maintenance.js | 4 +- test/backend-test/test-maintenance.js | 65 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 test/backend-test/test-maintenance.js diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 9c429752d..c180feec9 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -1,5 +1,5 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); -const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util"); +const { parseTimeObject, parseTimeFromTimeObject, log, SQL_DATETIME_FORMAT } = require("../../src/util"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); const Cron = require("croner"); @@ -262,7 +262,7 @@ class Maintenance extends BeanModel { }, duration); // Set last start date to current time - this.last_start_date = current.toISOString(); + this.last_start_date = current.utc().format(SQL_DATETIME_FORMAT); await R.store(this); }; diff --git a/test/backend-test/test-maintenance.js b/test/backend-test/test-maintenance.js new file mode 100644 index 000000000..a23bd77bb --- /dev/null +++ b/test/backend-test/test-maintenance.js @@ -0,0 +1,65 @@ +const test = require("node:test"); +const assert = require("node:assert"); +const dayjs = require("dayjs"); +const { SQL_DATETIME_FORMAT } = require("../../src/util"); + +dayjs.extend(require("dayjs/plugin/utc")); +dayjs.extend(require("dayjs/plugin/customParseFormat")); + +/** + * Tests for maintenance date formatting to ensure compatibility with MariaDB/MySQL. + * Issue: MariaDB rejects ISO format dates like '2025-12-19T01:04:02.129Z' + * Fix: Use SQL_DATETIME_FORMAT ('YYYY-MM-DD HH:mm:ss') instead of toISOString() + */ +test("Maintenance Date Format - MariaDB Compatibility", async (t) => { + + await t.test("SQL_DATETIME_FORMAT constant should match MariaDB format", async () => { + assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss"); + }); + + await t.test("Format date using SQL_DATETIME_FORMAT", async () => { + const current = dayjs.utc("2025-12-19T01:04:02.129Z"); + const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT); + + assert.strictEqual(sqlFormat, "2025-12-19 01:04:02"); + }); + + await t.test("SQL format should not contain ISO markers (T, Z)", async () => { + const current = dayjs.utc("2025-12-19T01:04:02.129Z"); + const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT); + + assert.strictEqual(sqlFormat.includes("T"), false, "SQL format should not contain 'T'"); + assert.strictEqual(sqlFormat.includes("Z"), false, "SQL format should not contain 'Z'"); + }); + + await t.test("SQL format should match YYYY-MM-DD HH:mm:ss pattern", async () => { + const current = dayjs.utc("2025-12-19T01:04:02.129Z"); + const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT); + const sqlDateTimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/; + + assert.strictEqual(sqlDateTimeRegex.test(sqlFormat), true); + }); + + await t.test("Parse SQL datetime back to dayjs preserves timestamp", async () => { + const originalDate = dayjs.utc("2025-12-19T01:04:02.000Z"); + const sqlFormat = originalDate.utc().format(SQL_DATETIME_FORMAT); + const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT); + + assert.strictEqual(parsedDate.unix(), originalDate.unix()); + }); + + await t.test("Edge case: midnight timestamp", async () => { + const midnight = dayjs.utc("2025-01-01T00:00:00.000Z"); + const sqlFormat = midnight.utc().format(SQL_DATETIME_FORMAT); + + assert.strictEqual(sqlFormat, "2025-01-01 00:00:00"); + }); + + await t.test("Edge case: end of day timestamp", async () => { + const endOfDay = dayjs.utc("2025-12-31T23:59:59.999Z"); + const sqlFormat = endOfDay.utc().format(SQL_DATETIME_FORMAT); + + assert.strictEqual(sqlFormat, "2025-12-31 23:59:59"); + }); + +}); From d23ff8c4860b09cdd8c57ba9ab6ced5321dd3782 Mon Sep 17 00:00:00 2001 From: Shengqi Chen Date: Mon, 22 Dec 2025 22:31:39 +0800 Subject: [PATCH 2/2] fix: v2 migration process report is always 0 when having many monitors / dates (#6516) Signed-off-by: Shengqi Chen Co-authored-by: Frank Elsinga --- server/database.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/database.js b/server/database.js index a65a7fdcc..51785a04b 100644 --- a/server/database.js +++ b/server/database.js @@ -826,9 +826,7 @@ class Database { await Settings.set("migrateAggregateTableState", "migrating"); let progressPercent = 0; - let part = 100 / monitors.length; - let i = 1; - for (let monitor of monitors) { + for (const [ i, monitor ] of monitors.entries()) { // Get a list of unique dates from the heartbeat table, using raw sql let dates = await R.getAll(` SELECT DISTINCT DATE(time) AS date @@ -839,7 +837,7 @@ class Database { monitor.monitor_id ]); - for (let date of dates) { + for (const [ dateIndex, date ] of dates.entries()) { // New Uptime Calculator let calculator = new UptimeCalculator(); calculator.monitorID = monitor.monitor_id; @@ -855,7 +853,7 @@ class Database { `, [ monitor.monitor_id, date.date ]); if (heartbeats.length > 0) { - msg = `[DON'T STOP] Migrating monitor data ${monitor.monitor_id} - ${date.date} [${progressPercent.toFixed(2)}%][${i}/${monitors.length}]`; + msg = `[DON'T STOP] Migrating monitor ${monitor.monitor_id}s' (${i + 1} of ${monitors.length} total) data - ${date.date} - total migration progress ${progressPercent.toFixed(2)}%`; log.info("db", msg); migrationServer?.update(msg); } @@ -864,15 +862,14 @@ class Database { await calculator.update(heartbeat.status, parseFloat(heartbeat.ping), dayjs(heartbeat.time)); } - progressPercent += (Math.round(part / dates.length * 100) / 100); + // Calculate progress: (current_monitor_index + relative_date_progress) / total_monitors + progressPercent = (i + (dateIndex + 1) / dates.length) / monitors.length * 100; // Lazy to fix the floating point issue, it is acceptable since it is just a progress bar if (progressPercent > 100) { progressPercent = 100; } } - - i++; } msg = "Clearing non-important heartbeats";