import type { InventoryResponse } from '../soteria-types'; import { formatBytes, formatLabel, formatTimestamp, progressChipClass } from '../soteria-ui-helpers'; interface PVCInventoryPanelProps { inventory: InventoryResponse | null; inventoryError: string; manualDedupe: boolean; manualKeepLast: number; busy: boolean; onManualDedupeChange: (value: boolean) => void; onManualKeepLastChange: (value: number) => void; onTriggerNamespaceBackup: (namespace: string) => void | Promise; onOpenNamespaceSelection: (namespace: string) => void; onTriggerBackup: (namespace: string, pvc: string) => void | Promise; onOpenPVCSelection: (namespace: string, pvc: string) => void | Promise; } export function PVCInventoryPanel({ inventory, inventoryError, manualDedupe, manualKeepLast, busy, onManualDedupeChange, onManualKeepLastChange, onTriggerNamespaceBackup, onOpenNamespaceSelection, onTriggerBackup, onOpenPVCSelection }: PVCInventoryPanelProps) { return (

PVC Inventory

{inventory?.generated_at ? `Updated ${formatTimestamp(inventory.generated_at)}` : 'No inventory yet'}

This setting applies to both `Backup now` and `Backup namespace` actions.

{inventoryError &&

{inventoryError}

} {!inventory && !inventoryError &&

Loading inventory...

} {inventory?.namespaces.map((namespace) => (

{namespace.name}

{namespace.pvcs.map((pvc) => { const healthClass = pvc.healthy ? 'good' : (pvc.health_reason === 'in_progress' ? 'warn' : 'bad'); const healthLabel = pvc.healthy ? 'Healthy' : formatLabel(pvc.health_reason); const progressPct = Math.max(0, Math.min(100, Number(pvc.last_job_progress_pct || 0))); const progressClass = progressChipClass(pvc.last_job_state); const showProgress = Boolean(pvc.last_job_name) || (pvc.active_backups || 0) > 0; const latestSizeLabel = formatBytes(pvc.last_backup_size_bytes); const totalStoredLabel = formatBytes(pvc.total_backup_size_bytes); const showResticSizeHint = pvc.driver === 'restic' && (pvc.last_backup_size_bytes === undefined || pvc.total_backup_size_bytes === undefined); return (

{pvc.pvc}

{pvc.volume || 'unknown volume'} | {pvc.storage_class || 'no class'} | {pvc.capacity || 'unknown size'}

{healthLabel}

Last backup: {pvc.last_backup_at ? `${formatTimestamp(pvc.last_backup_at)} (${(pvc.last_backup_age_hours || 0).toFixed(1)}h ago)` : 'never'}

Backups: {pvc.completed_backups}/{pvc.backup_count} completed | Latest size: {latestSizeLabel} | Total stored: {totalStoredLabel}

{showResticSizeHint && (

Per-PVC storage is estimated from restic upload summaries persisted by Soteria. Older backups created before tracking may show n/a until a new backup runs.

)} {showProgress && (

Job: {pvc.last_job_name || 'n/a'} {pvc.last_job_started_at ? ` | Started ${formatTimestamp(pvc.last_job_started_at)}` : ''}

{pvc.last_job_state || 'Unknown'}
0 ? 'active' : ''}`} style={{ width: `${progressPct}%` }} />

Progress {progressPct}% | Active jobs: {pvc.active_backups || 0}

)} {pvc.error &&

{pvc.error}

}
); })}
))}
); }