Add database maintenance tooling

- Implement maintenance hooks (useMaintenanceTables, useVacuumTable, useAnalyzeTable, useReindexTable)
- Add DatabaseMaintenance page and UI for vacuum/analyze/reindex
- Wire new route / admin/database-maintenance and sidebar entry
- Remove DatabaseMaintenance icon usage on page and align with AdminLayout props
This commit is contained in:
gpt-engineer-app[bot]
2025-11-12 01:39:05 +00:00
parent 2468d3cc18
commit e2ee11b9f5
7 changed files with 578 additions and 2 deletions

View File

@@ -0,0 +1,135 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { queryKeys } from '@/lib/queryKeys';
import { toast } from 'sonner';
export interface MaintenanceTable {
table_name: string;
row_count: number;
table_size: string;
indexes_size: string;
total_size: string;
}
export interface MaintenanceResult {
table_name: string;
operation: string;
started_at: string;
completed_at: string;
duration_ms: number;
success: boolean;
error?: string;
}
export function useMaintenanceTables() {
return useQuery({
queryKey: queryKeys.admin.maintenanceTables(),
queryFn: async () => {
const { data, error } = await supabase.rpc('get_maintenance_tables');
if (error) throw error;
return data as unknown as MaintenanceTable[];
},
staleTime: 2 * 60 * 1000, // 2 minutes
});
}
export function useVacuumTable() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (tableName: string) => {
const { data, error } = await supabase.rpc('run_vacuum_table', {
table_name: tableName,
});
if (error) throw error;
return data as unknown as MaintenanceResult;
},
onSuccess: (result) => {
if (result.success) {
toast.success(`Vacuum completed on ${result.table_name}`, {
description: `Duration: ${Math.round(result.duration_ms)}ms`,
});
} else {
toast.error(`Vacuum failed on ${result.table_name}`, {
description: result.error,
});
}
queryClient.invalidateQueries({ queryKey: queryKeys.admin.maintenanceTables() });
},
onError: (error: Error) => {
toast.error('Vacuum operation failed', {
description: error.message,
});
},
});
}
export function useAnalyzeTable() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (tableName: string) => {
const { data, error } = await supabase.rpc('run_analyze_table', {
table_name: tableName,
});
if (error) throw error;
return data as unknown as MaintenanceResult;
},
onSuccess: (result) => {
if (result.success) {
toast.success(`Analyze completed on ${result.table_name}`, {
description: `Duration: ${Math.round(result.duration_ms)}ms`,
});
} else {
toast.error(`Analyze failed on ${result.table_name}`, {
description: result.error,
});
}
queryClient.invalidateQueries({ queryKey: queryKeys.admin.maintenanceTables() });
},
onError: (error: Error) => {
toast.error('Analyze operation failed', {
description: error.message,
});
},
});
}
export function useReindexTable() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (tableName: string) => {
const { data, error } = await supabase.rpc('run_reindex_table', {
table_name: tableName,
});
if (error) throw error;
return data as unknown as MaintenanceResult;
},
onSuccess: (result) => {
if (result.success) {
toast.success(`Reindex completed on ${result.table_name}`, {
description: `Duration: ${Math.round(result.duration_ms)}ms`,
});
} else {
toast.error(`Reindex failed on ${result.table_name}`, {
description: result.error,
});
}
queryClient.invalidateQueries({ queryKey: queryKeys.admin.maintenanceTables() });
},
onError: (error: Error) => {
toast.error('Reindex operation failed', {
description: error.message,
});
},
});
}