import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { supabase } from '@/lib/supabaseClient'; import { queryKeys } from '@/lib/queryKeys'; import { toast } from 'sonner'; export interface Incident { id: string; incident_number: string; title: string; description: string; severity: 'critical' | 'high' | 'medium' | 'low'; status: 'open' | 'investigating' | 'resolved' | 'closed'; correlation_rule_id?: string; detected_at: string; acknowledged_at?: string; acknowledged_by?: string; resolved_at?: string; resolved_by?: string; resolution_notes?: string; alert_count: number; created_at: string; updated_at: string; } export function useIncidents(status?: 'open' | 'investigating' | 'resolved' | 'closed') { return useQuery({ queryKey: queryKeys.monitoring.incidents(status), queryFn: async () => { let query = supabase .from('incidents') .select('*') .order('detected_at', { ascending: false }); if (status) { query = query.eq('status', status); } const { data, error } = await query; if (error) throw error; return (data || []) as Incident[]; }, staleTime: 15000, refetchInterval: 30000, }); } export function useCreateIncident() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ ruleId, title, description, severity, alertIds, alertSources, }: { ruleId?: string; title: string; description?: string; severity: 'critical' | 'high' | 'medium' | 'low'; alertIds: string[]; alertSources: ('system' | 'rate_limit')[]; }) => { // Create the incident (incident_number is auto-generated by trigger) const { data: incident, error: incidentError } = await supabase .from('incidents') .insert([{ title, description, severity, correlation_rule_id: ruleId, status: 'open' as const, } as any]) .select() .single(); if (incidentError) throw incidentError; // Link alerts to the incident const incidentAlerts = alertIds.map((alertId, index) => ({ incident_id: incident.id, alert_source: alertSources[index] || 'system', alert_id: alertId, })); const { error: linkError } = await supabase .from('incident_alerts') .insert(incidentAlerts); if (linkError) throw linkError; // Log to audit trail const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); await logAdminAction('incident_created', { incident_id: incident.id, incident_number: incident.incident_number, title: title, severity: severity, alert_count: alertIds.length, correlation_rule_id: ruleId, }); return incident as Incident; }, onSuccess: (incident) => { queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.incidents() }); queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.correlatedAlerts() }); toast.success(`Incident ${incident.incident_number} created`); }, onError: (error) => { console.error('Failed to create incident:', error); toast.error('Failed to create incident'); }, }); } export function useAcknowledgeIncident() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (incidentId: string) => { const { data, error } = await supabase .from('incidents') .update({ status: 'investigating', acknowledged_at: new Date().toISOString(), acknowledged_by: (await supabase.auth.getUser()).data.user?.id, }) .eq('id', incidentId) .select() .single(); if (error) throw error; // Log to audit trail const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); await logAdminAction('incident_acknowledged', { incident_id: incidentId, incident_number: data.incident_number, severity: data.severity, status_change: 'open -> investigating', }); return data as Incident; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.incidents() }); toast.success('Incident acknowledged'); }, onError: (error) => { console.error('Failed to acknowledge incident:', error); toast.error('Failed to acknowledge incident'); }, }); } export function useResolveIncident() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ incidentId, resolutionNotes, resolveAlerts = true, }: { incidentId: string; resolutionNotes?: string; resolveAlerts?: boolean; }) => { const userId = (await supabase.auth.getUser()).data.user?.id; // Fetch incident details before resolving const { data: incident } = await supabase .from('incidents') .select('incident_number, severity, alert_count') .eq('id', incidentId) .single(); // Update incident const { error: incidentError } = await supabase .from('incidents') .update({ status: 'resolved', resolved_at: new Date().toISOString(), resolved_by: userId, resolution_notes: resolutionNotes, }) .eq('id', incidentId); if (incidentError) throw incidentError; // Log to audit trail const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); await logAdminAction('incident_resolved', { incident_id: incidentId, incident_number: incident?.incident_number, severity: incident?.severity, alert_count: incident?.alert_count, resolution_notes: resolutionNotes, resolved_linked_alerts: resolveAlerts, }); // Optionally resolve all linked alerts if (resolveAlerts) { const { data: linkedAlerts } = await supabase .from('incident_alerts') .select('alert_source, alert_id') .eq('incident_id', incidentId); if (linkedAlerts) { for (const alert of linkedAlerts) { const table = alert.alert_source === 'system' ? 'system_alerts' : 'rate_limit_alerts'; await supabase .from(table) .update({ resolved_at: new Date().toISOString() }) .eq('id', alert.alert_id); } } } return { incidentId }; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.incidents() }); queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.groupedAlerts() }); queryClient.invalidateQueries({ queryKey: queryKeys.monitoring.combinedAlerts() }); toast.success('Incident resolved'); }, onError: (error) => { console.error('Failed to resolve incident:', error); toast.error('Failed to resolve incident'); }, }); }