Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Each user can now store their own personal Tempo API token in their profile settings. Time is logged using each user's own credentials. Backend: - Migration b044: adds tempo_api_token column to users table - User model: adds tempo_api_token column - UserPreferencesUpdate: adds tempo_api_token field (write-only) - UserOut: adds tempo_api_token (excluded) + tempo_token_set bool; @model_validator derives both jira_token_set and tempo_token_set - users router: handles tempo_api_token same as jira_api_token (empty string clears it, never returned in responses) - tempo_service: refactored to per-user token; has_tempo_configured(), get_user_tempo_client(user) use user.tempo_api_token; global TEMPO_ENABLED still acts as kill-switch - system router: /system/tempo-test now uses current user's personal token (any role); removed global TEMPO_API_TOKEN dependency Frontend: - settings.ts: UserPreferencesUpdate.tempo_api_token, UserMeOut.tempo_token_set - SettingsPage ProfileSection: Tempo Integration section with password field, show/hide toggle, configured badge, and Test Tempo button — mirrors the Jira token UX exactly - JiraConfigSection: removed stale global Tempo test block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
203 lines
5.6 KiB
TypeScript
203 lines
5.6 KiB
TypeScript
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;
|
|
jira_api_token?: string | null;
|
|
/** Atlassian email used for Jira auth. Overrides Aegis account email. Empty string clears it. */
|
|
jira_email?: string | null;
|
|
/** Personal Tempo API token. Empty string clears it. */
|
|
tempo_api_token?: 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;
|
|
jira_email: string | null;
|
|
jira_token_set: boolean;
|
|
tempo_token_set: boolean;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Jira system config (admin only)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface JiraConfigOut {
|
|
enabled: boolean;
|
|
url: string;
|
|
project_key: string;
|
|
parent_ticket: string;
|
|
}
|
|
|
|
export interface JiraConfigUpdate {
|
|
enabled?: boolean;
|
|
url?: string;
|
|
project_key?: string;
|
|
parent_ticket?: string;
|
|
}
|
|
|
|
export async function getJiraConfig(): Promise<JiraConfigOut> {
|
|
const { data } = await client.get<JiraConfigOut>("/system/jira-config");
|
|
return data;
|
|
}
|
|
|
|
export async function updateJiraConfig(payload: JiraConfigUpdate): Promise<JiraConfigOut> {
|
|
const { data } = await client.patch<JiraConfigOut>("/system/jira-config", payload);
|
|
return data;
|
|
}
|
|
|
|
export async function testJiraConnection(): Promise<{
|
|
status: "ok" | "error";
|
|
connected_as?: string;
|
|
jira_url?: string;
|
|
message?: string;
|
|
}> {
|
|
const { data } = await client.post("/system/jira-test");
|
|
return data;
|
|
}
|
|
|
|
export async function testTempoConnection(): Promise<{
|
|
status: "ok" | "error" | "disabled";
|
|
message?: string;
|
|
worklogs_found?: number | string;
|
|
}> {
|
|
const { data } = await client.post("/system/tempo-test");
|
|
return data;
|
|
}
|
|
|