Phase 7: scheduled (recurring) backups (0.7.0)

- BackupSchedule model + schedule_service: next-run calc (hourly/daily/weekly,
  UTC), background scheduler loop (lifespan), run-one with retention pruning
  (keep newest N per stack on the destination), backup_failed notify event.
- routers/schedules.py: schedules CRUD + run-now; registered in main.py.
- Frontend: api/schedules.ts + Settings → Scheduled backups (list with next/last
  run + status, enable/disable, run-now, delete; add form with stack/destination/
  frequency/time/weekday/retention/volumes).

Rough-verified only (per request): py_compile, frontend tsc build, app import
(95 routes), next-run math sanity. Full live run to be tested after deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-07 22:02:52 +00:00
co-authored by Claude Opus 4.8
parent 7bd449101d
commit 84ef3df59e
10 changed files with 697 additions and 5 deletions
+49
View File
@@ -0,0 +1,49 @@
import api from "./client";
export interface BackupSchedule {
id: number;
stack_id: string;
destination_id: number;
destination_name: string | null;
frequency: "hourly" | "daily" | "weekly";
hour: number;
minute: number;
weekday: number;
include_volumes: boolean;
stop_first: boolean;
keep: number;
enabled: boolean;
last_run: string | null;
last_status: string | null;
next_run: string | null;
created_at: string;
}
export interface ScheduleInput {
stack_id: string;
destination_id: number;
frequency: string;
hour: number;
minute: number;
weekday: number;
include_volumes: boolean;
stop_first: boolean;
keep: number;
enabled: boolean;
}
export const schedulesApi = {
list: () => api.get<BackupSchedule[]>("/api/backups/schedules").then((r) => r.data),
create: (body: ScheduleInput) =>
api.post<BackupSchedule>("/api/backups/schedules", body).then((r) => r.data),
update: (id: number, body: Partial<ScheduleInput>) =>
api.put<BackupSchedule>(`/api/backups/schedules/${id}`, body).then((r) => r.data),
remove: (id: number) =>
api.delete(`/api/backups/schedules/${id}`).then((r) => r.data),
run: (id: number) =>
api
.post<{ ok: boolean; name?: string; destination?: string; error?: string }>(
`/api/backups/schedules/${id}/run`
)
.then((r) => r.data),
};