(feature) Link rsync logs from backup run detail
Record the final rsync log path for successful real backup runs, matching the existing dry-run and failure result payloads. Add a staff-only run log endpoint and surface the link on run detail pages, including fallback log discovery for older runs based on snapshot_path. Cover direct log links and inferred scheduled backup logs with view tests.
This commit is contained in:
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.conf import settings
|
||||
from django.http import FileResponse, Http404
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
@@ -362,16 +363,28 @@ def queue_manual_backup(request, host: str):
|
||||
def run_detail(request, run_id: int):
|
||||
run = get_object_or_404(BackupRun.objects.select_related("host", "snapshot"), id=run_id)
|
||||
run_stats = run.result.get("stats") if isinstance(run.result, dict) else {}
|
||||
rsync_log_path = _run_rsync_log_path(run)
|
||||
context = {
|
||||
"run": run,
|
||||
"can_cancel": run.status in {BackupRun.Status.QUEUED, BackupRun.Status.RUNNING},
|
||||
"requested": run.result.get("requested") if isinstance(run.result, dict) else {},
|
||||
"stats": run_stats if isinstance(run_stats, dict) else {},
|
||||
"rsync_log_path": str(rsync_log_path) if rsync_log_path is not None else "",
|
||||
"rsync_log_exists": bool(rsync_log_path and rsync_log_path.exists()),
|
||||
"result_json": _pretty_json(run.result),
|
||||
}
|
||||
return render(request, "pobsync_backend/run_detail.html", context)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def run_rsync_log(request, run_id: int):
|
||||
run = get_object_or_404(BackupRun.objects.select_related("host", "snapshot"), id=run_id)
|
||||
log_path = _run_rsync_log_path(run)
|
||||
if log_path is None or not log_path.is_file():
|
||||
raise Http404("Rsync log not found")
|
||||
return FileResponse(log_path.open("rb"), content_type="text/plain; charset=utf-8")
|
||||
|
||||
|
||||
@staff_member_required
|
||||
@require_POST
|
||||
def cancel_run(request, run_id: int):
|
||||
@@ -634,6 +647,21 @@ def _pretty_json(value: object) -> str:
|
||||
return json.dumps(value or {}, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def _run_rsync_log_path(run: BackupRun) -> Path | None:
|
||||
if isinstance(run.result, dict):
|
||||
log = run.result.get("log")
|
||||
if isinstance(log, str) and log:
|
||||
return Path(log)
|
||||
execution = run.result.get("execution")
|
||||
if isinstance(execution, dict):
|
||||
execution_log = execution.get("log")
|
||||
if isinstance(execution_log, str) and execution_log:
|
||||
return Path(execution_log)
|
||||
if run.snapshot_path:
|
||||
return Path(run.snapshot_path) / "meta" / "rsync.log"
|
||||
return None
|
||||
|
||||
|
||||
def _log_context(request) -> dict[str, object]:
|
||||
units = ("pobsync-web.service", "pobsync-worker.service", "pobsync-scheduler.service")
|
||||
priorities = {
|
||||
|
||||
Reference in New Issue
Block a user