Add functionality to reset stale RUNNING tasks to READY in task overview

This commit is contained in:
Ross
2026-05-18 14:15:21 +01:00
parent e016175d2b
commit c55797636a
3 changed files with 58 additions and 1 deletions
+22
View File
@@ -32,6 +32,8 @@ from reversion.admin import VersionAdmin
from django.forms.widgets import RadioSelect
from django.utils.html import format_html
from django.urls import reverse
from django.utils import timezone
from datetime import timedelta
from tinymce.widgets import TinyMCE
@@ -226,6 +228,24 @@ if DBTaskResult is not None:
msg += f" Skipped {blocked} running task(s)."
self.message_user(request, msg, level=messages.SUCCESS)
@admin.action(description="Reset stale RUNNING tasks to READY (>30 min)")
def reset_stale_running_to_ready(self, request, queryset):
cutoff = timezone.now() - timedelta(minutes=30)
updated = queryset.filter(status="RUNNING", started_at__lt=cutoff).update(
status="READY",
started_at=None,
finished_at=None,
worker_ids="",
exception_class_path="",
traceback="",
return_value=None,
)
self.message_user(
request,
f"Reset {updated} stale RUNNING task(s) to READY (older than 30 minutes).",
level=messages.SUCCESS,
)
@admin.action(description="Delete selected COMPLETED tasks")
def delete_selected_completed(self, request, queryset):
completed_qs = queryset.filter(status="SUCCESSFUL")
@@ -278,6 +298,7 @@ if DBTaskResult is not None:
admin_cls.actions = (
"reset_failed_to_ready",
"reset_selected_to_ready",
"reset_stale_running_to_ready",
"delete_selected_completed",
"delete_selected_failed",
)
@@ -339,6 +360,7 @@ if DBTaskResult is not None:
admin_cls.traceback_pretty = traceback_pretty
admin_cls.reset_failed_to_ready = reset_failed_to_ready
admin_cls.reset_selected_to_ready = reset_selected_to_ready
admin_cls.reset_stale_running_to_ready = reset_stale_running_to_ready
admin_cls.delete_selected_completed = delete_selected_completed
admin_cls.delete_selected_failed = delete_selected_failed
admin_cls.has_add_permission = has_add_permission
+12
View File
@@ -82,10 +82,22 @@
<option value="">Choose task action...</option>
<option value="reset_failed">Reset FAILED to READY</option>
<option value="reset_selected">Reset selected to READY (skip RUNNING)</option>
<option value="reset_stale_running">Reset stale RUNNING to READY</option>
<option value="delete_completed">Delete selected COMPLETED</option>
<option value="delete_failed">Delete selected FAILED</option>
<option value="delete_selected_all">Delete ALL selected (including RUNNING)</option>
</select>
<input
class="form-control form-control-sm"
type="number"
name="stale_minutes"
min="1"
step="1"
value="30"
style="max-width: 150px;"
title="Stale cutoff in minutes for reset stale RUNNING action"
aria-label="Stale minutes"
>
<button type="submit" class="btn btn-sm btn-primary">Apply to selected</button>
<div class="form-check ms-2">
<input class="form-check-input" type="checkbox" id="select-all-tasks">
+24 -1
View File
@@ -4,6 +4,7 @@ import csv
import random
import copy
import io
from datetime import timedelta
from typing import Any
from dal import autocomplete
from django.db.models.base import Model as Model
@@ -25,6 +26,7 @@ from django.template.defaultfilters import filesizeformat
# from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.exceptions import (
FieldError,
ObjectDoesNotExist,
@@ -882,7 +884,7 @@ def series_reconstruct_status_htmx(request, series_id, task_id):
@login_required
@user_is_atlas_editor
def task_overview(request):
"""Atlas admin-style task overview page with active/completed/failed task summaries."""
"""Atlas editor task control page for inspecting and recovering queued/background tasks."""
if DBTaskResult is None:
return render(
request,
@@ -930,6 +932,27 @@ def task_overview(request):
if blocked:
msg += f" Skipped {blocked} running task(s)."
messages.success(request, msg)
elif action == "reset_stale_running":
stale_minutes_raw = (request.POST.get("stale_minutes") or "30").strip()
try:
stale_minutes = max(int(stale_minutes_raw), 1)
except ValueError:
stale_minutes = 30
cutoff = timezone.now() - timedelta(minutes=stale_minutes)
updated = selected_qs.filter(status="RUNNING", started_at__lt=cutoff).update(
status="READY",
started_at=None,
finished_at=None,
worker_ids="",
exception_class_path="",
traceback="",
return_value=None,
)
messages.success(
request,
f"Reset {updated} stale RUNNING task(s) to READY (older than {stale_minutes} minute(s)).",
)
elif action == "delete_completed":
deleted, _ = selected_qs.filter(status="SUCCESSFUL").delete()
messages.success(request, f"Deleted {deleted} completed task record(s).")