schedule form and helpers
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
@ -1,15 +1,21 @@
|
||||
from dramatiq.actor import Actor
|
||||
from dramatiq.broker import get_broker
|
||||
from dramatiq.errors import ActorNotFound
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.mixins import (
|
||||
ListModelMixin,
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
)
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import SerializerMethodField
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.tasks.schedules.models import Schedule
|
||||
|
||||
|
||||
@ -23,6 +29,7 @@ class ScheduleSerializer(ModelSerializer):
|
||||
"uid",
|
||||
"actor_name",
|
||||
"crontab",
|
||||
"paused",
|
||||
"next_run",
|
||||
"description",
|
||||
]
|
||||
@ -52,3 +59,12 @@ class ScheduleViewSet(
|
||||
"uid",
|
||||
)
|
||||
ordering = ("next_run", "uid")
|
||||
|
||||
@permission_required("authentik_tasks_schedules.send_schedule")
|
||||
@extend_schema(request=OpenApiTypes.NONE)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
|
||||
def send(self, request: Request, pk=None) -> Response:
|
||||
"""Trigger this schedule now"""
|
||||
schedule: Schedule = self.get_object()
|
||||
schedule.send()
|
||||
return Response({})
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.13 on 2025-04-03 14:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tasks_schedules", "0003_schedule_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="schedule",
|
||||
options={
|
||||
"default_permissions": ("change", "view"),
|
||||
"permissions": [("send_schedule", "Manually trigger a schedule")],
|
||||
"verbose_name": "Schedule",
|
||||
"verbose_name_plural": "Schedules",
|
||||
},
|
||||
),
|
||||
]
|
||||
18
authentik/tasks/schedules/migrations/0005_schedule_paused.py
Normal file
18
authentik/tasks/schedules/migrations/0005_schedule_paused.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.13 on 2025-04-03 14:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tasks_schedules", "0004_alter_schedule_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="schedule",
|
||||
name="paused",
|
||||
field=models.BooleanField(default=False, help_text="Pause this schedule"),
|
||||
),
|
||||
]
|
||||
@ -40,6 +40,7 @@ class Schedule(SerializerModel):
|
||||
rel_obj = GenericForeignKey("rel_obj_content_type", "rel_obj_id")
|
||||
|
||||
crontab = models.TextField(validators=[validate_crontab], help_text=_("When to schedule tasks"))
|
||||
paused = models.BooleanField(default=False, help_text=_("Pause this schedule"))
|
||||
|
||||
next_run = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
|
||||
@ -50,6 +51,9 @@ class Schedule(SerializerModel):
|
||||
"change",
|
||||
"view",
|
||||
)
|
||||
permissions = [
|
||||
("send_schedule", _("Manually trigger a schedule")),
|
||||
]
|
||||
indexes = (models.Index(fields=("rel_obj_content_type", "rel_obj_id")),)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@ -40,7 +40,9 @@ class Scheduler:
|
||||
tenant=tenant.schema_name,
|
||||
)
|
||||
with transaction.atomic(using=router.db_for_write(Schedule)):
|
||||
for schedule in Schedule.objects.select_for_update().filter(next_run__lt=now()):
|
||||
for schedule in Schedule.objects.select_for_update().filter(
|
||||
next_run__lt=now(), paused=False
|
||||
):
|
||||
self.process_schedule(schedule)
|
||||
|
||||
def run(self):
|
||||
|
||||
@ -7754,6 +7754,7 @@
|
||||
"authentik_stages_user_write.view_userwritestage",
|
||||
"authentik_tasks.view_task",
|
||||
"authentik_tasks_schedules.change_schedule",
|
||||
"authentik_tasks_schedules.send_schedule",
|
||||
"authentik_tasks_schedules.view_schedule",
|
||||
"authentik_tenants.add_domain",
|
||||
"authentik_tenants.add_tenant",
|
||||
@ -14268,6 +14269,7 @@
|
||||
"authentik_stages_user_write.view_userwritestage",
|
||||
"authentik_tasks.view_task",
|
||||
"authentik_tasks_schedules.change_schedule",
|
||||
"authentik_tasks_schedules.send_schedule",
|
||||
"authentik_tasks_schedules.view_schedule",
|
||||
"authentik_tenants.add_domain",
|
||||
"authentik_tenants.add_tenant",
|
||||
@ -15218,6 +15220,11 @@
|
||||
"minLength": 1,
|
||||
"title": "Crontab",
|
||||
"description": "When to schedule tasks"
|
||||
},
|
||||
"paused": {
|
||||
"type": "boolean",
|
||||
"title": "Paused",
|
||||
"description": "Pause this schedule"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@ -15233,6 +15240,7 @@
|
||||
"permission": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"send_schedule",
|
||||
"change_schedule",
|
||||
"view_schedule"
|
||||
]
|
||||
|
||||
44
schema.yml
44
schema.yml
@ -39096,6 +39096,41 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/tasks/schedules/{id}/send/:
|
||||
post:
|
||||
operationId: tasks_schedules_send_create
|
||||
description: Trigger this schedule now
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Schedule.
|
||||
required: true
|
||||
tags:
|
||||
- tasks
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/tasks/tasks/:
|
||||
get:
|
||||
operationId: tasks_tasks_list
|
||||
@ -52931,6 +52966,9 @@ components:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: When to schedule tasks
|
||||
paused:
|
||||
type: boolean
|
||||
description: Pause this schedule
|
||||
PatchedScopeMappingRequest:
|
||||
type: object
|
||||
description: ScopeMapping Serializer
|
||||
@ -56807,6 +56845,9 @@ components:
|
||||
crontab:
|
||||
type: string
|
||||
description: When to schedule tasks
|
||||
paused:
|
||||
type: boolean
|
||||
description: Pause this schedule
|
||||
next_run:
|
||||
type: string
|
||||
format: date-time
|
||||
@ -56829,6 +56870,9 @@ components:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: When to schedule tasks
|
||||
paused:
|
||||
type: boolean
|
||||
description: Pause this schedule
|
||||
required:
|
||||
- crontab
|
||||
ScopeMapping:
|
||||
|
||||
66
web/src/admin/system-tasks/ScheduleForm.ts
Normal file
66
web/src/admin/system-tasks/ScheduleForm.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { Schedule, TasksApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-schedule-form")
|
||||
export class ScheduleForm extends ModelForm<Schedule, string> {
|
||||
async loadInstance(pk: string): Promise<Schedule> {
|
||||
return await new TasksApi(DEFAULT_CONFIG).tasksSchedulesRetrieve({
|
||||
id: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (!this.instance) {
|
||||
return "";
|
||||
}
|
||||
return msg("Successfully updated schedule.");
|
||||
}
|
||||
|
||||
async send(data: Schedule): Promise<Schedule | void> {
|
||||
if (!this.instance) {
|
||||
return;
|
||||
}
|
||||
return await new TasksApi(DEFAULT_CONFIG).tasksSchedulesUpdate({
|
||||
id: this.instance.id,
|
||||
scheduleRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-text-input
|
||||
name="crontab"
|
||||
value=${ifDefined(this.instance?.crontab)}
|
||||
label=${msg("Crontab")}
|
||||
required
|
||||
help=${msg("Crontab")}
|
||||
></ak-text-input>
|
||||
<ak-switch-input
|
||||
name="paused"
|
||||
label=${msg("Paused")}
|
||||
?checked=${this.instance?.paused ?? false}
|
||||
help=${msg("Pause this schedule")}
|
||||
>
|
||||
</ak-switch-input>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-schedule-form": ScheduleForm;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||
import "@goauthentik/admin/providers/rac/EndpointForm";
|
||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||
import "@goauthentik/admin/system-tasks/ScheduleForm";
|
||||
import "@goauthentik/admin/system-tasks/TaskList";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
@ -55,9 +55,45 @@ export class ScheduleList extends Table<Schedule> {
|
||||
html`<div>${item.description}</div>
|
||||
<small>${item.uid.replace(new RegExp("^authentik."), "")}</small>`,
|
||||
html`${item.crontab}`,
|
||||
html`<div>${getRelativeTime(item.nextRun)}</div>
|
||||
<small>${item.nextRun.toLocaleString()}</small>`,
|
||||
html``,
|
||||
html`
|
||||
${item.paused
|
||||
? html`Paused`
|
||||
: html`
|
||||
<div>${getRelativeTime(item.nextRun)}</div>
|
||||
<small>${item.nextRun.toLocaleString()}</small>
|
||||
`}
|
||||
`,
|
||||
html`<ak-action-button
|
||||
class="pf-m-plain"
|
||||
.apiRequest=${() => {
|
||||
return new TasksApi(DEFAULT_CONFIG)
|
||||
.tasksSchedulesSendCreate({
|
||||
id: item.id,
|
||||
})
|
||||
.then(() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_REFRESH, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
</ak-action-button>
|
||||
<pf-tooltip position="top" content=${msg("Run scheduled task now")}>
|
||||
<i class="fas fa-play" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit"> ${msg("Update")} </span>
|
||||
<span slot="header"> ${msg("Update Schedule")} </span>
|
||||
<ak-schedule-form slot="form" .instancePk=${item.id}> </ak-schedule-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal> `,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user