Pulse/frontend-modern/src/api/notifications.ts
2025-11-20 17:54:20 +00:00

252 lines
6.6 KiB
TypeScript

import { apiFetchJSON } from '@/utils/apiClient';
export interface EmailProvider {
id?: string;
name: string;
smtpHost: string;
smtpPort: number;
tls: boolean;
startTLS: boolean;
authRequired: boolean;
instructions: string;
server?: string;
port?: number;
security?: 'none' | 'tls' | 'starttls';
}
export interface WebhookTemplate {
id?: string;
service: string;
name: string;
urlPattern: string;
method: string;
headers: Record<string, string>;
payloadTemplate: string;
instructions: string;
description?: string;
template?: {
url?: string;
method?: string;
headers?: Record<string, string>;
body?: string;
};
}
export interface EmailConfig {
enabled: boolean;
provider: string;
server: string;
port: number;
username: string;
password?: string;
from: string;
to: string[];
tls: boolean;
startTLS: boolean;
}
export interface Webhook {
id: string;
name: string;
url: string;
method: string;
headers: Record<string, string>;
template?: string;
enabled: boolean;
service?: string; // Added to support Discord, Slack, etc.
customFields?: Record<string, string>;
}
export interface AppriseConfig {
enabled: boolean;
mode?: 'cli' | 'http';
targets?: string[];
cliPath?: string;
timeoutSeconds?: number;
serverUrl?: string;
configKey?: string;
apiKey?: string;
apiKeyHeader?: string;
skipTlsVerify?: boolean;
}
export interface NotificationTestRequest {
type: 'email' | 'webhook' | 'apprise';
config?: Record<string, unknown> | AppriseConfig; // Backend expects different format than frontend types
webhookId?: string;
}
export class NotificationsAPI {
private static baseUrl = '/api/notifications';
static async getAppriseConfig(): Promise<AppriseConfig> {
return apiFetchJSON(`${this.baseUrl}/apprise`);
}
static async updateAppriseConfig(config: AppriseConfig): Promise<AppriseConfig> {
return apiFetchJSON(`${this.baseUrl}/apprise`, {
method: 'PUT',
body: JSON.stringify(config),
});
}
// Email configuration
static async getEmailConfig(): Promise<EmailConfig> {
const backendConfig = await apiFetchJSON<Record<string, unknown>>(`${this.baseUrl}/email`);
// Backend already returns fields with correct names (server, port)
return {
enabled: (backendConfig.enabled as boolean) || false,
provider: (backendConfig.provider as string) || '',
server: (backendConfig.server as string) || '',
port: (backendConfig.port as number) || 587,
username: (backendConfig.username as string) || '',
password: (backendConfig.password as string) || '',
from: (backendConfig.from as string) || '',
to: (backendConfig.to as string[]) || [],
tls: (backendConfig.tls as boolean) || false,
startTLS: (backendConfig.startTLS as boolean) || false,
};
}
static async updateEmailConfig(config: EmailConfig): Promise<{ success: boolean }> {
// Backend expects fields with these names (server, port)
const backendConfig = {
enabled: config.enabled,
server: config.server,
port: config.port,
username: config.username,
password: config.password,
from: config.from,
to: config.to,
tls: config.tls || false,
startTLS: config.startTLS || false,
provider: config.provider || '',
};
return apiFetchJSON(`${this.baseUrl}/email`, {
method: 'PUT',
body: JSON.stringify(backendConfig),
});
}
// Webhook management
static async getWebhooks(): Promise<Webhook[]> {
const data = await apiFetchJSON<Webhook[] | null>(`${this.baseUrl}/webhooks`);
return Array.isArray(data) ? data : [];
}
static async createWebhook(webhook: Omit<Webhook, 'id'>): Promise<Webhook> {
return apiFetchJSON(`${this.baseUrl}/webhooks`, {
method: 'POST',
body: JSON.stringify(webhook),
});
}
static async updateWebhook(id: string, webhook: Partial<Webhook>): Promise<Webhook> {
return apiFetchJSON(`${this.baseUrl}/webhooks/${id}`, {
method: 'PUT',
body: JSON.stringify(webhook),
});
}
static async deleteWebhook(id: string): Promise<{ success: boolean }> {
return apiFetchJSON(`${this.baseUrl}/webhooks/${id}`, {
method: 'DELETE',
});
}
// Templates and providers
static async getEmailProviders(): Promise<EmailProvider[]> {
return apiFetchJSON(`${this.baseUrl}/email-providers`);
}
static async getWebhookTemplates(): Promise<WebhookTemplate[]> {
return apiFetchJSON(`${this.baseUrl}/webhook-templates`);
}
// Testing
static async testNotification(
request: NotificationTestRequest,
): Promise<{ success: boolean; message?: string }> {
const body: { method: string; config?: Record<string, unknown> | AppriseConfig; webhookId?: string } = {
method: request.type,
};
// Include config if provided for testing without saving
if (request.config) {
body.config = request.config;
}
// Include webhookId for webhook testing
if (request.webhookId) {
body.webhookId = request.webhookId;
}
return apiFetchJSON(`${this.baseUrl}/test`, {
method: 'POST',
body: JSON.stringify(body),
});
}
static async testWebhook(
webhook: Omit<Webhook, 'id'>,
): Promise<{ success: boolean; message?: string }> {
return apiFetchJSON(`${this.baseUrl}/webhooks/test`, {
method: 'POST',
body: JSON.stringify(webhook),
});
}
// Queue and DLQ management
static async getQueueStats(): Promise<Record<string, number>> {
return apiFetchJSON(`${this.baseUrl}/queue/stats`);
}
static async getDLQ(limit = 100): Promise<unknown[]> {
return apiFetchJSON(`${this.baseUrl}/dlq?limit=${limit}`);
}
static async retryDLQItem(id: string): Promise<{ success: boolean }> {
return apiFetchJSON(`${this.baseUrl}/dlq/retry`, {
method: 'POST',
body: JSON.stringify({ id }),
});
}
static async deleteDLQItem(id: string): Promise<{ success: boolean }> {
return apiFetchJSON(`${this.baseUrl}/dlq/delete`, {
method: 'POST',
body: JSON.stringify({ id }),
});
}
static async getNotificationHealth(): Promise<{
queue?: {
healthy: boolean;
pending: number;
sending: number;
sent: number;
failed: number;
dlq: number;
};
email: {
enabled: boolean;
configured: boolean;
};
webhooks: {
total: number;
enabled: number;
};
encryption: {
enabled: boolean;
};
healthy: boolean;
}> {
return apiFetchJSON(`${this.baseUrl}/health`);
}
static async getWebhookHistory(): Promise<unknown[]> {
return apiFetchJSON(`${this.baseUrl}/webhook-history`);
}
}