import React, { useState, useEffect, useCallback } from 'react'; import { Play, Wrench } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { FieldRow, Toggle, formatDateTime } from './adminShared'; export default function CleanupPanel() { const [status, setStatus] = useState(null); const [form, setForm] = useState({ import_sessions_enabled: true, temp_exports_enabled: true, temp_export_max_age_hours: 2, backup_partials_enabled: true, import_history_enabled: false, import_history_max_age_days: 365, }); const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [loading, setLoading] = useState(true); const load = useCallback(async () => { try { const data = await api.adminCleanup(); setStatus(data); if (data.settings) setForm(data.settings); } catch (err) { toast.error(err.message || 'Failed to load cleanup settings.'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const set = (k, v) => setForm(prev => ({ ...prev, [k]: v })); async function handleSave() { setSaving(true); try { const next = await api.saveAdminCleanup(form); if (next) setForm(next); toast.success('Cleanup settings saved.'); } catch (err) { toast.error(err.message || 'Failed to save cleanup settings.'); } finally { setSaving(false); } } async function handleRunNow() { setRunning(true); try { const result = await api.runAdminCleanup(); setStatus(prev => ({ ...prev, last_run_at: result.ran_at, last_result: result.tasks, })); toast.success('Cleanup tasks completed.'); } catch (err) { toast.error(err.message || 'Cleanup run failed.'); } finally { setRunning(false); } } function resultLine(label, task, countKey) { if (!task || task[countKey] == null) return null; return `${label}: ${task[countKey]}`; } const resultLines = status?.last_result ? [ resultLine('Import sessions pruned', status.last_result.import_sessions, 'pruned'), resultLine('Temp export files removed', status.last_result.temp_export_files, 'removed'), resultLine('Backup partials removed', status.last_result.backup_partials, 'removed'), resultLine('Import history rows pruned', status.last_result.import_history, 'pruned'), ].filter(Boolean) : []; if (loading) { return ( Loading cleanup settings… ); } return (
Cleanup / Maintenance

Automatic daily cleanup: expired import sessions, stale export temp files, orphaned backup partials. Runs at 6:00 AM.

Auto Cleanup runs automatically at 6:00 AM daily

Last Run

{status?.last_run_at ? formatDateTime(status.last_run_at) : '—'}

Last Result

{resultLines.length > 0 ? (
    {resultLines.map(line => (
  • {line}
  • ))}
) : (

No runs recorded yet

)}

Task Settings

{[ ['import_sessions_enabled', 'Prune expired import sessions (24h TTL)'], ['temp_exports_enabled', 'Remove stale SQLite export temp files'], ['backup_partials_enabled', 'Remove orphaned backup .partial / .upload files'], ].map(([key, label]) => ( set(key, v)} label={label} /> ))} set('temp_export_max_age_hours', parseInt(e.target.value, 10) || 2)} disabled={!form.temp_exports_enabled} className="max-w-[120px] h-8 text-sm" /> set('import_history_enabled', v)} label="Trim import history rows" /> {form.import_history_enabled && ( <> set('import_history_max_age_days', parseInt(e.target.value, 10) || 365)} className="max-w-[120px] h-8 text-sm" />
Warning: Import history trimming permanently deletes audit records older than {form.import_history_max_age_days} days. This cannot be undone.
)}
); }