import { useState, useEffect } from "react"; import { useVault } from "../App"; import { scanIntegrity, computeChecksums, verifyChecksums, findOrphanAttachments, createBackup, listBackups, restoreBackup, type IntegrityIssue, type ChecksumMismatch, type OrphanAttachment, type BackupEntry, } from "../lib/commands"; export default function IntegrityReport({ onClose }: { onClose: () => void }) { const { vaultPath } = useVault(); const [issues, setIssues] = useState([]); const [mismatches, setMismatches] = useState([]); const [orphans, setOrphans] = useState([]); const [backups, setBackups] = useState([]); const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState<"scan" | "checksums" | "orphans" | "backups">("scan"); const [status, setStatus] = useState(""); useEffect(() => { runScan(); loadBackups(); }, [vaultPath]); const runScan = async () => { if (!vaultPath) return; setLoading(true); setStatus("Scanning vault..."); try { const result = await scanIntegrity(vaultPath); setIssues(result); setStatus(`Found ${result.length} issue(s)`); } catch (e) { setStatus(`Scan failed: ${e}`); } setLoading(false); }; const runChecksumVerify = async () => { if (!vaultPath) return; setLoading(true); setStatus("Computing checksums..."); try { await computeChecksums(vaultPath); const result = await verifyChecksums(vaultPath); setMismatches(result); setStatus(result.length === 0 ? "All checksums valid โœ“" : `${result.length} mismatch(es) found`); } catch (e) { setStatus(`Checksum verification failed: ${e}`); } setLoading(false); }; const runOrphanScan = async () => { if (!vaultPath) return; setLoading(true); setStatus("Scanning attachments..."); try { const result = await findOrphanAttachments(vaultPath); setOrphans(result); setStatus(result.length === 0 ? "No orphan attachments โœ“" : `${result.length} orphan(s) found`); } catch (e) { setStatus(`Orphan scan failed: ${e}`); } setLoading(false); }; const handleCreateBackup = async () => { if (!vaultPath) return; setLoading(true); setStatus("Creating backup..."); try { const name = await createBackup(vaultPath); setStatus(`Backup created: ${name}`); loadBackups(); } catch (e) { setStatus(`Backup failed: ${e}`); } setLoading(false); }; const loadBackups = async () => { if (!vaultPath) return; try { const list = await listBackups(vaultPath); setBackups(list); } catch { /* ignore */ } }; const handleRestore = async (name: string) => { if (!vaultPath) return; if (!confirm(`Restore from ${name}? This will overwrite current files.`)) return; setLoading(true); try { const count = await restoreBackup(vaultPath, name); setStatus(`Restored ${count} files from ${name}`); } catch (e) { setStatus(`Restore failed: ${e}`); } setLoading(false); }; const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1048576).toFixed(1)} MB`; }; const severityIcon = (s: string) => s === "error" ? "๐Ÿ”ด" : s === "warning" ? "๐ŸŸก" : "๐Ÿ”ต"; return (

๐Ÿ›ก๏ธ Integrity Report

{(["scan", "checksums", "orphans", "backups"] as const).map((tab) => ( ))}
{status &&
{loading ? "โณ " : ""}{status}
}
{activeTab === "scan" && ( <> {issues.length === 0 && !loading && (
โœ… No issues found โ€” vault is clean
)} {issues.map((issue, i) => (
{severityIcon(issue.severity)}
{issue.path} {issue.description}
))} )} {activeTab === "checksums" && ( <> {mismatches.length === 0 && !loading && (
โœ… All checksums match
)} {mismatches.map((m, i) => (
โš ๏ธ
{m.path} Expected: {m.expected.slice(0, 12)}โ€ฆ โ†’ Got: {m.actual.slice(0, 12)}โ€ฆ
))} )} {activeTab === "orphans" && ( <> {orphans.length === 0 && !loading && (
โœ… No orphan attachments
)} {orphans.map((o, i) => (
๐Ÿ“Ž
{o.path} {formatSize(o.size)}
))} )} {activeTab === "backups" && ( <> {backups.length === 0 && !loading && (
No backups yet
)} {backups.map((b, i) => (
๐Ÿ’พ
{b.name} {b.created} ยท {formatSize(b.size)}
))} )}
); }