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.filters import BooleanFilter, MultipleChoiceFilter
|
||||||
from django_filters.filterset import FilterSet
|
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.fields import ReadOnlyField
|
||||||
from rest_framework.mixins import (
|
from rest_framework.mixins import (
|
||||||
ListModelMixin,
|
ListModelMixin,
|
||||||
RetrieveModelMixin,
|
RetrieveModelMixin,
|
||||||
)
|
)
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.events.logs import LogEventSerializer
|
from authentik.events.logs import LogEventSerializer
|
||||||
|
from authentik.rbac.decorators import permission_required
|
||||||
from authentik.tasks.models import Task, TaskStatus
|
from authentik.tasks.models import Task, TaskStatus
|
||||||
from authentik.tenants.utils import get_current_tenant
|
from authentik.tenants.utils import get_current_tenant
|
||||||
|
|
||||||
@ -84,3 +93,22 @@ class TaskViewSet(
|
|||||||
return Task.objects.select_related("rel_obj_content_type").filter(
|
return Task.objects.select_related("rel_obj_content_type").filter(
|
||||||
tenant=get_current_tenant()
|
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):
|
class Meta(TaskBase.Meta):
|
||||||
default_permissions = ("view",)
|
default_permissions = ("view",)
|
||||||
permissions = [
|
permissions = [
|
||||||
("retrigger_task", _("Restart failed task")),
|
("retry_task", _("Retry failed task")),
|
||||||
]
|
]
|
||||||
indexes = TaskBase.Meta.indexes + (
|
indexes = TaskBase.Meta.indexes + (
|
||||||
models.Index(fields=("rel_obj_content_type", "rel_obj_id")),
|
models.Index(fields=("rel_obj_content_type", "rel_obj_id")),
|
||||||
|
|||||||
@ -5450,7 +5450,7 @@
|
|||||||
"authentik_stages_user_write.change_userwritestage",
|
"authentik_stages_user_write.change_userwritestage",
|
||||||
"authentik_stages_user_write.delete_userwritestage",
|
"authentik_stages_user_write.delete_userwritestage",
|
||||||
"authentik_stages_user_write.view_userwritestage",
|
"authentik_stages_user_write.view_userwritestage",
|
||||||
"authentik_tasks.retrigger_task",
|
"authentik_tasks.retry_task",
|
||||||
"authentik_tasks.view_task",
|
"authentik_tasks.view_task",
|
||||||
"authentik_tasks_schedules.change_schedule",
|
"authentik_tasks_schedules.change_schedule",
|
||||||
"authentik_tasks_schedules.send_schedule",
|
"authentik_tasks_schedules.send_schedule",
|
||||||
@ -10102,7 +10102,7 @@
|
|||||||
"authentik_stages_user_write.change_userwritestage",
|
"authentik_stages_user_write.change_userwritestage",
|
||||||
"authentik_stages_user_write.delete_userwritestage",
|
"authentik_stages_user_write.delete_userwritestage",
|
||||||
"authentik_stages_user_write.view_userwritestage",
|
"authentik_stages_user_write.view_userwritestage",
|
||||||
"authentik_tasks.retrigger_task",
|
"authentik_tasks.retry_task",
|
||||||
"authentik_tasks.view_task",
|
"authentik_tasks.view_task",
|
||||||
"authentik_tasks_schedules.change_schedule",
|
"authentik_tasks_schedules.change_schedule",
|
||||||
"authentik_tasks_schedules.send_schedule",
|
"authentik_tasks_schedules.send_schedule",
|
||||||
|
|||||||
@ -64,9 +64,6 @@ class CurrentTask(Middleware):
|
|||||||
raise CurrentTaskNotFound()
|
raise CurrentTaskNotFound()
|
||||||
return task[-1]
|
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):
|
def before_process_message(self, broker: Broker, message: Message):
|
||||||
tasks = self._TASKS.get()
|
tasks = self._TASKS.get()
|
||||||
if tasks is None:
|
if tasks is None:
|
||||||
|
|||||||
29
schema.yml
29
schema.yml
@ -40629,6 +40629,35 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
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/:
|
/tenants/domains/:
|
||||||
get:
|
get:
|
||||||
operationId: tenants_domains_list
|
operationId: tenants_domains_list
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
import { formatElapsedTime } from "#common/temporal";
|
import { formatElapsedTime } from "#common/temporal";
|
||||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/events/LogViewer";
|
import "@goauthentik/elements/events/LogViewer";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
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 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")
|
@customElement("ak-task-list")
|
||||||
export class TaskList extends Table<Task> {
|
export class TaskList extends Table<Task> {
|
||||||
@ -166,7 +173,29 @@ export class TaskList extends Table<Task> {
|
|||||||
html`<div>${formatElapsedTime(item.mtime || new Date())}</div>
|
html`<div>${formatElapsedTime(item.mtime || new Date())}</div>
|
||||||
<small>${item.mtime.toLocaleString()}</small>`,
|
<small>${item.mtime.toLocaleString()}</small>`,
|
||||||
this.taskState(item),
|
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