diff --git a/authentik/admin/templates/administration/task/list.html b/authentik/admin/templates/administration/task/list.html deleted file mode 100644 index 373bff1fc..000000000 --- a/authentik/admin/templates/administration/task/list.html +++ /dev/null @@ -1,84 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load humanize %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'System Tasks' %} -

-

{% trans "Long-running operations which authentik executes in the background." %}

-
-
-
-
-
-
- -
-
- - - - - - - - - - - - - {% for task in object_list %} - - - - - - - - - {% endfor %} - -
{% trans 'Identifier' %}{% trans 'Description' %}{% trans 'Last Run' %}{% trans 'Status' %}{% trans 'Messages' %}
- {{ task.html_name|join:"_­" }} - - - {{ task.task_description }} - - - - {{ task.finish_timestamp|naturaltime }} - - - - {% if task.result.status == task_successful %} - {% trans 'Successful' %} - {% elif task.result.status == task_warning %} - {% trans 'Warning' %} - {% elif task.result.status == task_error %} - {% trans 'Error' %} - {% else %} - {% trans 'Unknown' %} - {% endif %} - - - {% for message in task.result.messages %} -
- {{ message }} -
- {% endfor %} -
- - {% trans 'Retry Task' %} - -
-
-
-{% endblock %} diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 7432e7c29..559550204 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -20,7 +20,6 @@ from authentik.admin.views import ( stages_bindings, stages_invitations, stages_prompts, - tasks, tokens, users, ) @@ -323,12 +322,6 @@ urlpatterns = [ outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), name="outpost-service-connection-delete", ), - # Tasks - path( - "tasks/", - tasks.TaskListView.as_view(), - name="tasks", - ), # Event Notification Transpots path( "events/transports/create/", diff --git a/authentik/admin/views/tasks.py b/authentik/admin/views/tasks.py deleted file mode 100644 index fdc69d5f4..000000000 --- a/authentik/admin/views/tasks.py +++ /dev/null @@ -1,23 +0,0 @@ -"""authentik Tasks List""" -from typing import Any - -from django.views.generic.base import TemplateView - -from authentik.admin.mixins import AdminRequiredMixin -from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus - - -class TaskListView(AdminRequiredMixin, TemplateView): - """Show list of all background tasks""" - - template_name = "administration/task/list.html" - - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - kwargs = super().get_context_data(**kwargs) - kwargs["object_list"] = sorted( - TaskInfo.all().values(), key=lambda x: x.task_name - ) - kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL - kwargs["task_warning"] = TaskResultStatus.WARNING - kwargs["task_error"] = TaskResultStatus.ERROR - return kwargs diff --git a/web/src/api/SystemTask.ts b/web/src/api/SystemTask.ts new file mode 100644 index 000000000..222873286 --- /dev/null +++ b/web/src/api/SystemTask.ts @@ -0,0 +1,33 @@ +import { DefaultClient, QueryArguments } from "./Client"; + +export enum TaskStatus { + SUCCESSFUL = 1, + WARNING = 2, + ERROR = 4, +} + +export class SystemTask { + + task_name: string; + task_description: string; + task_finish_timestamp: number; + status: TaskStatus; + messages: string[]; + + constructor() { + throw Error(); + } + + static get(task_name: string): Promise { + return DefaultClient.fetch(["admin", "system_tasks", task_name]); + } + + static list(filter?: QueryArguments): Promise { + return DefaultClient.fetch(["admin", "system_tasks"], filter); + } + + static retry(task_name: string): string { + return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]); + } + +} diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index d728a11d6..d56670720 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -8,7 +8,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ new SidebarItem("Library", "/library"), new SidebarItem("Monitor").children( new SidebarItem("Overview", "/administration/overview"), - new SidebarItem("System Tasks", "/administration/tasks/"), + new SidebarItem("System Tasks", "/administration/system-tasks"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), diff --git a/web/src/pages/system-tasks/SystemTaskListPage.ts b/web/src/pages/system-tasks/SystemTaskListPage.ts new file mode 100644 index 000000000..bce1e09d3 --- /dev/null +++ b/web/src/pages/system-tasks/SystemTaskListPage.ts @@ -0,0 +1,87 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { AKResponse } from "../../api/Client"; +import { TablePage } from "../../elements/table/TablePage"; + +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/buttons/ActionButton"; +import { TableColumn } from "../../elements/table/Table"; +import { SystemTask, TaskStatus } from "../../api/SystemTask"; + +@customElement("ak-system-task-list") +export class SystemTaskListPage extends TablePage { + searchEnabled(): boolean { + return false; + } + pageTitle(): string { + return gettext("System Tasks"); + } + pageDescription(): string { + return gettext("Long-running operations which authentik executes in the background."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-automation"); + } + + @property() + order = "slug"; + + apiEndpoint(page: number): Promise> { + return SystemTask.list({ + ordering: this.order, + page: page, + }).then((tasks) => { + return { + pagination: { + count: tasks.length, + total_pages: 1, + start_index: 0, + end_index: tasks.length, + current: 1, + }, + results: tasks, + }; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Identifier", "task_name"), + new TableColumn("Description"), + new TableColumn("Last run"), + new TableColumn("Status"), + new TableColumn("Messages"), + new TableColumn(""), + ]; + } + + taskStatus(task: SystemTask): TemplateResult { + switch (task.status) { + case TaskStatus.SUCCESSFUL: + return html` ${gettext("Successful")}`; + case TaskStatus.WARNING: + return html` ${gettext("Warning")}`; + case TaskStatus.ERROR: + return html` ${gettext("Error")}`; + default: + return html` ${gettext("Unknown")}`; + } + } + + row(item: SystemTask): TemplateResult[] { + return [ + html`${item.task_name}`, + html`${item.task_description}`, + html`${new Date(item.task_finish_timestamp * 1000).toLocaleString()}`, + this.taskStatus(item), + html`${item.messages.map(m => { + return html`
  • ${m}
  • `; + })}`, + html` + ${gettext("Retry Task")} + `, + ]; + } + +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 453325ad8..0bcf43ced 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -21,6 +21,7 @@ import "./pages/providers/ProviderViewPage"; import "./pages/sources/SourcesListPage"; import "./pages/sources/SourceViewPage"; import "./pages/groups/GroupListPage"; +import "./pages/system-tasks/SystemTaskListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -28,6 +29,7 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^#.*")).redirect("/library"), new Route(new RegExp("^/library$"), html``), new Route(new RegExp("^/administration/overview$"), html``), + new Route(new RegExp("^/administration/system-tasks$"), html``), new Route(new RegExp("^/providers$"), html``), new Route(new RegExp(`^/providers/(?${ID_REGEX})$`)).then((args) => { return html``;