mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 18:11:12 -05:00
Connect to Lovable Cloud
Fix security definer view issue by enabling security_invoker on grouped_alerts_view and implement grouped alerts system with new keys, hooks, components, and monitoring overview integration. Added migration to adjust view, updated query keys, created useGroupedAlerts, useAlertGroupActions, GroupedAlertsPanel, and updated MonitoringOverview to include grouped alerts.
This commit is contained in:
110
src/hooks/admin/useAlertGroupActions.ts
Normal file
110
src/hooks/admin/useAlertGroupActions.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { queryKeys } from '@/lib/queryKeys';
|
||||
import { toast } from 'sonner';
|
||||
import type { GroupedAlert } from './useGroupedAlerts';
|
||||
|
||||
export function useResolveAlertGroup() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
alertIds,
|
||||
source
|
||||
}: {
|
||||
alertIds: string[];
|
||||
source: 'system' | 'rate_limit';
|
||||
}) => {
|
||||
const table = source === 'system' ? 'system_alerts' : 'rate_limit_alerts';
|
||||
const { error } = await supabase
|
||||
.from(table)
|
||||
.update({ resolved_at: new Date().toISOString() })
|
||||
.in('id', alertIds);
|
||||
|
||||
if (error) throw error;
|
||||
return { count: alertIds.length };
|
||||
},
|
||||
onMutate: async ({ alertIds }) => {
|
||||
// Cancel any outgoing refetches
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: queryKeys.monitoring.groupedAlerts()
|
||||
});
|
||||
|
||||
const previousData = queryClient.getQueryData(
|
||||
queryKeys.monitoring.groupedAlerts()
|
||||
);
|
||||
|
||||
// Optimistically update to the new value
|
||||
queryClient.setQueryData(
|
||||
queryKeys.monitoring.groupedAlerts(),
|
||||
(old: GroupedAlert[] | undefined) => {
|
||||
if (!old) return old;
|
||||
return old.map(alert => {
|
||||
const hasMatchingIds = alert.alert_ids.some(id =>
|
||||
alertIds.includes(id)
|
||||
);
|
||||
if (hasMatchingIds) {
|
||||
return {
|
||||
...alert,
|
||||
unresolved_count: 0,
|
||||
has_resolved: true,
|
||||
};
|
||||
}
|
||||
return alert;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return { previousData };
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
toast.success(`Resolved ${data.count} alert${data.count > 1 ? 's' : ''}`);
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
// Rollback on error
|
||||
if (context?.previousData) {
|
||||
queryClient.setQueryData(
|
||||
queryKeys.monitoring.groupedAlerts(),
|
||||
context.previousData
|
||||
);
|
||||
}
|
||||
toast.error('Failed to resolve alerts');
|
||||
console.error('Error resolving alert group:', error);
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.monitoring.groupedAlerts()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.monitoring.combinedAlerts()
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSnoozeAlertGroup() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
groupKey,
|
||||
duration
|
||||
}: {
|
||||
groupKey: string;
|
||||
duration: number;
|
||||
}) => {
|
||||
const snoozedAlerts = JSON.parse(
|
||||
localStorage.getItem('snoozed_alerts') || '{}'
|
||||
);
|
||||
snoozedAlerts[groupKey] = Date.now() + duration;
|
||||
localStorage.setItem('snoozed_alerts', JSON.stringify(snoozedAlerts));
|
||||
return { groupKey, until: snoozedAlerts[groupKey] };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.monitoring.groupedAlerts()
|
||||
});
|
||||
toast.success('Alert group snoozed');
|
||||
},
|
||||
});
|
||||
}
|
||||
90
src/hooks/admin/useGroupedAlerts.ts
Normal file
90
src/hooks/admin/useGroupedAlerts.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { queryKeys } from '@/lib/queryKeys';
|
||||
|
||||
export interface GroupedAlert {
|
||||
group_key: string;
|
||||
alert_type?: string;
|
||||
severity: 'critical' | 'high' | 'medium' | 'low';
|
||||
source: 'system' | 'rate_limit';
|
||||
function_name?: string;
|
||||
metric_type?: string;
|
||||
alert_count: number;
|
||||
unresolved_count: number;
|
||||
first_seen: string;
|
||||
last_seen: string;
|
||||
alert_ids: string[];
|
||||
messages: string[];
|
||||
has_resolved: boolean;
|
||||
is_recurring: boolean;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
interface GroupedAlertsOptions {
|
||||
includeResolved?: boolean;
|
||||
minCount?: number;
|
||||
severity?: 'critical' | 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
export function useGroupedAlerts(options?: GroupedAlertsOptions) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.monitoring.groupedAlerts(options),
|
||||
queryFn: async () => {
|
||||
let query = supabase
|
||||
.from('grouped_alerts_view')
|
||||
.select('*')
|
||||
.order('last_seen', { ascending: false });
|
||||
|
||||
if (!options?.includeResolved) {
|
||||
query = query.gt('unresolved_count', 0);
|
||||
}
|
||||
|
||||
if (options?.minCount) {
|
||||
query = query.gte('alert_count', options.minCount);
|
||||
}
|
||||
|
||||
if (options?.severity) {
|
||||
query = query.eq('severity', options.severity);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
if (error) throw error;
|
||||
|
||||
return (data || []).map(alert => ({
|
||||
...alert,
|
||||
is_recurring: (alert.alert_count ?? 0) > 3,
|
||||
is_active: new Date(alert.last_seen ?? new Date()).getTime() > Date.now() - 3600000,
|
||||
})) as GroupedAlert[];
|
||||
},
|
||||
staleTime: 15000,
|
||||
refetchInterval: 30000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlertGroupDetails(groupKey: string, source: 'system' | 'rate_limit', alertIds: string[]) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.monitoring.alertGroupDetails(groupKey),
|
||||
queryFn: async () => {
|
||||
if (source === 'system') {
|
||||
const { data, error } = await supabase
|
||||
.from('system_alerts')
|
||||
.select('*')
|
||||
.in('id', alertIds)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
} else {
|
||||
const { data, error } = await supabase
|
||||
.from('rate_limit_alerts')
|
||||
.select('*')
|
||||
.in('id', alertIds)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
}
|
||||
},
|
||||
enabled: alertIds.length > 0,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user