(ui) Add dashboard-linked run and snapshot lists
Add staff-only list pages for backup runs and snapshots with practical filters, then wire the dashboard summary cards and latest-runs panel to those overviews. This gives the dashboard real drill-down paths for run and snapshot counts instead of leaving the data only partially visible on the first screen. Refs #23
This commit is contained in:
@@ -125,6 +125,12 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "1 run completed with warnings.")
|
||||
self.assertContains(response, "1 backup run in progress.")
|
||||
self.assertContains(response, "1 backup run waiting for the worker.")
|
||||
self.assertContains(response, f'href="{reverse("runs_list")}"', html=False)
|
||||
self.assertContains(response, f'href="{reverse("runs_list")}?status=queued"', html=False)
|
||||
self.assertContains(response, f'href="{reverse("runs_list")}?status=running"', html=False)
|
||||
self.assertContains(response, f'href="{reverse("runs_list")}?status=warning&review=needed"', html=False)
|
||||
self.assertContains(response, f'href="{reverse("runs_list")}?status=failed&review=needed"', html=False)
|
||||
self.assertContains(response, f'href="{reverse("snapshots_list")}"', html=False)
|
||||
|
||||
def test_dashboard_renders_backup_trend_summary(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
@@ -200,6 +206,45 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Operational Status")
|
||||
self.assertContains(response, "No queued, running, or unreviewed warning/failed runs.")
|
||||
|
||||
def test_runs_list_filters_by_status_and_review(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
web = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
db = HostConfig.objects.create(host="db-01", address="db-01.example.test")
|
||||
failed = BackupRun.objects.create(host=web, status=BackupRun.Status.FAILED, run_type=BackupRun.RunType.MANUAL)
|
||||
success = BackupRun.objects.create(host=db, status=BackupRun.Status.SUCCESS, run_type=BackupRun.RunType.SCHEDULED)
|
||||
BackupRun.objects.create(
|
||||
host=web,
|
||||
status=BackupRun.Status.WARNING,
|
||||
reviewed_at=datetime(2026, 5, 19, 4, 15, tzinfo=timezone.utc),
|
||||
reviewed_by="admin",
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("runs_list"), {"status": "failed", "review": "needed"})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Runs")
|
||||
self.assertContains(response, "Review queued, running, completed")
|
||||
self.assertContains(response, f"Run {failed.id}")
|
||||
self.assertContains(response, "web-01")
|
||||
self.assertContains(response, "needed")
|
||||
self.assertNotContains(response, f"Run {success.id}")
|
||||
|
||||
def test_snapshots_list_filters_by_host_and_kind(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
web = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
db = HostConfig.objects.create(host="db-01", address="db-01.example.test")
|
||||
manual = self._snapshot(web, "20260519-021500Z__MANUAL01", kind=SnapshotRecord.Kind.MANUAL)
|
||||
scheduled = self._snapshot(db, "20260519-021500Z__SCHED01", kind=SnapshotRecord.Kind.SCHEDULED)
|
||||
|
||||
response = self.client.get(reverse("snapshots_list"), {"host": web.host, "kind": SnapshotRecord.Kind.MANUAL})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Snapshots")
|
||||
self.assertContains(response, "Browse discovered scheduled, manual, and incomplete snapshots")
|
||||
self.assertContains(response, manual.dirname)
|
||||
self.assertContains(response, "web-01")
|
||||
self.assertNotContains(response, scheduled.dirname)
|
||||
|
||||
def test_dashboard_surfaces_retention_warnings(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(
|
||||
@@ -2223,13 +2268,19 @@ class ViewTests(TestCase):
|
||||
self.assertEqual(host.excludes_add, [])
|
||||
self.assertEqual(host.excludes_replace, ["*.cache", "node_modules/"])
|
||||
|
||||
def _snapshot(self, host: HostConfig, dirname: str) -> SnapshotRecord:
|
||||
def _snapshot(
|
||||
self,
|
||||
host: HostConfig,
|
||||
dirname: str,
|
||||
*,
|
||||
kind: str = SnapshotRecord.Kind.SCHEDULED,
|
||||
) -> SnapshotRecord:
|
||||
started_at = datetime.strptime(dirname.split("__", 1)[0], "%Y%m%d-%H%M%SZ").replace(tzinfo=timezone.utc)
|
||||
return SnapshotRecord.objects.create(
|
||||
host=host,
|
||||
kind=SnapshotRecord.Kind.SCHEDULED,
|
||||
kind=kind,
|
||||
dirname=dirname,
|
||||
path=f"/backups/{host.host}/scheduled/{dirname}",
|
||||
path=f"/backups/{host.host}/{kind}/{dirname}",
|
||||
status="success",
|
||||
started_at=started_at,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user