task retries from admin ui
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
@ -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)
|
||||
|
22
authentik/tasks/migrations/0004_alter_task_options.py
Normal file
22
authentik/tasks/migrations/0004_alter_task_options.py
Normal 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",
|
||||
},
|
||||
),
|
||||
]
|
@ -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")),
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
29
schema.yml
29
schema.yml
@ -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
|
||||
|
@ -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``,
|
||||
];
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user