Files
thrilltrack-explorer/src/components/admin/VersionCleanupSettings.tsx
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

213 lines
6.6 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Loader2, Trash2, CheckCircle, AlertCircle } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { supabase } from '@/lib/supabaseClient';
import { format } from 'date-fns';
import { handleNonCriticalError } from '@/lib/errorHandler';
export function VersionCleanupSettings() {
const [retentionDays, setRetentionDays] = useState(90);
const [lastCleanup, setLastCleanup] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const { toast } = useToast();
useEffect(() => {
loadSettings();
}, []);
const loadSettings = async () => {
try {
const { data: retention, error: retentionError } = await supabase
.from('admin_settings')
.select('setting_value')
.eq('setting_key', 'version_retention_days')
.single();
if (retentionError) throw retentionError;
const { data: cleanup, error: cleanupError } = await supabase
.from('admin_settings')
.select('setting_value')
.eq('setting_key', 'last_version_cleanup')
.single();
if (cleanupError) throw cleanupError;
if (retention?.setting_value) {
const retentionValue = typeof retention.setting_value === 'string'
? retention.setting_value
: String(retention.setting_value);
setRetentionDays(Number(retentionValue));
}
if (cleanup?.setting_value && cleanup.setting_value !== 'null') {
const cleanupValue = typeof cleanup.setting_value === 'string'
? cleanup.setting_value.replace(/"/g, '')
: String(cleanup.setting_value);
setLastCleanup(cleanupValue);
}
} catch (error: unknown) {
handleNonCriticalError(error, {
action: 'Load version cleanup settings'
});
toast({
title: 'Error',
description: 'Failed to load cleanup settings',
variant: 'destructive',
});
} finally {
setIsInitialLoad(false);
}
};
const handleSaveRetention = async () => {
setIsSaving(true);
const oldRetentionDays = retentionDays;
try {
// Get current value for audit log
const { data: currentSetting } = await supabase
.from('admin_settings')
.select('setting_value')
.eq('setting_key', 'version_retention_days')
.single();
const { error } = await supabase
.from('admin_settings')
.update({ setting_value: retentionDays.toString() })
.eq('setting_key', 'version_retention_days');
if (error) throw error;
// Log to audit trail
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
await logAdminAction('version_cleanup_config_changed', {
setting_key: 'version_retention_days',
old_value: currentSetting?.setting_value,
new_value: retentionDays,
});
toast({
title: 'Settings Saved',
description: 'Retention period updated successfully'
});
} catch (error) {
toast({
title: 'Save Failed',
description: error instanceof Error ? error.message : 'Failed to save settings',
variant: 'destructive'
});
} finally {
setIsSaving(false);
}
};
const handleManualCleanup = async () => {
setIsLoading(true);
try {
const { data, error } = await supabase.functions.invoke('cleanup-old-versions', {
body: { manual: true }
});
if (error) throw error;
toast({
title: 'Cleanup Complete',
description: data.message || `Deleted ${data.stats?.item_edit_history_deleted || 0} old versions`,
});
await loadSettings();
} catch (error) {
toast({
title: 'Cleanup Failed',
description: error instanceof Error ? error.message : 'Failed to run cleanup',
variant: 'destructive'
});
} finally {
setIsLoading(false);
}
};
if (isInitialLoad) {
return (
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Version History Cleanup</CardTitle>
<CardDescription>
Manage automatic cleanup of old version history records
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="retention">Retention Period (days)</Label>
<div className="flex gap-2">
<Input
id="retention"
type="number"
min={30}
max={365}
value={retentionDays}
onChange={(e) => setRetentionDays(Number(e.target.value))}
className="w-32"
/>
<Button onClick={handleSaveRetention} loading={isSaving} loadingText="Saving...">
Save
</Button>
</div>
<p className="text-xs text-muted-foreground">
Keep most recent 10 versions per item, delete older ones beyond this period
</p>
</div>
{lastCleanup ? (
<Alert>
<CheckCircle className="h-4 w-4" />
<AlertDescription>
Last cleanup: {format(new Date(lastCleanup), 'PPpp')}
</AlertDescription>
</Alert>
) : (
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
No cleanup has been performed yet
</AlertDescription>
</Alert>
)}
<div className="pt-4 border-t">
<Button
onClick={handleManualCleanup}
loading={isLoading}
loadingText="Running Cleanup..."
variant="outline"
className="w-full"
>
<Trash2 className="h-4 w-4 mr-2" />
Run Manual Cleanup Now
</Button>
<p className="text-xs text-muted-foreground mt-2 text-center">
Automatic cleanup runs every Sunday at 2 AM UTC
</p>
</div>
</CardContent>
</Card>
);
}