From fe4ae9d147477cf1b7d1c53d4175b77c9efae43a Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 13:07:45 +0200 Subject: [PATCH] (ui) Add review actions to filtered run lists Add inline Mark reviewed actions for failed and warning runs on the run list, preserving active filters after review so Operational Status drill-downs can be cleared without opening every run detail page. Refs #22 --- .../templates/pobsync_backend/base.html | 5 ++++ .../templates/pobsync_backend/runs_list.html | 17 ++++++++++++- src/pobsync_backend/tests/test_views.py | 25 +++++++++++++++++++ src/pobsync_backend/views.py | 11 ++++++-- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index 4e8c6a7..ed8e326 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -273,6 +273,11 @@ } button.secondary:hover, .button-link.secondary:hover { background: #eef3f8; } + button.compact, + .button-link.compact { + font-size: 12px; + padding: 5px 8px; + } button:disabled { background: #d8dee6; border-color: #d8dee6; diff --git a/src/pobsync_backend/templates/pobsync_backend/runs_list.html b/src/pobsync_backend/templates/pobsync_backend/runs_list.html index a8dc10d..9be3a57 100644 --- a/src/pobsync_backend/templates/pobsync_backend/runs_list.html +++ b/src/pobsync_backend/templates/pobsync_backend/runs_list.html @@ -95,7 +95,22 @@ none {% endif %} - {% if run.reviewed_at %}reviewed{% elif run.status == "failed" or run.status == "warning" %}needed{% else %}none{% endif %} + + {% if run.reviewed_at %} + reviewed + {% elif run.status == "failed" or run.status == "warning" %} +
+ needed +
+ {% csrf_token %} + + +
+
+ {% else %} + none + {% endif %} + {% empty %} No runs matched the current filter. diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index 3437007..aa929b8 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -234,6 +234,31 @@ class ViewTests(TestCase): self.assertContains(response, "needed") self.assertNotContains(response, f"Run {success.id}") + def test_runs_list_can_mark_problem_run_reviewed(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.FAILED, run_type=BackupRun.RunType.MANUAL) + list_url = f'{reverse("runs_list")}?status=failed&review=needed' + + response = self.client.get(list_url) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Mark reviewed") + self.assertContains(response, 'value="/runs/?status=failed&review=needed"', html=False) + + response = self.client.post( + reverse("resolve_run_review", args=[run.id]), + {"next": list_url}, + follow=True, + ) + + run.refresh_from_db() + self.assertIsNotNone(run.reviewed_at) + self.assertEqual(run.reviewed_by, self.staff_user.username) + self.assertRedirects(response, list_url) + self.assertContains(response, f"Run {run.id} marked reviewed.") + self.assertNotContains(response, f"Run {run.id}", html=False) + 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") diff --git a/src/pobsync_backend/views.py b/src/pobsync_backend/views.py index e319485..5e2b85d 100644 --- a/src/pobsync_backend/views.py +++ b/src/pobsync_backend/views.py @@ -661,13 +661,13 @@ def resolve_run_review(request, run_id: int): return redirect("run_detail", run_id=run.id) if run.reviewed_at: messages.info(request, f"Run {run.id} was already marked reviewed.") - return redirect("run_detail", run_id=run.id) + return _redirect_after_run_review(request, run) run.reviewed_at = timezone.now() run.reviewed_by = request.user.get_username() run.save(update_fields=["reviewed_at", "reviewed_by"]) messages.success(request, f"Run {run.id} marked reviewed.") - return redirect("run_detail", run_id=run.id) + return _redirect_after_run_review(request, run) @staff_member_required @@ -937,6 +937,13 @@ def _next_run_for_schedule(schedule: ScheduleConfig | None, host_config: HostCon return None +def _redirect_after_run_review(request, run: BackupRun): + next_url = request.POST.get("next", "").strip() + if next_url.startswith("/"): + return redirect(next_url) + return redirect("run_detail", run_id=run.id) + + def _retention_warning_for_host(host_config: HostConfig, schedule: ScheduleConfig | None) -> dict[str, object]: incomplete_count = host_config.snapshots.filter( kind=SnapshotRecord.Kind.INCOMPLETE, -- 2.43.0