restart front
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
@ -1,3 +1,6 @@
|
|||||||
|
from django_filters.filters import BooleanFilter
|
||||||
|
from django_filters.filterset import FilterSet
|
||||||
|
from rest_framework.fields import ReadOnlyField
|
||||||
from rest_framework.mixins import (
|
from rest_framework.mixins import (
|
||||||
ListModelMixin,
|
ListModelMixin,
|
||||||
RetrieveModelMixin,
|
RetrieveModelMixin,
|
||||||
@ -11,6 +14,9 @@ from authentik.tenants.utils import get_current_tenant
|
|||||||
|
|
||||||
|
|
||||||
class TaskSerializer(ModelSerializer):
|
class TaskSerializer(ModelSerializer):
|
||||||
|
rel_obj_app_label = ReadOnlyField(source="rel_obj_content_type.app_label")
|
||||||
|
rel_obj_model = ReadOnlyField(source="rel_obj_content_type.model")
|
||||||
|
|
||||||
messages = LogEventSerializer(many=True, source="_messages")
|
messages = LogEventSerializer(many=True, source="_messages")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -21,7 +27,8 @@ class TaskSerializer(ModelSerializer):
|
|||||||
"actor_name",
|
"actor_name",
|
||||||
"state",
|
"state",
|
||||||
"mtime",
|
"mtime",
|
||||||
"rel_obj_content_type",
|
"rel_obj_app_label",
|
||||||
|
"rel_obj_model",
|
||||||
"rel_obj_id",
|
"rel_obj_id",
|
||||||
"uid",
|
"uid",
|
||||||
"messages",
|
"messages",
|
||||||
@ -29,6 +36,23 @@ class TaskSerializer(ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TaskFilter(FilterSet):
|
||||||
|
rel_obj_id__isnull = BooleanFilter("rel_obj_id", "isnull")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Task
|
||||||
|
fields = (
|
||||||
|
"queue_name",
|
||||||
|
"actor_name",
|
||||||
|
"state",
|
||||||
|
"rel_obj_content_type__app_label",
|
||||||
|
"rel_obj_content_type__model",
|
||||||
|
"rel_obj_id",
|
||||||
|
"rel_obj_id__isnull",
|
||||||
|
"aggregated_status",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskViewSet(
|
class TaskViewSet(
|
||||||
RetrieveModelMixin,
|
RetrieveModelMixin,
|
||||||
ListModelMixin,
|
ListModelMixin,
|
||||||
@ -41,16 +65,16 @@ class TaskViewSet(
|
|||||||
"queue_name",
|
"queue_name",
|
||||||
"actor_name",
|
"actor_name",
|
||||||
"state",
|
"state",
|
||||||
|
"rel_obj_app_label",
|
||||||
|
"rel_obj_model",
|
||||||
|
"rel_obj_id",
|
||||||
"_uid",
|
"_uid",
|
||||||
"aggregated_status",
|
"aggregated_status",
|
||||||
)
|
)
|
||||||
filterset_fields = (
|
filterset_class = TaskFilter
|
||||||
"queue_name",
|
|
||||||
"actor_name",
|
|
||||||
"state",
|
|
||||||
"aggregated_status",
|
|
||||||
)
|
|
||||||
ordering = ("-mtime",)
|
ordering = ("-mtime",)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Task.objects.filter(tenant=get_current_tenant())
|
return Task.objects.select_related("rel_obj_content_type").filter(
|
||||||
|
tenant=get_current_tenant()
|
||||||
|
)
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
from django_filters.filters import BooleanFilter
|
||||||
|
from django_filters.filterset import FilterSet
|
||||||
from dramatiq.actor import Actor
|
from dramatiq.actor import Actor
|
||||||
from dramatiq.broker import get_broker
|
from dramatiq.broker import get_broker
|
||||||
from dramatiq.errors import ActorNotFound
|
from dramatiq.errors import ActorNotFound
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import ReadOnlyField
|
||||||
from rest_framework.mixins import (
|
from rest_framework.mixins import (
|
||||||
ListModelMixin,
|
ListModelMixin,
|
||||||
RetrieveModelMixin,
|
RetrieveModelMixin,
|
||||||
@ -20,19 +23,25 @@ from authentik.tasks.schedules.models import Schedule
|
|||||||
|
|
||||||
|
|
||||||
class ScheduleSerializer(ModelSerializer):
|
class ScheduleSerializer(ModelSerializer):
|
||||||
|
rel_obj_app_label = ReadOnlyField(source="rel_obj_content_type.app_label")
|
||||||
|
rel_obj_model = ReadOnlyField(source="rel_obj_content_type.model")
|
||||||
|
|
||||||
description = SerializerMethodField()
|
description = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Schedule
|
model = Schedule
|
||||||
fields = [
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"uid",
|
"uid",
|
||||||
"actor_name",
|
"actor_name",
|
||||||
|
"rel_obj_app_label",
|
||||||
|
"rel_obj_model",
|
||||||
|
"rel_obj_id",
|
||||||
"crontab",
|
"crontab",
|
||||||
"paused",
|
"paused",
|
||||||
"next_run",
|
"next_run",
|
||||||
"description",
|
"description",
|
||||||
]
|
)
|
||||||
|
|
||||||
def get_description(self, instance: Schedule) -> str | None:
|
def get_description(self, instance: Schedule) -> str | None:
|
||||||
if instance.rel_obj:
|
if instance.rel_obj:
|
||||||
@ -43,22 +52,48 @@ class ScheduleSerializer(ModelSerializer):
|
|||||||
actor: Actor = get_broker().get_actor(instance.actor_name)
|
actor: Actor = get_broker().get_actor(instance.actor_name)
|
||||||
except ActorNotFound:
|
except ActorNotFound:
|
||||||
return "FIXME this shouldn't happen"
|
return "FIXME this shouldn't happen"
|
||||||
|
if not actor.fn.__doc__:
|
||||||
|
return "no doc"
|
||||||
return actor.fn.__doc__.strip()
|
return actor.fn.__doc__.strip()
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleFilter(FilterSet):
|
||||||
|
rel_obj_id__isnull = BooleanFilter("rel_obj_id", "isnull")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Schedule
|
||||||
|
fields = (
|
||||||
|
"actor_name",
|
||||||
|
"rel_obj_content_type__app_label",
|
||||||
|
"rel_obj_content_type__model",
|
||||||
|
"rel_obj_id",
|
||||||
|
"rel_obj_id__isnull",
|
||||||
|
"paused",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ScheduleViewSet(
|
class ScheduleViewSet(
|
||||||
RetrieveModelMixin,
|
RetrieveModelMixin,
|
||||||
UpdateModelMixin,
|
UpdateModelMixin,
|
||||||
ListModelMixin,
|
ListModelMixin,
|
||||||
GenericViewSet,
|
GenericViewSet,
|
||||||
):
|
):
|
||||||
queryset = Schedule.objects.all()
|
queryset = Schedule.objects.select_related("rel_obj_content_type").all()
|
||||||
serializer_class = ScheduleSerializer
|
serializer_class = ScheduleSerializer
|
||||||
search_fields = (
|
search_fields = (
|
||||||
"id",
|
"id",
|
||||||
"uid",
|
"uid",
|
||||||
|
"actor_name",
|
||||||
|
"rel_obj_content_type__app_label",
|
||||||
|
"rel_obj_content_type__model",
|
||||||
|
"rel_obj_id",
|
||||||
|
"description",
|
||||||
|
)
|
||||||
|
filterset_class = ScheduleFilter
|
||||||
|
ordering = (
|
||||||
|
"next_run",
|
||||||
|
"uid",
|
||||||
)
|
)
|
||||||
ordering = ("next_run", "uid")
|
|
||||||
|
|
||||||
@permission_required("authentik_tasks_schedules.send_schedule")
|
@permission_required("authentik_tasks_schedules.send_schedule")
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.1.10 on 2025-06-11 12:57
|
||||||
|
|
||||||
|
import pgtrigger.compiler
|
||||||
|
import pgtrigger.migrations
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_tasks_schedules", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name="schedule",
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name="set_next_run_on_paused",
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
condition='WHEN (NEW."paused" AND NOT OLD."paused")',
|
||||||
|
func="\n NEW.next_run = to_timestamp(0);\n RETURN NEW;\n ",
|
||||||
|
hash="7fe580a86de70723522cfcbac712785984000f92",
|
||||||
|
operation="UPDATE",
|
||||||
|
pgid="pgtrigger_set_next_run_on_paused_95c6d",
|
||||||
|
table="authentik_tasks_schedules_schedule",
|
||||||
|
when="BEFORE",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import pickle # nosec
|
import pickle # nosec
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import pgtrigger
|
||||||
from cron_converter import Cron
|
from cron_converter import Cron
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -55,6 +56,18 @@ class Schedule(SerializerModel):
|
|||||||
("send_schedule", _("Manually trigger a schedule")),
|
("send_schedule", _("Manually trigger a schedule")),
|
||||||
]
|
]
|
||||||
indexes = (models.Index(fields=("rel_obj_content_type", "rel_obj_id")),)
|
indexes = (models.Index(fields=("rel_obj_content_type", "rel_obj_id")),)
|
||||||
|
triggers = (
|
||||||
|
pgtrigger.Trigger(
|
||||||
|
name="set_next_run_on_paused",
|
||||||
|
operation=pgtrigger.Update,
|
||||||
|
when=pgtrigger.Before,
|
||||||
|
condition=pgtrigger.Q(new__paused=True) & pgtrigger.Q(old__paused=False),
|
||||||
|
func="""
|
||||||
|
NEW.next_run = to_timestamp(0);
|
||||||
|
RETURN NEW;
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.uid
|
return self.uid
|
||||||
|
|||||||
@ -15878,6 +15878,14 @@
|
|||||||
"model_authentik_tasks_schedules.schedule": {
|
"model_authentik_tasks_schedules.schedule": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"rel_obj_id": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"minLength": 1,
|
||||||
|
"title": "Rel obj id"
|
||||||
|
},
|
||||||
"crontab": {
|
"crontab": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
|
|||||||
72
schema.yml
72
schema.yml
@ -40441,6 +40441,10 @@ paths:
|
|||||||
get:
|
get:
|
||||||
operationId: tasks_schedules_list
|
operationId: tasks_schedules_list
|
||||||
parameters:
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: actor_name
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
required: false
|
required: false
|
||||||
in: query
|
in: query
|
||||||
@ -40459,6 +40463,26 @@ paths:
|
|||||||
description: Number of results to return per page.
|
description: Number of results to return per page.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
- in: query
|
||||||
|
name: paused
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_content_type__app_label
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_content_type__model
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_id
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_id__isnull
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
- name: search
|
- name: search
|
||||||
required: false
|
required: false
|
||||||
in: query
|
in: query
|
||||||
@ -40668,6 +40692,22 @@ paths:
|
|||||||
name: queue_name
|
name: queue_name
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_content_type__app_label
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_content_type__model
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_id
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: rel_obj_id__isnull
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
- name: search
|
- name: search
|
||||||
required: false
|
required: false
|
||||||
in: query
|
in: query
|
||||||
@ -54942,6 +54982,10 @@ components:
|
|||||||
PatchedScheduleRequest:
|
PatchedScheduleRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
rel_obj_id:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
minLength: 1
|
||||||
crontab:
|
crontab:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
@ -58883,6 +58927,16 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
description: Dramatiq actor to call
|
description: Dramatiq actor to call
|
||||||
|
rel_obj_app_label:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
rel_obj_model:
|
||||||
|
type: string
|
||||||
|
title: Python model class name
|
||||||
|
readOnly: true
|
||||||
|
rel_obj_id:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
crontab:
|
crontab:
|
||||||
type: string
|
type: string
|
||||||
description: When to schedule tasks
|
description: When to schedule tasks
|
||||||
@ -58903,10 +58957,16 @@ components:
|
|||||||
- description
|
- description
|
||||||
- id
|
- id
|
||||||
- next_run
|
- next_run
|
||||||
|
- rel_obj_app_label
|
||||||
|
- rel_obj_model
|
||||||
- uid
|
- uid
|
||||||
ScheduleRequest:
|
ScheduleRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
rel_obj_id:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
minLength: 1
|
||||||
crontab:
|
crontab:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
@ -59849,9 +59909,13 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: Task last modified time
|
description: Task last modified time
|
||||||
rel_obj_content_type:
|
rel_obj_app_label:
|
||||||
type: integer
|
type: string
|
||||||
nullable: true
|
readOnly: true
|
||||||
|
rel_obj_model:
|
||||||
|
type: string
|
||||||
|
title: Python model class name
|
||||||
|
readOnly: true
|
||||||
rel_obj_id:
|
rel_obj_id:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
@ -59868,6 +59932,8 @@ components:
|
|||||||
- actor_name
|
- actor_name
|
||||||
- aggregated_status
|
- aggregated_status
|
||||||
- messages
|
- messages
|
||||||
|
- rel_obj_app_label
|
||||||
|
- rel_obj_model
|
||||||
- uid
|
- uid
|
||||||
Tenant:
|
Tenant:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@ -16,10 +16,10 @@ export const ROUTES: Route[] = [
|
|||||||
await import("@goauthentik/admin/admin-overview/DashboardUserPage");
|
await import("@goauthentik/admin/admin-overview/DashboardUserPage");
|
||||||
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
|
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
|
||||||
}),
|
}),
|
||||||
// new Route(new RegExp("^/administration/system-tasks$"), async () => {
|
new Route(new RegExp("^/administration/system-tasks$"), async () => {
|
||||||
// await import("@goauthentik/admin/system-tasks/SystemTaskListPage");
|
await import("@goauthentik/admin/system-tasks/SystemTasksPage");
|
||||||
// return html`<ak-system-task-list></ak-system-task-list>`;
|
return html`<ak-system-tasks></ak-system-tasks>`;
|
||||||
// }),
|
}),
|
||||||
new Route(new RegExp("^/core/providers$"), async () => {
|
new Route(new RegExp("^/core/providers$"), async () => {
|
||||||
await import("@goauthentik/admin/providers/ProviderListPage");
|
await import("@goauthentik/admin/providers/ProviderListPage");
|
||||||
return html`<ak-provider-list></ak-provider-list>`;
|
return html`<ak-provider-list></ak-provider-list>`;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import { formatElapsedTime } from "#common/temporal";
|
||||||
import "@goauthentik/admin/system-tasks/ScheduleForm";
|
import "@goauthentik/admin/system-tasks/ScheduleForm";
|
||||||
import "@goauthentik/admin/system-tasks/TaskList";
|
import "@goauthentik/admin/system-tasks/TaskList";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
@ -31,16 +31,42 @@ export class ScheduleList extends Table<Schedule> {
|
|||||||
@property()
|
@property()
|
||||||
order = "next_run";
|
order = "next_run";
|
||||||
|
|
||||||
|
@property()
|
||||||
|
relObjAppLabel?: string;
|
||||||
|
@property()
|
||||||
|
relObjModel?: string;
|
||||||
|
@property()
|
||||||
|
relObjId?: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
showOnlyStandalone: boolean = true;
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return super.styles.concat(PFDescriptionList);
|
return super.styles.concat(PFDescriptionList);
|
||||||
}
|
}
|
||||||
|
|
||||||
async apiEndpoint(): Promise<PaginatedResponse<Schedule>> {
|
async apiEndpoint(): Promise<PaginatedResponse<Schedule>> {
|
||||||
|
const relObjIdIsnull =
|
||||||
|
typeof this.relObjId !== "undefined"
|
||||||
|
? undefined
|
||||||
|
: this.showOnlyStandalone
|
||||||
|
? true
|
||||||
|
: undefined;
|
||||||
return new TasksApi(DEFAULT_CONFIG).tasksSchedulesList({
|
return new TasksApi(DEFAULT_CONFIG).tasksSchedulesList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
|
relObjContentTypeAppLabel: this.relObjAppLabel,
|
||||||
|
relObjContentTypeModel: this.relObjModel,
|
||||||
|
relObjId: this.relObjId,
|
||||||
|
relObjIdIsnull,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggleShowOnlyStandalone = () => {
|
||||||
|
this.showOnlyStandalone = !this.showOnlyStandalone;
|
||||||
|
this.page = 1;
|
||||||
|
return this.fetch();
|
||||||
|
};
|
||||||
|
|
||||||
columns(): TableColumn[] {
|
columns(): TableColumn[] {
|
||||||
return [
|
return [
|
||||||
new TableColumn(msg("Schedule"), "actor_name"),
|
new TableColumn(msg("Schedule"), "actor_name"),
|
||||||
@ -50,6 +76,35 @@ export class ScheduleList extends Table<Schedule> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderToolbarAfter(): TemplateResult {
|
||||||
|
if (this.relObjId !== undefined) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div class="pf-c-toolbar__group pf-m-filter-group">
|
||||||
|
<div class="pf-c-toolbar__item pf-m-search-filter">
|
||||||
|
<div class="pf-c-input-group">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${this.showOnlyStandalone}
|
||||||
|
@change=${this.#toggleShowOnlyStandalone}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria - hidden="true"> </i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label">
|
||||||
|
${msg("Show only standalone schedules")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
row(item: Schedule): TemplateResult[] {
|
row(item: Schedule): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`<div>${item.description}</div>
|
html`<div>${item.description}</div>
|
||||||
@ -59,7 +114,7 @@ export class ScheduleList extends Table<Schedule> {
|
|||||||
${item.paused
|
${item.paused
|
||||||
? html`Paused`
|
? html`Paused`
|
||||||
: html`
|
: html`
|
||||||
<div>${getRelativeTime(item.nextRun)}</div>
|
<div>${formatElapsedTime(item.nextRun)}</div>
|
||||||
<small>${item.nextRun.toLocaleString()}</small>
|
<small>${item.nextRun.toLocaleString()}</small>
|
||||||
`}
|
`}
|
||||||
`,
|
`,
|
||||||
@ -101,7 +156,11 @@ export class ScheduleList extends Table<Schedule> {
|
|||||||
return html` <td role="cell" colspan="3">
|
return html` <td role="cell" colspan="3">
|
||||||
<div class="pf-c-table__expandable-row-content">
|
<div class="pf-c-table__expandable-row-content">
|
||||||
<div class="pf-c-content">
|
<div class="pf-c-content">
|
||||||
<ak-task-list .schedule=${item}></ak-task-list>
|
<ak-task-list
|
||||||
|
.relObjAppLabel="authentik_tasks_schedules"
|
||||||
|
.relObjModel="schedule"
|
||||||
|
.relObjId="${item.id}"
|
||||||
|
></ak-task-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>`;
|
</td>`;
|
||||||
|
|||||||
@ -1,163 +0,0 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
|
||||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
|
||||||
import "@goauthentik/elements/events/LogViewer";
|
|
||||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
|
||||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
|
||||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
|
||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators.js";
|
|
||||||
|
|
||||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
|
||||||
|
|
||||||
import { EventsApi, SystemTask, SystemTaskStatusEnum } from "@goauthentik/api";
|
|
||||||
|
|
||||||
@customElement("ak-system-task-list")
|
|
||||||
export class SystemTaskListPage extends TablePage<SystemTask> {
|
|
||||||
pageTitle(): string {
|
|
||||||
return msg("System Tasks");
|
|
||||||
}
|
|
||||||
pageDescription(): string {
|
|
||||||
return msg("Long-running operations which authentik executes in the background.");
|
|
||||||
}
|
|
||||||
pageIcon(): string {
|
|
||||||
return "pf-icon pf-icon-automation";
|
|
||||||
}
|
|
||||||
|
|
||||||
expandable = true;
|
|
||||||
|
|
||||||
searchEnabled(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property()
|
|
||||||
order = "name";
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return super.styles.concat(PFDescriptionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
async apiEndpoint(): Promise<PaginatedResponse<SystemTask>> {
|
|
||||||
return new EventsApi(DEFAULT_CONFIG).eventsSystemTasksList(
|
|
||||||
await this.defaultEndpointConfig(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
columns(): TableColumn[] {
|
|
||||||
return [
|
|
||||||
new TableColumn(msg("Identifier"), "name"),
|
|
||||||
new TableColumn(msg("Description")),
|
|
||||||
new TableColumn(msg("Last run")),
|
|
||||||
new TableColumn(msg("Status"), "status"),
|
|
||||||
new TableColumn(msg("Actions")),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
taskStatus(task: SystemTask): TemplateResult {
|
|
||||||
switch (task.status) {
|
|
||||||
case SystemTaskStatusEnum.Successful:
|
|
||||||
return html`<ak-label color=${PFColor.Green}>${msg("Successful")}</ak-label>`;
|
|
||||||
case SystemTaskStatusEnum.Warning:
|
|
||||||
return html`<ak-label color=${PFColor.Orange}>${msg("Warning")}</ak-label>`;
|
|
||||||
case SystemTaskStatusEnum.Error:
|
|
||||||
return html`<ak-label color=${PFColor.Red}>${msg("Error")}</ak-label>`;
|
|
||||||
default:
|
|
||||||
return html`<ak-label color=${PFColor.Grey}>${msg("Unknown")}</ak-label>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderExpanded(item: SystemTask): TemplateResult {
|
|
||||||
return html` <td role="cell" colspan="3">
|
|
||||||
<div class="pf-c-table__expandable-row-content">
|
|
||||||
<dl class="pf-c-description-list pf-m-horizontal">
|
|
||||||
<div class="pf-c-description-list__group">
|
|
||||||
<dt class="pf-c-description-list__term">
|
|
||||||
<span class="pf-c-description-list__text">${msg("Duration")}</span>
|
|
||||||
</dt>
|
|
||||||
<dd class="pf-c-description-list__description">
|
|
||||||
<div class="pf-c-description-list__text">
|
|
||||||
${msg(str`${item.duration.toFixed(2)} seconds`)}
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-description-list__group">
|
|
||||||
<dt class="pf-c-description-list__term">
|
|
||||||
<span class="pf-c-description-list__text">${msg("Expiry")}</span>
|
|
||||||
</dt>
|
|
||||||
<dd class="pf-c-description-list__description">
|
|
||||||
<div class="pf-c-description-list__text">
|
|
||||||
${item.expiring
|
|
||||||
? html`
|
|
||||||
<pf-tooltip
|
|
||||||
position="top"
|
|
||||||
content=${(
|
|
||||||
item.expires || new Date()
|
|
||||||
).toLocaleString()}
|
|
||||||
>
|
|
||||||
${formatElapsedTime(item.expires || new Date())}
|
|
||||||
</pf-tooltip>
|
|
||||||
`
|
|
||||||
: msg("-")}
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-description-list__group">
|
|
||||||
<dt class="pf-c-description-list__term">
|
|
||||||
<span class="pf-c-description-list__text">${msg("Messages")}</span>
|
|
||||||
</dt>
|
|
||||||
<dd class="pf-c-description-list__description">
|
|
||||||
<div class="pf-c-description-list__text">
|
|
||||||
<ak-log-viewer .logs=${item?.messages}></ak-log-viewer>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
row(item: SystemTask): TemplateResult[] {
|
|
||||||
return [
|
|
||||||
html`<pre>${item.name}${item.uid ? `:${item.uid}` : ""}</pre>`,
|
|
||||||
html`${item.description}`,
|
|
||||||
html`<div>${formatElapsedTime(item.finishTimestamp)}</div>
|
|
||||||
<small>${item.finishTimestamp.toLocaleString()}</small>`,
|
|
||||||
this.taskStatus(item),
|
|
||||||
html`<ak-action-button
|
|
||||||
class="pf-m-plain"
|
|
||||||
.apiRequest=${() => {
|
|
||||||
return new EventsApi(DEFAULT_CONFIG)
|
|
||||||
.eventsSystemTasksRunCreate({
|
|
||||||
uuid: item.uuid,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EVENT_REFRESH, {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pf-tooltip position="top" content=${msg("Restart task")}>
|
|
||||||
<i class="fas fa-redo" aria-hidden="true"></i>
|
|
||||||
</pf-tooltip>
|
|
||||||
</ak-action-button>`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ak-system-task-list": SystemTaskListPage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -62,7 +62,7 @@ export class SystemTasksPage extends AKElement {
|
|||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
slot="page-tasks"
|
slot="page-tasks"
|
||||||
data-tab-title="${msg("One-off tasks")}"
|
data-tab-title="${msg("Tasks")}"
|
||||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||||
>
|
>
|
||||||
<div class="pf-l-grid pf-m-gutter">
|
<div class="pf-l-grid pf-m-gutter">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
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 { getRelativeTime } from "@goauthentik/common/utils";
|
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/events/LogViewer";
|
import "@goauthentik/elements/events/LogViewer";
|
||||||
@ -16,7 +16,7 @@ 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 { Schedule, Task, TasksApi, TasksTasksListStateEnum } from "@goauthentik/api";
|
import { Task, TasksApi, TasksTasksListStateEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-task-list")
|
@customElement("ak-task-list")
|
||||||
export class TaskList extends Table<Task> {
|
export class TaskList extends Table<Task> {
|
||||||
@ -24,7 +24,14 @@ export class TaskList extends Table<Task> {
|
|||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
schedule: Schedule | undefined;
|
relObjAppLabel?: string;
|
||||||
|
@property()
|
||||||
|
relObjModel?: string;
|
||||||
|
@property()
|
||||||
|
relObjId?: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
showOnlyStandalone: boolean = true;
|
||||||
|
|
||||||
searchEnabled(): boolean {
|
searchEnabled(): boolean {
|
||||||
return true;
|
return true;
|
||||||
@ -38,14 +45,27 @@ export class TaskList extends Table<Task> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async apiEndpoint(): Promise<PaginatedResponse<Task>> {
|
async apiEndpoint(): Promise<PaginatedResponse<Task>> {
|
||||||
const excludeScheduled = this.schedule === undefined;
|
const relObjIdIsnull =
|
||||||
|
typeof this.relObjId !== "undefined"
|
||||||
|
? undefined
|
||||||
|
: this.showOnlyStandalone
|
||||||
|
? true
|
||||||
|
: undefined;
|
||||||
return new TasksApi(DEFAULT_CONFIG).tasksTasksList({
|
return new TasksApi(DEFAULT_CONFIG).tasksTasksList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
excludeScheduled: excludeScheduled,
|
relObjContentTypeAppLabel: this.relObjAppLabel,
|
||||||
scheduleUid: this.schedule?.uid,
|
relObjContentTypeModel: this.relObjModel,
|
||||||
|
relObjId: this.relObjId,
|
||||||
|
relObjIdIsnull,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggleShowOnlyStandalone = () => {
|
||||||
|
this.showOnlyStandalone = !this.showOnlyStandalone;
|
||||||
|
this.page = 1;
|
||||||
|
return this.fetch();
|
||||||
|
};
|
||||||
|
|
||||||
columns(): TableColumn[] {
|
columns(): TableColumn[] {
|
||||||
return [
|
return [
|
||||||
new TableColumn(msg("Task"), "actor_name"),
|
new TableColumn(msg("Task"), "actor_name"),
|
||||||
@ -56,6 +76,37 @@ export class TaskList extends Table<Task> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderToolbarAfter(): TemplateResult {
|
||||||
|
console.log("task show standalone");
|
||||||
|
console.log(this.showOnlyStandalone);
|
||||||
|
if (this.relObjId !== undefined) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div class="pf-c-toolbar__group pf-m-filter-group">
|
||||||
|
<div class="pf-c-toolbar__item pf-m-search-filter">
|
||||||
|
<div class="pf-c-input-group">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${this.showOnlyStandalone}
|
||||||
|
@change=${this.#toggleShowOnlyStandalone}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria - hidden="true"> </i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label">
|
||||||
|
${msg("Show only standalone tasks")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
taskState(task: Task): TemplateResult {
|
taskState(task: Task): TemplateResult {
|
||||||
switch (task.state) {
|
switch (task.state) {
|
||||||
case TasksTasksListStateEnum.Queued:
|
case TasksTasksListStateEnum.Queued:
|
||||||
@ -74,9 +125,9 @@ export class TaskList extends Table<Task> {
|
|||||||
row(item: Task): TemplateResult[] {
|
row(item: Task): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`<div>${item.actorName}</div>
|
html`<div>${item.actorName}</div>
|
||||||
<small>${item.uid}</small>`,
|
<small>${item.uid.replace(new RegExp("^authentik."), "")}</small>`,
|
||||||
html`${item.queueName}`,
|
html`${item.queueName}`,
|
||||||
html`<div>${getRelativeTime(item.mtime)}</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``,
|
html``,
|
||||||
|
|||||||
Reference in New Issue
Block a user