(ui) Make retention planning warnings explicit
Show keep/delete reasons in the retention plan, surface scheduled prune limit warnings, and explain base snapshot protection before retention is applied. Also surface incomplete snapshots from the retention views without deleting them automatically, so interrupted backups are visible on the dashboard, host detail, and retention plan.
This commit is contained in:
@@ -55,6 +55,7 @@ def dashboard(request):
|
||||
.first()
|
||||
)
|
||||
host_config.next_run_at = _next_run_for_host(host_config)
|
||||
host_config.retention_warning = _retention_warning_for_host(host_config, _schedule_for_host(host_config))
|
||||
stats_summary = collect_dashboard_stats(hosts=hosts, global_config=global_config)
|
||||
context = {
|
||||
"hosts": hosts,
|
||||
@@ -274,6 +275,7 @@ def host_detail(request, host: str):
|
||||
context = {
|
||||
"host": host_config,
|
||||
"schedule": schedule,
|
||||
"retention_warning": _retention_warning_for_host(host_config, schedule),
|
||||
"next_run_at": _next_run_for_schedule(schedule, host_config),
|
||||
"scheduler_timezone": timezone.get_current_timezone_name(),
|
||||
"discovery": inspect_snapshot_discovery(host=host_config),
|
||||
@@ -526,17 +528,23 @@ def host_retention_plan(request, host: str):
|
||||
except PobsyncError as exc:
|
||||
messages.error(request, str(exc))
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
schedule = _schedule_for_host(host_config)
|
||||
scheduled_prune_limit = schedule.prune_max_delete if schedule and schedule.prune else None
|
||||
delete_count = len(plan["delete"])
|
||||
context = {
|
||||
"host": host_config,
|
||||
"kind": kind,
|
||||
"protect_bases": protect_bases,
|
||||
"plan": plan,
|
||||
"schedule": schedule,
|
||||
"scheduled_prune_limit": scheduled_prune_limit,
|
||||
"scheduled_prune_exceeded": scheduled_prune_limit is not None and delete_count > scheduled_prune_limit,
|
||||
"apply_form": RetentionApplyForm(
|
||||
host_name=host_config.host,
|
||||
initial={
|
||||
"kind": kind,
|
||||
"protect_bases": protect_bases,
|
||||
"max_delete": len(plan["delete"]),
|
||||
"max_delete": delete_count,
|
||||
},
|
||||
),
|
||||
}
|
||||
@@ -652,6 +660,43 @@ def _next_run_for_schedule(schedule: ScheduleConfig | None, host_config: HostCon
|
||||
return None
|
||||
|
||||
|
||||
def _retention_warning_for_host(host_config: HostConfig, schedule: ScheduleConfig | None) -> dict[str, object]:
|
||||
incomplete_count = host_config.snapshots.filter(kind=SnapshotRecord.Kind.INCOMPLETE).count()
|
||||
warning: dict[str, object] = {
|
||||
"has_warning": incomplete_count > 0,
|
||||
"incomplete_count": incomplete_count,
|
||||
}
|
||||
if schedule is None or not schedule.prune or not host_config.enabled:
|
||||
return warning
|
||||
try:
|
||||
plan = run_sql_retention_plan(
|
||||
host=host_config.host,
|
||||
kind="scheduled",
|
||||
protect_bases=bool(schedule.prune_protect_bases),
|
||||
)
|
||||
except PobsyncError as exc:
|
||||
warning.update(
|
||||
{
|
||||
"has_warning": True,
|
||||
"error": str(exc),
|
||||
}
|
||||
)
|
||||
return warning
|
||||
|
||||
delete_count = len(plan.get("delete") or [])
|
||||
warning.update(
|
||||
{
|
||||
"delete_count": delete_count,
|
||||
"max_delete": schedule.prune_max_delete,
|
||||
"protect_bases": bool(schedule.prune_protect_bases),
|
||||
"prune_exceeded": delete_count > schedule.prune_max_delete,
|
||||
}
|
||||
)
|
||||
if warning["prune_exceeded"]:
|
||||
warning["has_warning"] = True
|
||||
return warning
|
||||
|
||||
|
||||
def _default_schedule_initial() -> dict[str, object]:
|
||||
return {
|
||||
"cron_expr": "15 2 * * *",
|
||||
|
||||
Reference in New Issue
Block a user