Authentik Users + Messaging tabs + message endpoint (Slice 8)
Replace the UsersTab and MessagingTab stubs on the Authentik service
page, built new against the Authentik directory endpoint (the old
Jellyfin-backed Users page was deleted in slice 3).
Backend message endpoint (Option A -- implemented):
- POST /api/services/authentik/{service_id}/message accepts
{recipient_emails, subject, html_body}, validates SMTP, enqueues via
the existing mail_queue. Returns {status, request_id, recipient_count}
on success or {status: 'error', error} on failure (200, matching the
directory endpoint's graceful-error pattern).
- GET /api/services/authentik/{service_id}/message/status proxies
mail_queue.status().
UsersTab: paginated (25/page), searchable directory table sourced from
GET /api/services/authentik/{id}/users. Columns: name, username, email,
status (is_active badge). Graceful error Alert on endpoint error.
MessagingTab: minimal but functional compose -- recipient search +
toggle buttons (Authentik users with emails), subject, HTML body
textarea (default template), send wired to the new endpoint, result
Alert. Rich-text toolbar, attachment upload, and queue-status banner
are follow-ups (the old compose UI had them; this slice ships the core
send flow).
New: api/authentik.ts, hooks/useAuthentik.ts (useAuthentikUsers +
useAuthentikMessageStatus), UsersTab + MessagingTab + tests. stubs.tsx
loses both stubs; index.ts wires the real components.
Tests: UsersTab (renders users + error state), MessagingTab (renders
compose form). 100 frontend tests pass (+4); 271 backend tests pass
(no regression); lint/build green both sides.
Refs openspec/changes/services-as-hub-ia/ (spec R6.2/R7.2/R7.3, tasks
slice 8).
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/** API client for the Authentik service (directory + messaging). */
|
||||
import { get, post } from "./shared";
|
||||
|
||||
export interface AuthentikUser {
|
||||
pk: number;
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
is_active: boolean;
|
||||
avatar: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface AuthentikUsersResponse {
|
||||
items: AuthentikUser[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function fetchAuthentikUsers(
|
||||
serviceId: string,
|
||||
params: { search?: string; page?: number; page_size?: number },
|
||||
): Promise<AuthentikUsersResponse> {
|
||||
return get<AuthentikUsersResponse>(
|
||||
`/api/services/authentik/${serviceId}/users`,
|
||||
{
|
||||
search: params.search ?? "",
|
||||
page: String(params.page ?? 1),
|
||||
page_size: String(params.page_size ?? 50),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export interface AuthentikMessageInput {
|
||||
recipient_emails: string[];
|
||||
subject: string;
|
||||
html_body: string;
|
||||
}
|
||||
|
||||
export interface AuthentikMessageResponse {
|
||||
status: string;
|
||||
request_id?: string;
|
||||
recipient_count?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function sendAuthentikMessage(
|
||||
serviceId: string,
|
||||
input: AuthentikMessageInput,
|
||||
): Promise<AuthentikMessageResponse> {
|
||||
return post<AuthentikMessageResponse>(
|
||||
`/api/services/authentik/${serviceId}/message`,
|
||||
input,
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchAuthentikMessageStatus(
|
||||
serviceId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
return get<Record<string, unknown>>(
|
||||
`/api/services/authentik/${serviceId}/message/status`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user