mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
feat(admin): show the newest database dump on the backups page
GET /api/admin/backups now carries `database`: the newest good dump (when, how big, kept on the server, off-host or both), the newest failed run, whether this server can write its backup directory, and whether off-host copies are configured. The backups card leads with one row built from it, with a notice when the last run failed and when dumps can only go off-host or cannot be taken at all. The route stays an unpublished admin console route, so the OpenAPI document does not change.
This commit is contained in:
1 parent
e665f17f52
commit
3646502a86
14 files changed
+282
-10
No files matched your search
@@ -57,6 +57,24 @@ vi.mock("@/lib/jobs/offhost-backup", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const databaseHealth = {
|
||||
enabled: true,
|
||||
directory: "/var/lib/healthlog/backups",
|
||||
localDirWritable: true,
|
||||
offhostConfigured: true,
|
||||
lastGood: {
|
||||
at: "2026-09-21T23:31:40.000Z",
|
||||
fileName: "healthlog-20260921T233000Z-daily.dump",
|
||||
sizeBytes: 52_428_800,
|
||||
local: true,
|
||||
offhost: true,
|
||||
},
|
||||
lastFailure: null,
|
||||
};
|
||||
vi.mock("@/lib/database-backup/health", () => ({
|
||||
getDatabaseBackupHealth: async () => databaseHealth,
|
||||
}));
|
||||
|
||||
import { GET } from "../route";
|
||||
import type { BackupsList } from "@/types/backups";
|
||||
|
||||
@@ -285,3 +303,10 @@ describe("GET /api/admin/backups — a run in progress and one a process died un
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/admin/backups — the database dump", () => {
|
||||
it("carries the dump's health beside the account copies", async () => {
|
||||
const body = await read();
|
||||
expect(body.database).toEqual(databaseHealth);
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,10 @@
|
||||
* stopped six weeks ago — every row it lists has a perfectly ordinary
|
||||
* timestamp either way.
|
||||
*
|
||||
* The database row comes first in what an operator needs and last in this
|
||||
* file: the nightly whole-database dump (`getDatabaseBackupHealth`), the
|
||||
* one copy that rebuilds a lost server, read from its own ledger.
|
||||
*
|
||||
* And alongside both, the off-host leg: one row per account saying when this
|
||||
* host last put that account's encrypted copy in the operator's bucket and how
|
||||
* stale that is against the nightly schedule. It comes from the ledger the
|
||||
@@ -31,6 +35,7 @@ import {
|
||||
} from "@/lib/jobs/job-failures";
|
||||
import { readBackupPassActivity } from "@/lib/jobs/backup-pass-attempts";
|
||||
import { summariseBackupSchedule } from "@/lib/jobs/backup-schedule-status";
|
||||
import { getDatabaseBackupHealth } from "@/lib/database-backup/health";
|
||||
import {
|
||||
offhostBackupConfigured,
|
||||
probeOffhostLifecycle,
|
||||
@@ -190,7 +195,7 @@ export const GET = apiHandler(async () => {
|
||||
readBackupPassActivity(prisma, DATA_BACKUP_QUEUE, weeklyRunningSince),
|
||||
readBackupPassActivity(prisma, OFFHOST_BACKUP_QUEUE, offhostRunningSince),
|
||||
]);
|
||||
const [purgeCount, oldestPurge, lifecycle] = await Promise.all([
|
||||
const [purgeCount, oldestPurge, lifecycle, database] = await Promise.all([
|
||||
prisma.offhostPurgeRequest.count({ where: stillInBucket }),
|
||||
prisma.offhostPurgeRequest.findFirst({
|
||||
where: stillInBucket,
|
||||
@@ -200,6 +205,7 @@ export const GET = apiHandler(async () => {
|
||||
configured
|
||||
? probeOffhostLifecycle()
|
||||
: Promise.resolve({ state: "unknown" as const, expirationDays: null }),
|
||||
getDatabaseBackupHealth(now),
|
||||
]);
|
||||
|
||||
const payload: BackupsList = {
|
||||
@@ -229,6 +235,7 @@ export const GET = apiHandler(async () => {
|
||||
},
|
||||
lifecycle,
|
||||
},
|
||||
database,
|
||||
};
|
||||
|
||||
return apiSuccess(payload);
|
||||
|
||||
Reference in new issue
Block a user