feat: partial success warning status (#74)

* feat: report partial backups with warnings

* chore: rebase

* chore: remove un-used size prop
This commit is contained in:
Nico
2025-11-26 19:02:29 +01:00
committed by GitHub
parent f8363a6c71
commit d190d9c8cd
15 changed files with 102 additions and 42 deletions

View File

@@ -25,7 +25,7 @@ const backupScheduleSchema = type({
excludePatterns: "string[] | null",
includePatterns: "string[] | null",
lastBackupAt: "number | null",
lastBackupStatus: "'success' | 'error' | 'in_progress' | null",
lastBackupStatus: "'success' | 'error' | 'in_progress' | 'warning' | null",
lastBackupError: "string | null",
nextBackupAt: "number | null",
createdAt: "number",

View File

@@ -236,7 +236,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
backupOptions.include = schedule.includePatterns;
}
await restic.backup(repository.config, volumePath, {
const { exitCode } = await restic.backup(repository.config, volumePath, {
...backupOptions,
compressionMode: repository.compressionMode ?? "auto",
onProgress: (progress) => {
@@ -258,24 +258,28 @@ const executeBackup = async (scheduleId: number, manual = false) => {
.update(backupSchedulesTable)
.set({
lastBackupAt: Date.now(),
lastBackupStatus: "success",
lastBackupStatus: exitCode === 0 ? "success" : "warning",
lastBackupError: null,
nextBackupAt: nextBackupAt,
updatedAt: Date.now(),
})
.where(eq(backupSchedulesTable.id, scheduleId));
logger.info(`Backup completed successfully for volume ${volume.name} to repository ${repository.name}`);
if (exitCode !== 0) {
logger.warn(`Backup completed with warnings for volume ${volume.name} to repository ${repository.name}`);
} else {
logger.info(`Backup completed successfully for volume ${volume.name} to repository ${repository.name}`);
}
serverEvents.emit("backup:completed", {
scheduleId,
volumeName: volume.name,
repositoryName: repository.name,
status: "success",
status: exitCode === 0 ? "success" : "warning",
});
notificationsService
.sendBackupNotification(scheduleId, "success", {
.sendBackupNotification(scheduleId, exitCode === 0 ? "success" : "warning", {
volumeName: volume.name,
repositoryName: repository.name,
})

View File

@@ -41,7 +41,7 @@ export const eventsController = new Hono().get("/", (c) => {
scheduleId: number;
volumeName: string;
repositoryName: string;
status: "success" | "error" | "stopped";
status: "success" | "error" | "stopped" | "warning";
}) => {
stream.writeSSE({
data: JSON.stringify(data),

View File

@@ -291,6 +291,7 @@ const sendBackupNotification = async (
case "success":
return assignment.notifyOnSuccess;
case "failure":
case "warning":
return assignment.notifyOnFailure;
default:
return false;
@@ -367,7 +368,7 @@ function buildNotificationMessage(
case "success":
return {
title: "✅ Backup Completed Successfully",
title: "✅ Backup Completed successfully",
body: [
`Volume: ${context.volumeName}`,
`Repository: ${context.repositoryName}`,
@@ -381,9 +382,26 @@ function buildNotificationMessage(
.join("\n"),
};
case "warning":
return {
title: "! Backup completed with warnings",
body: [
`Volume: ${context.volumeName}`,
`Repository: ${context.repositoryName}`,
context.duration ? `Duration: ${Math.round(context.duration / 1000)}s` : null,
context.filesProcessed !== undefined ? `Files: ${context.filesProcessed}` : null,
context.bytesProcessed ? `Size: ${context.bytesProcessed}` : null,
context.snapshotId ? `Snapshot: ${context.snapshotId}` : null,
context.error ? `Warning: ${context.error}` : null,
`Time: ${date} - ${time}`,
]
.filter(Boolean)
.join("\n"),
};
case "failure":
return {
title: "❌ Backup Failed",
title: "❌ Backup failed",
body: [
`Volume: ${context.volumeName}`,
`Repository: ${context.repositoryName}`,