feat(settings): Settings page with email, webhooks, notifications, profile [FASE-8]
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- SystemConfig model + migration b033 for runtime key-value config - GET/PATCH /system/email-config + POST /system/email-test (admin only) - email_service reads SMTP config from DB (overrides .env) - Webhooks now accessible to red_lead/blue_lead + admin - GET /users/me already existed; /users/me/preferences already working - SettingsPage with 4 role-aware tabs: * Profile & Jira: jira_account_id, user info * Notifications: role-specific email/in-app toggles (12 prefs) * Webhooks: full CRUD + test ping (leads + admin) * Email/SMTP: enable toggle, server config, test email (admin only) - Added /settings route (all authenticated users) - Settings link added to Sidebar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
147
frontend/src/api/settings.ts
Normal file
147
frontend/src/api/settings.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import client from "./client";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Email / SMTP config (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface EmailConfigOut {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
from_email: string;
|
||||
use_tls: boolean;
|
||||
}
|
||||
|
||||
export interface EmailConfigUpdate {
|
||||
enabled?: boolean;
|
||||
host?: string;
|
||||
port?: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
from_email?: string;
|
||||
use_tls?: boolean;
|
||||
}
|
||||
|
||||
export async function getEmailConfig(): Promise<EmailConfigOut> {
|
||||
const { data } = await client.get<EmailConfigOut>("/system/email-config");
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateEmailConfig(payload: EmailConfigUpdate): Promise<EmailConfigOut> {
|
||||
const { data } = await client.patch<EmailConfigOut>("/system/email-config", payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function sendTestEmail(to: string): Promise<{ detail: string }> {
|
||||
const { data } = await client.post<{ detail: string }>("/system/email-test", { to });
|
||||
return data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Webhook config (admin + leads)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface WebhookOut {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
secret: string | null;
|
||||
events: string[];
|
||||
is_active: boolean;
|
||||
created_at: string | null;
|
||||
last_triggered_at: string | null;
|
||||
failure_count: number;
|
||||
}
|
||||
|
||||
export interface WebhookCreate {
|
||||
name: string;
|
||||
url: string;
|
||||
secret?: string;
|
||||
events: string[];
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate {
|
||||
name?: string;
|
||||
url?: string;
|
||||
secret?: string;
|
||||
events?: string[];
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export async function getWebhooks(): Promise<WebhookOut[]> {
|
||||
const { data } = await client.get<WebhookOut[]>("/webhooks");
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createWebhook(payload: WebhookCreate): Promise<WebhookOut> {
|
||||
const { data } = await client.post<WebhookOut>("/webhooks", payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateWebhook(id: string, payload: WebhookUpdate): Promise<WebhookOut> {
|
||||
const { data } = await client.patch<WebhookOut>(`/webhooks/${id}`, payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteWebhook(id: string): Promise<void> {
|
||||
await client.delete(`/webhooks/${id}`);
|
||||
}
|
||||
|
||||
export async function testWebhook(id: string): Promise<{ detail: string }> {
|
||||
const { data } = await client.post<{ detail: string }>(`/webhooks/${id}/test`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// User preferences (all users)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface NotificationPreferences {
|
||||
// Universal
|
||||
email_on_test_validated: boolean;
|
||||
email_on_test_rejected: boolean;
|
||||
email_on_campaign_completed: boolean;
|
||||
email_on_new_mitre_techniques: boolean;
|
||||
email_on_stale_coverage: boolean;
|
||||
in_app_all: boolean;
|
||||
// Tech + leads
|
||||
email_on_assigned_to_campaign?: boolean;
|
||||
email_on_test_state_change?: boolean;
|
||||
// Leads only
|
||||
email_on_all_team_validations?: boolean;
|
||||
email_on_webhook_failures?: boolean;
|
||||
// Admin only
|
||||
email_on_new_users?: boolean;
|
||||
email_on_system_errors?: boolean;
|
||||
}
|
||||
|
||||
export interface UserPreferencesUpdate {
|
||||
notification_preferences?: Partial<NotificationPreferences>;
|
||||
jira_account_id?: string | null;
|
||||
}
|
||||
|
||||
export interface UserMeOut {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string | null;
|
||||
role: string;
|
||||
is_active: boolean;
|
||||
must_change_password: boolean;
|
||||
created_at: string | null;
|
||||
last_login: string | null;
|
||||
notification_preferences: NotificationPreferences | null;
|
||||
jira_account_id: string | null;
|
||||
}
|
||||
|
||||
export async function getMe(): Promise<UserMeOut> {
|
||||
const { data } = await client.get<UserMeOut>("/users/me");
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateMyPreferences(payload: UserPreferencesUpdate): Promise<UserMeOut> {
|
||||
const { data } = await client.patch<UserMeOut>("/users/me/preferences", payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user