import { apiFetch } from '@/lib/api'; import { toast } from '@/components/ui/toast-store'; import { CATEGORY_LABELS } from '@/lib/notificationCategories'; import type { NotificationCategory, NotificationItem } from '@/components/dashboard/types'; export type MuteRuleLevel = 'info' | 'warning' | 'error'; export type MuteRuleAppliesTo = 'bell' | 'external' | 'both'; export type MuteRuleDraft = { name: string; node_id?: number | null; stack_patterns?: string[]; label_ids?: number[] | null; categories?: NotificationCategory[] | null; levels?: MuteRuleLevel[] | null; applies_to?: MuteRuleAppliesTo; enabled?: boolean; expires_at?: number | null; schedule?: MuteRuleSchedule | null; }; export type MuteRuleSchedule = { days: number[]; start_minute: number; end_minute: number; tz: 'UTC'; }; export const MUTE_RULES_CHANGED_EVENT = 'sencho:mute-rules-changed'; export function emitMuteRulesChanged(): void { window.dispatchEvent(new CustomEvent(MUTE_RULES_CHANGED_EVENT)); } const DEFAULT_BODY = { applies_to: 'both' as MuteRuleAppliesTo, enabled: true, expires_at: null as number | null, schedule: null as MuteRuleSchedule | null, node_id: null as number | null, stack_patterns: [] as string[], label_ids: null as number[] | null, categories: null as NotificationCategory[] | null, levels: null as MuteRuleLevel[] | null, }; export async function createMuteRule(draft: MuteRuleDraft): Promise<{ ok: boolean; error?: string }> { try { const res = await apiFetch('/notification-suppression-rules', { method: 'POST', body: JSON.stringify({ ...DEFAULT_BODY, ...draft }), }); if (res.ok) { emitMuteRulesChanged(); return { ok: true }; } const err = await res.json().catch(() => ({})); return { ok: false, error: (err as { error?: string })?.error || 'Failed to create mute rule.' }; } catch { return { ok: false, error: 'Network error.' }; } } export async function createMuteRuleWithToast( draft: MuteRuleDraft, successMessage = 'Mute rule created. Open Settings ยท Mute Rules to edit it.', ): Promise { const result = await createMuteRule(draft); if (result.ok) { toast.success(successMessage); return true; } toast.error(result.error || 'Failed to create mute rule.'); return false; } export function stackMuteAllDraft(stackName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute ${stackName}`, node_id: nodeId ?? null, stack_patterns: [stackName], }; } export function stackMuteDeploySuccessDraft(stackName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute ${stackName} deploy success`, node_id: nodeId ?? null, stack_patterns: [stackName], categories: ['deploy_success'], }; } export function stackMuteMonitorDraft(stackName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute ${stackName} monitor alerts`, node_id: nodeId ?? null, stack_patterns: [stackName], categories: ['monitor_alert'], }; } export function nodeMuteAllDraft(nodeId: number, nodeName: string): MuteRuleDraft { return { name: `Mute ${nodeName}`, node_id: nodeId, }; } export function nodeMuteUpdatesDraft(nodeId: number, nodeName: string): MuteRuleDraft { return { name: `Mute ${nodeName} update notifications`, node_id: nodeId, categories: ['image_update_available', 'node_update_available', 'update_started'], }; } export function nodeMuteMonitorDraft(nodeId: number, nodeName: string): MuteRuleDraft { return { name: `Mute ${nodeName} monitor alerts`, node_id: nodeId, categories: ['monitor_alert'], }; } export function labelMuteAllDraft(labelId: number, labelName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute label: ${labelName}`, node_id: nodeId ?? null, label_ids: [labelId], applies_to: 'both', }; } export function labelMuteExternalDraft(labelId: number, labelName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute external for label: ${labelName}`, node_id: nodeId ?? null, label_ids: [labelId], applies_to: 'external', }; } export function labelMuteLowPriorityDraft(labelId: number, labelName: string, nodeId?: number | null): MuteRuleDraft { return { name: `Mute low-priority alerts for label: ${labelName}`, node_id: nodeId ?? null, label_ids: [labelId], levels: ['info', 'warning'], }; } export type BellMuteMode = 'category' | 'similar' | 'stack'; export function muteDraftFromNotification( notif: NotificationItem, mode: BellMuteMode, ): MuteRuleDraft | null { const categoryLabel = notif.category ? CATEGORY_LABELS[notif.category as NotificationCategory] : 'alert'; if (mode === 'category' && notif.category) { return { name: `Mute ${categoryLabel}`, node_id: notif.nodeId ?? null, categories: [notif.category as NotificationCategory], }; } if (mode === 'stack' && notif.stack_name) { return { name: `Mute ${notif.stack_name}`, node_id: notif.nodeId ?? null, stack_patterns: [notif.stack_name], }; } if (mode === 'similar') { return { name: 'Mute similar alerts', node_id: notif.nodeId ?? null, categories: notif.category ? [notif.category as NotificationCategory] : null, levels: [notif.level], stack_patterns: notif.stack_name ? [notif.stack_name] : [], }; } return null; } export async function createMuteFromNotification( notif: NotificationItem, mode: BellMuteMode, ): Promise { const draft = muteDraftFromNotification(notif, mode); if (!draft) { toast.error('This notification cannot be muted with that shortcut.'); return false; } return createMuteRuleWithToast(draft); }