(release) Add review resolution for operational tasks
Add reviewed state for failed/warning runs and incomplete snapshot records, then use it to clear dashboard and host “need review” tasks after an operator has acknowledged them. Expose Mark reviewed actions on run detail and host retention warnings, keep reviewed records available for audit/debug, and exclude reviewed problem runs from operational counts and latest issue summaries. Refs #19 Refs #8
This commit is contained in:
@@ -53,8 +53,16 @@ def dashboard(request):
|
||||
run_count=Count("runs", distinct=True),
|
||||
queued_run_count=Count("runs", filter=Q(runs__status=BackupRun.Status.QUEUED), distinct=True),
|
||||
running_run_count=Count("runs", filter=Q(runs__status=BackupRun.Status.RUNNING), distinct=True),
|
||||
warning_run_count=Count("runs", filter=Q(runs__status=BackupRun.Status.WARNING), distinct=True),
|
||||
failed_run_count=Count("runs", filter=Q(runs__status=BackupRun.Status.FAILED), distinct=True),
|
||||
warning_run_count=Count(
|
||||
"runs",
|
||||
filter=Q(runs__status=BackupRun.Status.WARNING, runs__reviewed_at__isnull=True),
|
||||
distinct=True,
|
||||
),
|
||||
failed_run_count=Count(
|
||||
"runs",
|
||||
filter=Q(runs__status=BackupRun.Status.FAILED, runs__reviewed_at__isnull=True),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
.order_by("host")
|
||||
)
|
||||
@@ -83,8 +91,14 @@ def dashboard(request):
|
||||
"runs": BackupRun.objects.count(),
|
||||
"queued_runs": BackupRun.objects.filter(status=BackupRun.Status.QUEUED).count(),
|
||||
"running_runs": BackupRun.objects.filter(status=BackupRun.Status.RUNNING).count(),
|
||||
"warning_runs": BackupRun.objects.filter(status=BackupRun.Status.WARNING).count(),
|
||||
"failed_runs": BackupRun.objects.filter(status=BackupRun.Status.FAILED).count(),
|
||||
"warning_runs": BackupRun.objects.filter(
|
||||
status=BackupRun.Status.WARNING,
|
||||
reviewed_at__isnull=True,
|
||||
).count(),
|
||||
"failed_runs": BackupRun.objects.filter(
|
||||
status=BackupRun.Status.FAILED,
|
||||
reviewed_at__isnull=True,
|
||||
).count(),
|
||||
},
|
||||
}
|
||||
return render(request, "pobsync_backend/dashboard.html", context)
|
||||
@@ -331,7 +345,10 @@ def host_detail(request, host: str):
|
||||
"runs": host_config.runs.count(),
|
||||
"queued_runs": queued_runs.count(),
|
||||
"running_runs": running_runs.count(),
|
||||
"failed_runs": host_config.runs.filter(status=BackupRun.Status.FAILED).count(),
|
||||
"failed_runs": host_config.runs.filter(
|
||||
status=BackupRun.Status.FAILED,
|
||||
reviewed_at__isnull=True,
|
||||
).count(),
|
||||
"incomplete_snapshots": host_config.snapshots.filter(kind=SnapshotRecord.Kind.INCOMPLETE).count(),
|
||||
},
|
||||
}
|
||||
@@ -515,6 +532,40 @@ def cancel_run(request, run_id: int):
|
||||
return redirect("run_detail", run_id=run.id)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
@require_POST
|
||||
def resolve_run_review(request, run_id: int):
|
||||
run = get_object_or_404(BackupRun.objects.select_related("host"), id=run_id)
|
||||
if run.status not in {BackupRun.Status.FAILED, BackupRun.Status.WARNING}:
|
||||
messages.warning(request, f"Run {run.id} does not need review.")
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
@require_POST
|
||||
def resolve_host_incomplete_reviews(request, host: str):
|
||||
host_config = get_object_or_404(HostConfig, host=host)
|
||||
reviewed_count = host_config.snapshots.filter(
|
||||
kind=SnapshotRecord.Kind.INCOMPLETE,
|
||||
reviewed_at__isnull=True,
|
||||
).update(reviewed_at=timezone.now(), reviewed_by=request.user.get_username())
|
||||
|
||||
if reviewed_count:
|
||||
messages.success(request, f"Marked {reviewed_count} incomplete snapshot(s) reviewed for {host_config.host}.")
|
||||
else:
|
||||
messages.info(request, f"No incomplete snapshots needed review for {host_config.host}.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def snapshot_detail(request, snapshot_id: int):
|
||||
snapshot = get_object_or_404(
|
||||
@@ -764,7 +815,10 @@ def _next_run_for_schedule(schedule: ScheduleConfig | None, host_config: HostCon
|
||||
|
||||
|
||||
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()
|
||||
incomplete_count = host_config.snapshots.filter(
|
||||
kind=SnapshotRecord.Kind.INCOMPLETE,
|
||||
reviewed_at__isnull=True,
|
||||
).count()
|
||||
warning: dict[str, object] = {
|
||||
"has_warning": incomplete_count > 0,
|
||||
"incomplete_count": incomplete_count,
|
||||
|
||||
Reference in New Issue
Block a user