(feature) add Django detail views for backup runs and snapshots

Add staff-only run and snapshot detail pages so scheduler and command
output can be inspected from the Django UI.

Link dashboard and host detail tables to the new detail views, including
snapshot/base relationships and linked backup runs.

Render stored result and metadata JSON in readable form and cover the new
inspection views with tests.
This commit is contained in:
2026-05-19 12:31:47 +02:00
parent 6bcc15c174
commit 66e1f549b9
8 changed files with 239 additions and 6 deletions

View File

@@ -165,6 +165,8 @@ class ViewTests(TestCase):
self.assertContains(response, "Discover snapshots")
self.assertContains(response, "Edit schedule")
self.assertContains(response, "Edit config")
self.assertContains(response, reverse("run_detail", args=[BackupRun.objects.get().id]))
self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id]))
def test_host_detail_returns_404_for_unknown_host(self) -> None:
self.client.force_login(self.staff_user)
@@ -173,6 +175,52 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 404)
def test_run_detail_renders_result_payload(self) -> None:
self.client.force_login(self.staff_user)
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
snapshot = self._snapshot(host, "20260519-021500Z__ABCDEFGH")
run = BackupRun.objects.create(
host=host,
status=BackupRun.Status.SUCCESS,
snapshot=snapshot,
snapshot_path=snapshot.path,
base_path="/backups/web-01/scheduled/base",
rsync_exit_code=0,
result={"ok": True, "snapshot": snapshot.path},
)
response = self.client.get(reverse("run_detail", args=[run.id]))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Run")
self.assertContains(response, "web-01")
self.assertContains(response, "success")
self.assertContains(response, "ABCDEFGH")
self.assertContains(response, '"ok": true')
self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id]))
def test_snapshot_detail_renders_metadata_runs_and_children(self) -> None:
self.client.force_login(self.staff_user)
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
base = self._snapshot(host, "20260518-021500Z__BASESNAP")
base.metadata = {"status": "success", "snapshot_id": "BASESNAP"}
base.save(update_fields=["metadata"])
child = self._snapshot(host, "20260519-021500Z__CHILDSNP")
child.base = base
child.base_dirname = base.dirname
child.save(update_fields=["base", "base_dirname"])
run = BackupRun.objects.create(host=host, status=BackupRun.Status.SUCCESS, snapshot=base)
response = self.client.get(reverse("snapshot_detail", args=[base.id]))
self.assertEqual(response.status_code, 200)
self.assertContains(response, base.dirname)
self.assertContains(response, "BASESNAP")
self.assertContains(response, child.dirname)
self.assertContains(response, f"Run {run.id}")
self.assertContains(response, reverse("run_detail", args=[run.id]))
self.assertContains(response, reverse("snapshot_detail", args=[child.id]))
def test_discover_host_snapshots_action_discovers_and_redirects(self) -> None:
self.client.force_login(self.staff_user)
with TemporaryDirectory() as tmp: