"use client" import { useCallback, useEffect, useState } from "react" import { Check, HardDrive, Info, Loader2, Settings2 } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Switch } from "./ui/switch" import { fetchApi } from "../lib/api-config" import { useT } from "../lib/i18n/provider" interface DiskEntry { name: string key: string model: string serial: string size_bytes: number transport: string rotational: boolean excluded: boolean excluded_at: string | null idle: boolean present: boolean } function formatSize(bytes: number): string { if (!bytes) return "" const units = ["B", "KB", "MB", "GB", "TB", "PB"] let value = bytes let i = 0 while (value >= 1000 && i < units.length - 1) { value /= 1000 i++ } return `${value.toFixed(value >= 100 || i === 0 ? 0 : 1)} ${units[i]}` } // Disks the user wants left alone. An excluded disk is never read on a // schedule โ€” no temperature, no SMART refresh, not even a power-mode query โ€” // so it can spin down on its own timer. Rotational disks with no I/O are // already left alone automatically; this is for the ones that should never // be touched at all, such as a drive handed whole to a VM. export function DiskExclusions() { const t = useT() const [disks, setDisks] = useState([]) const [loading, setLoading] = useState(true) const [editMode, setEditMode] = useState(false) const [pending, setPending] = useState>(new Map()) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [error, setError] = useState("") const load = useCallback(async () => { try { const data = await fetchApi<{ disks: DiskEntry[] }>("/api/health/disks") setDisks(data.disks || []) } catch { setDisks([]) } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) const cancel = () => { setPending(new Map()) setError("") setEditMode(false) } const save = async () => { if (pending.size === 0) { setEditMode(false) return } setSaving(true) setError("") try { for (const [key, excluded] of pending.entries()) { const disk = disks.find((d) => d.key === key) if (!disk) continue if (excluded) { await fetchApi("/api/health/disk-exclusions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ disk_key: disk.key, disk_name: disk.name, model: disk.model, serial: disk.serial, }), }) } else { await fetchApi(`/api/health/disk-exclusions/${encodeURIComponent(disk.key)}`, { method: "DELETE", }) } } setPending(new Map()) setEditMode(false) setSaved(true) setTimeout(() => setSaved(false), 2000) await load() } catch { setError(t("settings.diskExclusions.saveFailed")) } finally { setSaving(false) } } const transportLabel = (disk: DiskEntry) => { const tr = disk.transport.toLowerCase() if (tr === "usb") return "USB" if (tr === "nvme") return "NVMe" if (tr === "sata" || tr === "ata") return disk.rotational ? "HDD" : "SSD" if (tr) return tr.toUpperCase() return disk.rotational ? "HDD" : "SSD" } return (
{t("settings.diskExclusions.title")}
{!loading && disks.length > 0 && (
{saved && ( {t("status.saved")} )} {editMode ? ( <> ) : ( )}
)}
{t("settings.diskExclusions.description")}
{loading ? (
) : disks.length === 0 ? (

{t("settings.diskExclusions.empty")}

) : (
{t("settings.diskExclusions.disk")} {t("settings.diskExclusions.periodicReads")}
{disks.map((disk) => { const excluded = pending.has(disk.key) ? pending.get(disk.key)! : disk.excluded return (
{disk.name || "โ€”"} {transportLabel(disk)} {excluded && ( {t("settings.diskExclusions.excluded")} )} {!excluded && disk.idle && ( {t("storage.idle")} )} {!disk.present && ( {t("settings.diskExclusions.notConnected")} )}
{[disk.model, formatSize(disk.size_bytes), disk.serial].filter(Boolean).join(" ยท ")}
{ setPending((m) => { const next = new Map(m) if (!checked === disk.excluded) next.delete(disk.key) else next.set(disk.key, !checked) return next }) }} className={`data-[state=checked]:bg-blue-600 data-[state=unchecked]:bg-input border border-border ${!editMode ? "opacity-60" : ""}`} />
) })}
{error &&

{error}

}

{t("settings.diskExclusions.help")}
{t("settings.diskExclusions.idleHelp")}

)} ) }