(ui) Surface run debugging details in Django
Restructure the run detail page into clearer sections for summary, failure classification, requested options, rsync command, rsync log output, stats, retention, and raw result data. Show recent rsync log output inline with a link to the full log, and promote failure and retention warning details out of the JSON payload so failed or slow runs are easier to debug from the control panel.
This commit is contained in:
@@ -857,6 +857,11 @@ class ViewTests(TestCase):
|
||||
result={
|
||||
"ok": True,
|
||||
"snapshot": snapshot.path,
|
||||
"rsync": {
|
||||
"command": ["rsync", "--archive", "root@web-01:/", snapshot.path],
|
||||
"exit_code": 0,
|
||||
"log_tail": ["sending incremental file list", "sent 500 bytes"],
|
||||
},
|
||||
"requested": {
|
||||
"dry_run": True,
|
||||
"verbose_output": True,
|
||||
@@ -889,6 +894,10 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Requested Options")
|
||||
self.assertContains(response, "Dry run:</strong> yes")
|
||||
self.assertContains(response, "Verbose rsync output:</strong> yes")
|
||||
self.assertContains(response, "Rsync Command")
|
||||
self.assertContains(response, "--archive")
|
||||
self.assertContains(response, "Rsync Log")
|
||||
self.assertContains(response, "sending incremental file list")
|
||||
self.assertContains(response, "Stats")
|
||||
self.assertContains(response, "Files seen:</strong> 10")
|
||||
self.assertContains(response, "Estimated link-dest saving")
|
||||
@@ -901,7 +910,7 @@ class ViewTests(TestCase):
|
||||
with TemporaryDirectory() as tmp:
|
||||
log_path = Path(tmp) / "snapshot" / "meta" / "rsync.log"
|
||||
log_path.parent.mkdir(parents=True)
|
||||
log_path.write_text("rsync log line\n", encoding="utf-8")
|
||||
log_path.write_text("old line\nrsync log line\n", encoding="utf-8")
|
||||
run = BackupRun.objects.create(
|
||||
host=host,
|
||||
status=BackupRun.Status.SUCCESS,
|
||||
@@ -915,8 +924,40 @@ class ViewTests(TestCase):
|
||||
|
||||
self.assertContains(response, reverse("run_rsync_log", args=[run.id]))
|
||||
self.assertContains(response, str(log_path))
|
||||
self.assertContains(response, "rsync log line")
|
||||
self.assertEqual(log_response.status_code, 200)
|
||||
self.assertEqual(log_body, b"rsync log line\n")
|
||||
self.assertEqual(log_body, b"old line\nrsync log line\n")
|
||||
|
||||
def test_run_detail_surfaces_failure_and_retention_warning(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
run = BackupRun.objects.create(
|
||||
host=host,
|
||||
status=BackupRun.Status.WARNING,
|
||||
rsync_exit_code=0,
|
||||
result={
|
||||
"ok": True,
|
||||
"failure": {
|
||||
"category": "transport",
|
||||
"summary": "SSH connection dropped.",
|
||||
"hint": "Check network connectivity.",
|
||||
},
|
||||
"prune": {
|
||||
"ok": False,
|
||||
"type": "ConfigError",
|
||||
"error": "Deletion blocked by --max-delete=0",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("run_detail", args=[run.id]))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Failure")
|
||||
self.assertContains(response, "transport")
|
||||
self.assertContains(response, "Check network connectivity.")
|
||||
self.assertContains(response, "Retention")
|
||||
self.assertContains(response, "Deletion blocked by --max-delete=0")
|
||||
|
||||
def test_run_detail_infers_rsync_log_from_snapshot_path(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
Reference in New Issue
Block a user