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:
+
+ {% for action in prune_result.actions %}
+ - {{ action }}
+ {% endfor %}
+
+ {% 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)