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:
Developer
2026-06-26 19:44:35 +00:00
parent 6a1f8bbd59
commit 8f4e8428f0
9 changed files with 558 additions and 20 deletions
+65
View File
@@ -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`,
);
}