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:
co-authored by
Claude Opus 4.8
parent
7bd449101d
commit
84ef3df59e
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
def _now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
FREQUENCIES = ["hourly", "daily", "weekly"]
|
||||
|
||||
|
||||
class BackupSchedule(SQLModel, table=True):
|
||||
"""An automatic, recurring backup of a local stack to a destination."""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
stack_id: str
|
||||
destination_id: int
|
||||
frequency: str = "daily" # one of FREQUENCIES
|
||||
hour: int = 3 # UTC, used for daily/weekly
|
||||
minute: int = 0
|
||||
weekday: int = 0 # 0=Mon .. 6=Sun, used for weekly
|
||||
include_volumes: bool = True
|
||||
stop_first: bool = True
|
||||
keep: int = 7 # retention: keep newest N for this stack on the dest (0 = all)
|
||||
enabled: bool = True
|
||||
last_run: Optional[datetime] = None
|
||||
last_status: Optional[str] = None # "ok" | "error: ..."
|
||||
next_run: Optional[datetime] = None
|
||||
created_at: datetime = Field(default_factory=_now)
|
||||
|
||||
|
||||
# --- API schemas ---
|
||||
|
||||
|
||||
class ScheduleCreate(SQLModel):
|
||||
stack_id: str
|
||||
destination_id: int
|
||||
frequency: str = "daily"
|
||||
hour: int = 3
|
||||
minute: int = 0
|
||||
weekday: int = 0
|
||||
include_volumes: bool = True
|
||||
stop_first: bool = True
|
||||
keep: int = 7
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ScheduleUpdate(SQLModel):
|
||||
destination_id: Optional[int] = None
|
||||
frequency: Optional[str] = None
|
||||
hour: Optional[int] = None
|
||||
minute: Optional[int] = None
|
||||
weekday: Optional[int] = None
|
||||
include_volumes: Optional[bool] = None
|
||||
stop_first: Optional[bool] = None
|
||||
keep: Optional[int] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class ScheduleRead(SQLModel):
|
||||
id: int
|
||||
stack_id: str
|
||||
destination_id: int
|
||||
destination_name: Optional[str]
|
||||
frequency: str
|
||||
hour: int
|
||||
minute: int
|
||||
weekday: int
|
||||
include_volumes: bool
|
||||
stop_first: bool
|
||||
keep: int
|
||||
enabled: bool
|
||||
last_run: Optional[datetime]
|
||||
last_status: Optional[str]
|
||||
next_run: Optional[datetime]
|
||||
created_at: datetime
|
||||
Reference in New Issue
Block a user