'use client'; import { type ReactNode, useCallback, useEffect, useMemo, useState, } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertCircle, Archive, Clock, Database, FileWarning, HardDrive, RefreshCw, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { backendClient } from '@/app/infra/http'; import { PanelToolbar } from '../settings-dialog/panel-layout'; interface StorageSection { key: string; path: string; exists: boolean; size_bytes: number; file_count: number; } interface CleanupCandidate { key?: string; name?: string; size_bytes: number; modified_at?: string; date?: string; } interface StorageAnalysis { generated_at: string; cleanup_policy: { uploaded_file_retention_days: number; log_retention_days: number; }; sections: StorageSection[]; database: { type: string; monitoring_counts: Record; binary_storage: { count: number; size_bytes: number | null; }; }; cleanup_candidates: { uploaded_files: CleanupCandidate[]; log_files: CleanupCandidate[]; }; tasks: Record; } interface StorageAnalysisPanelProps { // True when this panel is the active section and the dialog is open. active: boolean; } function formatBytes(bytes: number | null | undefined): string { if (bytes === null || bytes === undefined) { return '-'; } if (bytes < 1024) { return `${bytes} B`; } const units = ['KB', 'MB', 'GB', 'TB']; let value = bytes / 1024; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex += 1; } return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[unitIndex]}`; } export default function StorageAnalysisPanel({ active, }: StorageAnalysisPanelProps) { const { t } = useTranslation(); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const loadAnalysis = useCallback(async () => { setLoading(true); setError(null); try { const result = await backendClient.get( '/api/v1/system/storage-analysis', ); setAnalysis(result); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, []); useEffect(() => { if (active) { loadAnalysis(); } }, [loadAnalysis, active]); const totalBytes = useMemo(() => { return ( analysis?.sections.reduce((sum, item) => sum + item.size_bytes, 0) ?? 0 ); }, [analysis]); const uploadedCandidateBytes = useMemo(() => { return ( analysis?.cleanup_candidates.uploaded_files.reduce( (sum, item) => sum + item.size_bytes, 0, ) ?? 0 ); }, [analysis]); const logCandidateBytes = useMemo(() => { return ( analysis?.cleanup_candidates.log_files.reduce( (sum, item) => sum + item.size_bytes, 0, ) ?? 0 ); }, [analysis]); return (
{analysis ? t('storageAnalysis.generatedAt', { time: new Date(analysis.generated_at).toLocaleString(), }) : t('storageAnalysis.loading')}
{error && (
{error}
)} {analysis && ( <>
} /> } /> } /> } />

{t('storageAnalysis.cleanupPolicy')}

{t('storageAnalysis.sections')}

{analysis.sections.map((section) => (
{t(`storageAnalysis.sectionNames.${section.key}`)}
{section.path || '-'}
{section.exists ? ( ) : ( {t('storageAnalysis.missing')} )}
{formatBytes(section.size_bytes)}
{section.file_count}
))}
)}
); } function SummaryItem({ label, value, icon, meta, }: { label: string; value: string; icon: ReactNode; meta?: string; }) { return (
{icon} {label}
{value} {meta && {meta}}
); } function PolicyItem({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function MetricPanel({ title, values, }: { title: string; values: Record; }) { return (

{title}

{Object.entries(values).map(([key, value]) => (
{key} {value ?? '-'}
))}
); } function CandidatePanel({ title, emptyText, candidates, }: { title: string; emptyText: string; candidates: CleanupCandidate[]; }) { return (

{title}

{candidates.length === 0 ? (
{emptyText}
) : ( candidates.slice(0, 8).map((candidate, index) => (
{candidate.key ?? candidate.name}
{candidate.modified_at ?? candidate.date ?? '-'}
{formatBytes(candidate.size_bytes)}
)) )}
); }