feat(backup): add database backup/restore with scheduled retention and admin dashboard
- Add BackupRecord model for tracking backup archives - Add migration 021_add_backup_records - Add backup configuration settings (backup_enabled, backup_dir, backup_remote_destination, backup_remote_folder, backup_retain_hourly/daily/weekly) - Add backup_tasks.py with create_backup, cleanup_old_backups, and helpers - Register hourly/daily/weekly Celery beat schedules - Add /api/admin/backup/* REST endpoints (list, create, download, restore, delete, cleanup) - Add /admin/backup dashboard view and template - Add backup link to admin dropdown navigation in base.html - Add backup settings to SETTING_METADATA in settings_service.py - Add comprehensive test suite (39 tests passing) Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Backup management dashboard view – admin only.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import settings
|
||||
from app.models import BackupRecord
|
||||
from app.views.base import APIRouter, get_db, require_login, templates
|
||||
from app.views.settings import require_admin_access
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/admin/backup")
|
||||
@require_login
|
||||
@require_admin_access
|
||||
async def backup_dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
"""Backup management dashboard – admin only."""
|
||||
try:
|
||||
records = db.query(BackupRecord).order_by(BackupRecord.created_at.desc()).limit(500).all()
|
||||
|
||||
# Summarise counts per tier
|
||||
counts: dict[str, int] = {"hourly": 0, "daily": 0, "weekly": 0}
|
||||
for r in records:
|
||||
if r.backup_type in counts:
|
||||
counts[r.backup_type] += 1
|
||||
|
||||
# Compute total local size
|
||||
import os
|
||||
|
||||
total_size = sum(r.size_bytes for r in records if r.local_path and os.path.exists(r.local_path))
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"backup.html",
|
||||
{
|
||||
"request": request,
|
||||
"records": records,
|
||||
"counts": counts,
|
||||
"total_size": total_size,
|
||||
"backup_enabled": getattr(settings, "backup_enabled", True),
|
||||
"backup_remote_destination": getattr(settings, "backup_remote_destination", None),
|
||||
"backup_retain_hourly": getattr(settings, "backup_retain_hourly", 96),
|
||||
"backup_retain_daily": getattr(settings, "backup_retain_daily", 21),
|
||||
"backup_retain_weekly": getattr(settings, "backup_retain_weekly", 13),
|
||||
"app_version": settings.version,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading backup dashboard: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to load backup dashboard",
|
||||
)
|
||||
Reference in New Issue
Block a user