Files
thrilltrack-explorer/src/hooks/useRateLimitAlerts.ts
gpt-engineer-app[bot] a5fed1e26a Implement Phase 2 audit logging
Add audit logging for admin settings changes, rate limit config updates, anomaly detection config changes (skipped due to no UI), and version cleanup settings updates. Implement logging via central logAdminAction helper and integrate into AdminSettings, VersionCleanupSettings, and RateLimitAlerts mutations (create, update, delete).
2025-11-11 14:36:10 +00:00

249 lines
7.0 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
export interface AlertConfig {
id: string;
metric_type: 'block_rate' | 'total_requests' | 'unique_ips' | 'function_specific';
threshold_value: number;
time_window_ms: number;
function_name?: string;
enabled: boolean;
created_at: string;
updated_at: string;
}
export interface Alert {
id: string;
config_id: string;
metric_type: string;
metric_value: number;
threshold_value: number;
time_window_ms: number;
function_name?: string;
alert_message: string;
resolved_at?: string;
created_at: string;
}
export function useAlertConfigs() {
return useQuery({
queryKey: ['rateLimitAlertConfigs'],
queryFn: async () => {
const { data, error } = await supabase
.from('rate_limit_alert_config')
.select('*')
.order('metric_type');
if (error) throw error;
return data as AlertConfig[];
},
});
}
export function useAlertHistory(limit: number = 50) {
return useQuery({
queryKey: ['rateLimitAlerts', limit],
queryFn: async () => {
const { data, error } = await supabase
.from('rate_limit_alerts')
.select('*')
.order('created_at', { ascending: false })
.limit(limit);
if (error) throw error;
return data as Alert[];
},
refetchInterval: 30000, // Refetch every 30 seconds
});
}
export function useUnresolvedAlerts() {
return useQuery({
queryKey: ['rateLimitAlertsUnresolved'],
queryFn: async () => {
const { data, error } = await supabase
.from('rate_limit_alerts')
.select('*')
.is('resolved_at', null)
.order('created_at', { ascending: false });
if (error) throw error;
return data as Alert[];
},
refetchInterval: 15000, // Refetch every 15 seconds
});
}
export function useUpdateAlertConfig() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, updates }: { id: string; updates: Partial<AlertConfig> }) => {
// Fetch old config for audit log
const { data: oldConfig } = await supabase
.from('rate_limit_alert_config')
.select('*')
.eq('id', id)
.single();
const { data, error } = await supabase
.from('rate_limit_alert_config')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) throw error;
return { data, oldConfig };
},
onSuccess: async ({ data, oldConfig }) => {
queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] });
// Log to audit trail
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
await logAdminAction('rate_limit_config_updated', {
config_id: data.id,
metric_type: data.metric_type,
old_threshold: oldConfig?.threshold_value,
new_threshold: data.threshold_value,
old_enabled: oldConfig?.enabled,
new_enabled: data.enabled,
function_name: data.function_name,
});
toast.success('Alert configuration updated');
},
onError: (error) => {
toast.error(`Failed to update alert config: ${error.message}`);
},
});
}
export function useCreateAlertConfig() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (config: Omit<AlertConfig, 'id' | 'created_at' | 'updated_at'>) => {
const { data, error } = await supabase
.from('rate_limit_alert_config')
.insert(config)
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: async (data) => {
queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] });
// Log to audit trail
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
await logAdminAction('rate_limit_config_created', {
config_id: data.id,
metric_type: data.metric_type,
threshold_value: data.threshold_value,
time_window_ms: data.time_window_ms,
function_name: data.function_name,
enabled: data.enabled,
});
toast.success('Alert configuration created');
},
onError: (error) => {
toast.error(`Failed to create alert config: ${error.message}`);
},
});
}
export function useDeleteAlertConfig() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
// Fetch config details before deletion for audit log
const { data: config } = await supabase
.from('rate_limit_alert_config')
.select('*')
.eq('id', id)
.single();
const { error } = await supabase
.from('rate_limit_alert_config')
.delete()
.eq('id', id);
if (error) throw error;
return config;
},
onSuccess: async (config) => {
queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] });
// Log to audit trail
if (config) {
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
await logAdminAction('rate_limit_config_deleted', {
config_id: config.id,
metric_type: config.metric_type,
threshold_value: config.threshold_value,
time_window_ms: config.time_window_ms,
function_name: config.function_name,
});
}
toast.success('Alert configuration deleted');
},
onError: (error) => {
toast.error(`Failed to delete alert config: ${error.message}`);
},
});
}
export function useResolveAlert() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
// Fetch full alert details before resolving
const { data: alert, error: fetchError } = await supabase
.from('rate_limit_alerts')
.select('*')
.eq('id', id)
.single();
if (fetchError) throw fetchError;
// Resolve the alert
const { data, error } = await supabase
.from('rate_limit_alerts')
.update({ resolved_at: new Date().toISOString() })
.eq('id', id)
.select()
.single();
if (error) throw error;
// Log to audit trail
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
await logAdminAction('rate_limit_alert_resolved', {
alert_id: id,
metric_type: alert.metric_type,
metric_value: alert.metric_value,
threshold_value: alert.threshold_value,
function_name: alert.function_name,
time_window_ms: alert.time_window_ms,
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['rateLimitAlerts'] });
queryClient.invalidateQueries({ queryKey: ['rateLimitAlertsUnresolved'] });
toast.success('Alert resolved');
},
onError: (error) => {
toast.error(`Failed to resolve alert: ${error.message}`);
},
});
}