diff --git a/authentik/events/api/tasks.py b/authentik/events/api/tasks.py index ca8926b15..99a97aa8d 100644 --- a/authentik/events/api/tasks.py +++ b/authentik/events/api/tasks.py @@ -1,4 +1,5 @@ """Tasks API""" +from datetime import datetime, timezone from importlib import import_module from django.contrib import messages @@ -6,13 +7,7 @@ from django.utils.translation import gettext_lazy as _ 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 ( - CharField, - ChoiceField, - DateTimeField, - ListField, - SerializerMethodField, -) +from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer @@ -32,8 +27,8 @@ class SystemTaskSerializer(ModelSerializer): full_name = SerializerMethodField() uid = CharField(required=False) description = CharField() - start_timestamp = DateTimeField() - finish_timestamp = DateTimeField() + start_timestamp = SerializerMethodField() + finish_timestamp = SerializerMethodField() duration = SerializerMethodField() status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus]) @@ -45,9 +40,15 @@ class SystemTaskSerializer(ModelSerializer): return f"{instance.name}:{instance.uid}" return instance.name - def get_duration(self, instance: SystemTask) -> int: + def get_start_timestamp(self, instance: SystemTask) -> datetime: + return datetime.fromtimestamp(instance.start_timestamp, tz=timezone.utc) + + def get_finish_timestamp(self, instance: SystemTask) -> datetime: + return datetime.fromtimestamp(instance.finish_timestamp, tz=timezone.utc) + + def get_duration(self, instance: SystemTask) -> float: """Get the duration a task took to run""" - return max(instance.finish_timestamp.timestamp() - instance.start_timestamp.timestamp(), 0) + return max(instance.finish_timestamp - instance.start_timestamp, 0) class Meta: model = SystemTask @@ -74,7 +75,7 @@ class SystemTaskViewSet(ReadOnlyModelViewSet): ordering = ["name", "uid", "status"] search_fields = ["name", "description", "uid", "status"] - @permission_required(None, ["authentik_events.rerun_task"]) + @permission_required(None, ["authentik_events.run_task"]) @extend_schema( request=OpenApiTypes.NONE, responses={ @@ -84,21 +85,21 @@ class SystemTaskViewSet(ReadOnlyModelViewSet): }, ) @action(detail=True, methods=["post"]) - def retry(self, request: Request, pk=None) -> Response: - """Retry task""" - task = self.get_object() + def run(self, request: Request, pk=None) -> Response: + """Run task""" + task: SystemTask = self.get_object() try: task_module = import_module(task.task_call_module) task_func = getattr(task_module, task.task_call_func) - LOGGER.debug("Running task", task=task_func) + LOGGER.info("Running task", task=task_func) task_func.delay(*task.task_call_args, **task.task_call_kwargs) messages.success( self.request, - _("Successfully re-scheduled Task %(name)s!" % {"name": task.task_name}), + _("Successfully started task %(name)s." % {"name": task.name}), ) return Response(status=204) - except (ImportError, AttributeError): # pragma: no cover - LOGGER.warning("Failed to run task, remove state", task=task) + except (ImportError, AttributeError) as exc: # pragma: no cover + LOGGER.warning("Failed to run task, remove state", task=task.name, exc=exc) # if we get an import error, the module path has probably changed task.delete() return Response(status=500) diff --git a/authentik/events/middleware.py b/authentik/events/middleware.py index 9843402ab..d088fcfec 100644 --- a/authentik/events/middleware.py +++ b/authentik/events/middleware.py @@ -21,7 +21,7 @@ from authentik.core.models import ( UserSourceConnection, ) from authentik.enterprise.providers.rac.models import ConnectionToken -from authentik.events.models import Event, EventAction, Notification +from authentik.events.models import Event, EventAction, Notification, SystemTask from authentik.events.utils import model_to_dict from authentik.flows.models import FlowToken, Stage from authentik.lib.sentry import before_send @@ -56,6 +56,7 @@ IGNORED_MODELS = ( SCIMGroup, Reputation, ConnectionToken, + SystemTask, ) diff --git a/authentik/events/migrations/0003_systemtask.py b/authentik/events/migrations/0003_systemtask.py index 79ca4bf06..77bad3af8 100644 --- a/authentik/events/migrations/0003_systemtask.py +++ b/authentik/events/migrations/0003_systemtask.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.1 on 2024-01-13 20:14 +# Generated by Django 5.0.1 on 2024-01-13 21:42 import uuid @@ -29,8 +29,8 @@ class Migration(migrations.Migration): ), ("name", models.TextField()), ("uid", models.TextField(null=True)), - ("start_timestamp", models.DateTimeField(auto_now_add=True)), - ("finish_timestamp", models.DateTimeField(auto_now=True)), + ("start_timestamp", models.FloatField()), + ("finish_timestamp", models.FloatField()), ( "status", models.TextField( @@ -52,7 +52,7 @@ class Migration(migrations.Migration): options={ "verbose_name": "System Task", "verbose_name_plural": "System Tasks", - "permissions": [("rerun_task", "Rerun task")], + "permissions": [("run_task", "Run task")], "default_permissions": ["view"], "unique_together": {("name", "uid")}, }, diff --git a/authentik/events/models.py b/authentik/events/models.py index 8d4c70a97..91d36c5a0 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -601,8 +601,8 @@ class SystemTask(SerializerModel, ExpiringModel): name = models.TextField() uid = models.TextField(null=True) - start_timestamp = models.DateTimeField(auto_now_add=True) - finish_timestamp = models.DateTimeField(auto_now=True) + start_timestamp = models.FloatField() + finish_timestamp = models.FloatField() status = models.TextField(choices=TaskStatus.choices) @@ -636,6 +636,6 @@ class SystemTask(SerializerModel, ExpiringModel): unique_together = (("name", "uid"),) # Remove "add", "change" and "delete" permissions as those are not used default_permissions = ["view"] - permissions = [("rerun_task", _("Rerun task"))] + permissions = [("run_task", _("Run task"))] verbose_name = _("System Task") verbose_name_plural = _("System Tasks") diff --git a/authentik/events/monitored_tasks.py b/authentik/events/monitored_tasks.py index 43eb63719..383bc376c 100644 --- a/authentik/events/monitored_tasks.py +++ b/authentik/events/monitored_tasks.py @@ -1,5 +1,5 @@ """Monitored tasks""" -from datetime import datetime, timedelta, timezone +from datetime import timedelta from timeit import default_timer from typing import Any, Optional @@ -69,10 +69,8 @@ class MonitoredTask(Task): uid=self._uid, defaults={ "description": self.__doc__, - "start_timestamp": datetime.fromtimestamp( - self._start or default_timer(), tz=timezone.utc - ), - "finish_timestamp": datetime.fromtimestamp(default_timer(), tz=timezone.utc), + "start_timestamp": self._start or default_timer(), + "finish_timestamp": default_timer(), "task_call_module": self.__module__, "task_call_func": self.__name__, "task_call_args": args, @@ -95,10 +93,8 @@ class MonitoredTask(Task): uid=self._uid, defaults={ "description": self.__doc__, - "start_timestamp": datetime.fromtimestamp( - self._start or default_timer(), tz=timezone.utc - ), - "finish_timestamp": datetime.fromtimestamp(default_timer(), tz=timezone.utc), + "start_timestamp": self._start or default_timer(), + "finish_timestamp": default_timer(), "task_call_module": self.__module__, "task_call_func": self.__name__, "task_call_args": args, diff --git a/blueprints/schema.json b/blueprints/schema.json index 25f9d226c..946952b33 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -3252,16 +3252,6 @@ "minLength": 1, "title": "Description" }, - "start_timestamp": { - "type": "string", - "format": "date-time", - "title": "Start timestamp" - }, - "finish_timestamp": { - "type": "string", - "format": "date-time", - "title": "Finish timestamp" - }, "status": { "type": "string", "enum": [ diff --git a/schema.yml b/schema.yml index 729eb6a61..e12028bdb 100644 --- a/schema.yml +++ b/schema.yml @@ -6944,10 +6944,10 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' - /events/system_tasks/{uuid}/retry/: + /events/system_tasks/{uuid}/run/: post: - operationId: events_system_tasks_retry_create - description: Retry task + operationId: events_system_tasks_run_create + description: Run task parameters: - in: path name: uuid @@ -42719,11 +42719,14 @@ components: start_timestamp: type: string format: date-time + readOnly: true finish_timestamp: type: string format: date-time + readOnly: true duration: - type: integer + type: number + format: double description: Get the duration a task took to run readOnly: true status: diff --git a/web/src/admin/system-tasks/SystemTaskListPage.ts b/web/src/admin/system-tasks/SystemTaskListPage.ts index 85d3e0a14..82dc9171f 100644 --- a/web/src/admin/system-tasks/SystemTaskListPage.ts +++ b/web/src/admin/system-tasks/SystemTaskListPage.ts @@ -113,7 +113,7 @@ export class SystemTaskListPage extends TablePage { class="pf-m-plain" .apiRequest=${() => { return new EventsApi(DEFAULT_CONFIG) - .eventsSystemTasksRetryCreate({ + .eventsSystemTasksRunCreate({ uuid: item.uuid, }) .then(() => {