restart front

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2025-06-11 17:25:21 +02:00
parent 1a9c529e92
commit 7ef547b357
11 changed files with 317 additions and 194 deletions

View File

@ -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()
)

View File

@ -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(

View File

@ -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",
),
),
),
]

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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>`;

View File

@ -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`&nbsp;
<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>`;

View File

@ -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;
}
}

View File

@ -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">

View File

@ -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`&nbsp;
<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``,