task retries from admin ui

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2025-06-24 20:24:38 +02:00
parent 0e67c1d818
commit 94867aaebf
7 changed files with 113 additions and 8 deletions

View File

@ -1,14 +1,23 @@
from django_dramatiq_postgres.models import TaskState
from django_filters.filters import BooleanFilter, MultipleChoiceFilter
from django_filters.filterset import FilterSet
from dramatiq.broker import get_broker
from dramatiq.message import Message
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.mixins import (
ListModelMixin,
RetrieveModelMixin,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.events.logs import LogEventSerializer
from authentik.rbac.decorators import permission_required
from authentik.tasks.models import Task, TaskStatus
from authentik.tenants.utils import get_current_tenant
@ -84,3 +93,22 @@ class TaskViewSet(
return Task.objects.select_related("rel_obj_content_type").filter(
tenant=get_current_tenant()
)
@permission_required(None, ["authentik_tasks.retry_task"])
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: OpenApiResponse(description="Task retried successfully"),
400: OpenApiResponse(description="Task is not in a retryable state"),
404: OpenApiResponse(description="Task not found"),
},
)
@action(detail=True, methods=["POST"], permission_classes=[])
def retry(self, request: Request, pk=None) -> Response:
"""Retry task"""
task: Task = self.get_object()
if task.state not in (TaskState.REJECTED, TaskState.DONE):
return Response(status=400)
broker = get_broker()
broker.enqueue(Message.decode(task.message))
return Response(status=204)

View File

@ -0,0 +1,22 @@
# Generated by Django 5.1.11 on 2025-06-24 18:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_tasks", "0003_task__previous_messages"),
]
operations = [
migrations.AlterModelOptions(
name="task",
options={
"default_permissions": ("view",),
"permissions": [("retry_task", "Retry failed task")],
"verbose_name": "Task",
"verbose_name_plural": "Tasks",
},
),
]

View File

@ -46,7 +46,7 @@ class Task(SerializerModel, TaskBase):
class Meta(TaskBase.Meta):
default_permissions = ("view",)
permissions = [
("retrigger_task", _("Restart failed task")),
("retry_task", _("Retry failed task")),
]
indexes = TaskBase.Meta.indexes + (
models.Index(fields=("rel_obj_content_type", "rel_obj_id")),

View File

@ -5450,7 +5450,7 @@
"authentik_stages_user_write.change_userwritestage",
"authentik_stages_user_write.delete_userwritestage",
"authentik_stages_user_write.view_userwritestage",
"authentik_tasks.retrigger_task",
"authentik_tasks.retry_task",
"authentik_tasks.view_task",
"authentik_tasks_schedules.change_schedule",
"authentik_tasks_schedules.send_schedule",
@ -10102,7 +10102,7 @@
"authentik_stages_user_write.change_userwritestage",
"authentik_stages_user_write.delete_userwritestage",
"authentik_stages_user_write.view_userwritestage",
"authentik_tasks.retrigger_task",
"authentik_tasks.retry_task",
"authentik_tasks.view_task",
"authentik_tasks_schedules.change_schedule",
"authentik_tasks_schedules.send_schedule",

View File

@ -64,9 +64,6 @@ class CurrentTask(Middleware):
raise CurrentTaskNotFound()
return task[-1]
def before_enqueue(self, broker: Broker, message: Message, delay: int):
self.after_process_message(broker, message)
def before_process_message(self, broker: Broker, message: Message):
tasks = self._TASKS.get()
if tasks is None:

View File

@ -40629,6 +40629,35 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/tasks/tasks/{message_id}/retry/:
post:
operationId: tasks_tasks_retry_create
description: Retry task
parameters:
- in: path
name: message_id
schema:
type: string
format: uuid
description: A UUID string identifying this Task.
required: true
tags:
- tasks
security:
- authentik: []
responses:
'204':
description: Task retried successfully
'400':
description: Task is not in a retryable state
'404':
description: Task not found
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/tenants/domains/:
get:
operationId: tenants_domains_list

View File

@ -1,7 +1,9 @@
import { EVENT_REFRESH } from "#common/constants";
import { formatElapsedTime } from "#common/temporal";
import "@goauthentik/admin/rbac/ObjectPermissionModal";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { PFColor } from "@goauthentik/elements/Label";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/events/LogViewer";
import "@goauthentik/elements/forms/DeleteBulkForm";
@ -16,7 +18,12 @@ import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { Task, TasksApi, TasksTasksListAggregatedStatusEnum } from "@goauthentik/api";
import {
Task,
TasksApi,
TasksTasksListAggregatedStatusEnum,
TasksTasksListStateEnum,
} from "@goauthentik/api";
@customElement("ak-task-list")
export class TaskList extends Table<Task> {
@ -166,7 +173,29 @@ export class TaskList extends Table<Task> {
html`<div>${formatElapsedTime(item.mtime || new Date())}</div>
<small>${item.mtime.toLocaleString()}</small>`,
this.taskState(item),
html``,
[TasksTasksListStateEnum.Rejected, TasksTasksListStateEnum.Done].includes(item.state)
? html`<ak-action-button
class="pf-m-plain"
.apiRequest=${() => {
return new TasksApi(DEFAULT_CONFIG)
.tasksTasksRetryCreate({
messageId: item.messageId,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
}}
>
<pf-tooltip position="top" content=${msg("Retry task")}>
<i class="fas fa-redo" aria-hidden="true"></i>
</pf-tooltip>
</ak-action-button>`
: html``,
];
}