From 97753c3d3c387ef4f0eeaee42e2adb06bb9a0120 Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 01:25:40 +0200 Subject: [PATCH] (ui) Show retention apply details on run detail Record planned delete counts, max-delete settings, base protection, and ignored incomplete snapshots in retention apply results. Surface those details on run detail pages so scheduled and manual prune outcomes are understandable without reading the raw JSON payload. --- src/pobsync_backend/retention.py | 5 +++++ .../templates/pobsync_backend/run_detail.html | 13 +++++++++++++ .../tests/test_sql_retention.py | 3 +++ src/pobsync_backend/tests/test_views.py | 18 ++++++++++++++---- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/pobsync_backend/retention.py b/src/pobsync_backend/retention.py index e986d8d..0d17a45 100644 --- a/src/pobsync_backend/retention.py +++ b/src/pobsync_backend/retention.py @@ -78,8 +78,11 @@ def run_sql_retention_apply( def _do_apply() -> dict[str, Any]: plan = run_sql_retention_plan(host=host, kind=kind, protect_bases=bool(protect_bases)) delete_list = plan.get("delete") or [] + incomplete_list = plan.get("incomplete") or [] if not isinstance(delete_list, list): raise ConfigError("Invalid retention plan output: delete is not a list") + if not isinstance(incomplete_list, list): + raise ConfigError("Invalid retention plan output: incomplete is not a list") if max_delete == 0 and len(delete_list) > 0: raise ConfigError("Deletion blocked by --max-delete=0") if len(delete_list) > max_delete: @@ -116,6 +119,8 @@ def run_sql_retention_apply( "protect_bases": bool(protect_bases), "max_delete": max_delete, "source": "sql", + "planned_delete_count": len(delete_list), + "incomplete_ignored_count": len(incomplete_list), "deleted": deleted, "actions": actions, } diff --git a/src/pobsync_backend/templates/pobsync_backend/run_detail.html b/src/pobsync_backend/templates/pobsync_backend/run_detail.html index da7ffff..6d82121 100644 --- a/src/pobsync_backend/templates/pobsync_backend/run_detail.html +++ b/src/pobsync_backend/templates/pobsync_backend/run_detail.html @@ -172,7 +172,20 @@
Status: {% if prune_result.ok %}ok{% else %}warning{% endif %}
{% if prune_result.source %}
Source: {{ prune_result.source }}
{% endif %} + {% if prune_result.kind %}
Kind: {{ prune_result.kind }}
{% endif %} + {% if prune_result.planned_delete_count is not None %}
Planned deletions: {{ prune_result.planned_delete_count }}
{% endif %} {% if prune_result.deleted %}
Deleted: {{ prune_result.deleted|length }}
{% endif %} + {% if prune_result.max_delete is not None %}
Max delete: {{ prune_result.max_delete }}
{% endif %} + {% if prune_result.protect_bases is not None %}
Protect bases: {{ prune_result.protect_bases|yesno:"yes,no" }}
{% endif %} + {% if prune_result.incomplete_ignored_count %}
Incomplete ignored: {{ prune_result.incomplete_ignored_count }}
{% endif %} + {% if prune_result.actions %} +
Actions:
+ + {% endif %} {% if prune_result.error %}
Error: {{ prune_result.error }}
{% endif %} {% if prune_result.type %}
Type: {{ prune_result.type }}
{% endif %}
diff --git a/src/pobsync_backend/tests/test_sql_retention.py b/src/pobsync_backend/tests/test_sql_retention.py index 33e1c86..bdb903f 100644 --- a/src/pobsync_backend/tests/test_sql_retention.py +++ b/src/pobsync_backend/tests/test_sql_retention.py @@ -88,6 +88,9 @@ class SqlRetentionTests(TestCase): self.assertTrue(SnapshotRecord.objects.filter(pk=new.pk).exists()) self.assertFalse(SnapshotRecord.objects.filter(pk=old.pk).exists()) self.assertEqual(result["deleted"], [{"dirname": old.dirname, "kind": "scheduled", "path": str(old_dir)}]) + self.assertEqual(result["planned_delete_count"], 1) + self.assertEqual(result["max_delete"], 1) + self.assertEqual(result["incomplete_ignored_count"], 0) def test_apply_deletes_snapshot_with_readonly_data_directory(self) -> None: with TemporaryDirectory() as tmp: diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index c6e9cc5..e5f80b4 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -1197,9 +1197,15 @@ class ViewTests(TestCase): "hint": "Check network connectivity.", }, "prune": { - "ok": False, - "type": "ConfigError", - "error": "Deletion blocked by --max-delete=0", + "ok": True, + "source": "sql", + "kind": "scheduled", + "planned_delete_count": 1, + "max_delete": 1, + "protect_bases": True, + "incomplete_ignored_count": 1, + "deleted": [{"dirname": "20260518-021500Z__OLD"}], + "actions": ["deleted scheduled 20260518-021500Z__OLD"], }, }, ) @@ -1211,7 +1217,11 @@ class ViewTests(TestCase): self.assertContains(response, "transport") self.assertContains(response, "Check network connectivity.") self.assertContains(response, "Retention") - self.assertContains(response, "Deletion blocked by --max-delete=0") + self.assertContains(response, "Planned deletions") + self.assertContains(response, "Max delete") + self.assertContains(response, "Protect bases") + self.assertContains(response, "Incomplete ignored") + self.assertContains(response, "deleted scheduled 20260518-021500Z__OLD") def test_run_detail_surfaces_host_retention_warnings(self) -> None: self.client.force_login(self.staff_user)