2026-05-19 11:53:32 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-05-19 11:56:45 +02:00
|
|
|
from django.contrib import messages
|
2026-05-19 11:53:32 +02:00
|
|
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
|
|
|
from django.db.models import Count
|
2026-05-19 11:56:45 +02:00
|
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
|
|
|
from django.views.decorators.http import require_POST
|
2026-05-19 11:53:32 +02:00
|
|
|
|
|
|
|
|
from .models import BackupRun, HostConfig, ScheduleConfig, SnapshotRecord
|
2026-05-19 11:56:45 +02:00
|
|
|
from .snapshot_discovery import discover_snapshots
|
2026-05-19 11:53:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@staff_member_required
|
|
|
|
|
def dashboard(request):
|
|
|
|
|
host_qs = (
|
|
|
|
|
HostConfig.objects.annotate(snapshot_count=Count("snapshots", distinct=True), run_count=Count("runs", distinct=True))
|
|
|
|
|
.order_by("host")
|
|
|
|
|
)
|
|
|
|
|
context = {
|
|
|
|
|
"hosts": host_qs,
|
|
|
|
|
"latest_runs": BackupRun.objects.select_related("host", "snapshot").order_by("-created_at")[:10],
|
|
|
|
|
"counts": {
|
|
|
|
|
"hosts": HostConfig.objects.count(),
|
|
|
|
|
"enabled_hosts": HostConfig.objects.filter(enabled=True).count(),
|
|
|
|
|
"schedules": ScheduleConfig.objects.count(),
|
|
|
|
|
"enabled_schedules": ScheduleConfig.objects.filter(enabled=True).count(),
|
|
|
|
|
"snapshots": SnapshotRecord.objects.count(),
|
|
|
|
|
"runs": BackupRun.objects.count(),
|
|
|
|
|
"running_runs": BackupRun.objects.filter(status=BackupRun.Status.RUNNING).count(),
|
|
|
|
|
"failed_runs": BackupRun.objects.filter(status=BackupRun.Status.FAILED).count(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return render(request, "pobsync_backend/dashboard.html", context)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staff_member_required
|
|
|
|
|
def host_detail(request, host: str):
|
|
|
|
|
host_config = get_object_or_404(HostConfig, host=host)
|
|
|
|
|
context = {
|
|
|
|
|
"host": host_config,
|
|
|
|
|
"schedule": _schedule_for_host(host_config),
|
|
|
|
|
"latest_runs": host_config.runs.select_related("snapshot").order_by("-created_at")[:10],
|
|
|
|
|
"snapshots": host_config.snapshots.select_related("base").order_by("-started_at", "dirname")[:20],
|
|
|
|
|
"counts": {
|
|
|
|
|
"snapshots": host_config.snapshots.count(),
|
|
|
|
|
"runs": host_config.runs.count(),
|
|
|
|
|
"failed_runs": host_config.runs.filter(status=BackupRun.Status.FAILED).count(),
|
|
|
|
|
"incomplete_snapshots": host_config.snapshots.filter(kind=SnapshotRecord.Kind.INCOMPLETE).count(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return render(request, "pobsync_backend/host_detail.html", context)
|
|
|
|
|
|
|
|
|
|
|
2026-05-19 11:56:45 +02:00
|
|
|
@staff_member_required
|
|
|
|
|
@require_POST
|
|
|
|
|
def discover_host_snapshots(request, host: str):
|
|
|
|
|
host_config = get_object_or_404(HostConfig, host=host)
|
|
|
|
|
try:
|
|
|
|
|
result = discover_snapshots(host=host_config)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
messages.error(request, f"Snapshot discovery failed for {host_config.host}: {exc}")
|
|
|
|
|
else:
|
|
|
|
|
messages.success(
|
|
|
|
|
request,
|
|
|
|
|
(
|
|
|
|
|
f"Snapshot discovery scanned {result['scanned']} items for {host_config.host}: "
|
|
|
|
|
f"{result['created']} created, {result['updated']} updated."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
return redirect("host_detail", host=host_config.host)
|
|
|
|
|
|
|
|
|
|
|
2026-05-19 11:53:32 +02:00
|
|
|
def _schedule_for_host(host_config: HostConfig) -> ScheduleConfig | None:
|
|
|
|
|
try:
|
|
|
|
|
return host_config.schedule
|
|
|
|
|
except ScheduleConfig.DoesNotExist:
|
|
|
|
|
return None
|