"use client" import { useEffect, useState } from "react" import useSWR from "swr" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Button } from "./ui/button" import { Input } from "./ui/input" import { Label } from "./ui/label" import { fetchApi } from "../lib/api-config" import { formatBytes } from "../lib/utils" import { useT } from "../lib/i18n/provider" interface Connection { id: string; name: string; url: string; token_id: string; fingerprint: string; has_token: boolean } interface Connections { local_available: boolean; servers: Connection[] } interface Datastore { name: string; path?: string; total?: number | null; used?: number | null; avail?: number | null; error?: string } interface Task { upid: string; worker_type?: string; worker_id?: string; starttime?: number; endtime?: number; status?: string } interface Job { id: string; store?: string; schedule?: string; "last-run-state"?: string } interface PBSStatus { name: string; version: string | null; fetched_at: number; services: Record; datastores: Datastore[] | null; tasks: Task[] | null; sync: Job[] | null; verify: Job[] | null; prune: Job[] | null; errors: Record; } const emptyForm = { id: "", name: "", url: "", token_id: "", fingerprint: "", token_secret: "" } const selectClass = "h-10 rounded-md border bg-background px-3 text-sm" export function PBSPanel() { const t = useT() const { data: connections, error: connectionError, mutate: reloadConnections } = useSWR( "/api/pbs/servers", url => fetchApi(url), { revalidateOnFocus: false }) const [selected, setSelected] = useState("") const [manage, setManage] = useState(false) const [form, setForm] = useState(emptyForm) const [busy, setBusy] = useState(false) const [message, setMessage] = useState("") const [error, setError] = useState("") const { data, error: statusError, isLoading, mutate } = useSWR( selected ? `/api/pbs/status?server=${encodeURIComponent(selected)}` : null, url => fetchApi(url), { refreshInterval: 30000, revalidateOnFocus: false }) useEffect(() => { if (!connections) return if (selected === "local" && connections.local_available) return if (connections.servers.some(row => row.id === selected)) return setSelected(connections.local_available ? "local" : connections.servers[0]?.id || "") }, [connections, selected]) const save = async () => { setBusy(true); setError(""); setMessage("") try { const result = await fetchApi<{ id: string }>("/api/pbs/servers", { method: "POST", body: JSON.stringify({ ...form, id: form.id || undefined, token_secret: form.token_secret || undefined, }) }) await reloadConnections() setSelected(result.id) setForm(emptyForm) setMessage(t("pbs.saved")) } catch (err) { setError(err instanceof Error ? err.message : t("pbs.saveFailed")) } finally { setBusy(false) } } const remove = async () => { if (!form.id || !window.confirm(t("pbs.removeConfirm"))) return setBusy(true); setError(""); setMessage("") try { await fetchApi(`/api/pbs/servers/${encodeURIComponent(form.id)}`, { method: "DELETE" }) setForm(emptyForm) await reloadConnections() } catch (err) { setError(err instanceof Error ? err.message : t("pbs.saveFailed")) } finally { setBusy(false) } } const capacity = (value?: number | null) => value == null ? "—" : formatBytes(value) return

{t("pbs.title")}

{(connectionError || statusError) &&

{String((connectionError || statusError)?.message || t("pbs.loadFailed"))}

} {isLoading &&

{t("pbs.loading")}

} {!selected && !connectionError && connections &&

{t("pbs.noServer")}

} {manage && {t("pbs.manage")}

{t("pbs.permissionsHelp")}

{(["name", "url", "token_id", "token_secret", "fingerprint"] as const).map(field =>
setForm({ ...form, [field]: event.target.value })} />
)}

{t("pbs.tlsHelp")}

{error &&

{error}

} {message &&

{message}

}
{form.id && }
} {data && <>

{data.name} · PBS {data.version || "—"} · {t("pbs.sampled")} {new Date(data.fetched_at * 1000).toLocaleTimeString()}

{Object.keys(data.services).length > 0 &&
{Object.entries(data.services).map(([service, state]) => {service}: {state})}
} {Object.entries(data.errors).map(([section, detail]) =>

{t(`pbs.${section}`)}: {detail}

)} {t("pbs.datastores")} {data.datastores?.length === 0 &&

{t("pbs.noDatastores")}

} {data.datastores && data.datastores.length > 0 &&
{data.datastores.map(row => )}
{t("pbs.name")}{t("pbs.total")}{t("pbs.used")}{t("pbs.free")}
{row.name}{row.error &&

{row.error}

}
{capacity(row.total)}{capacity(row.used)}{capacity(row.avail)}
}
{t("pbs.tasks")} {data.tasks?.length === 0 &&

{t("pbs.noTasks")}

} {data.tasks && data.tasks.length > 0 &&
{data.tasks.map(row => )}
{t("pbs.task")}{t("pbs.started")}{t("pbs.status")}
{row.worker_type} {row.worker_id}{row.starttime ? new Date(row.starttime * 1000).toLocaleString() : "—"}{row.status || (row.endtime ? "—" : t("pbs.running"))}
}
{t("pbs.jobs")}
{(["sync", "verify", "prune"] as const).flatMap(kind => (data[kind] || []).map(row => ))}
{t("pbs.job")}{t("pbs.datastores")}{t("pbs.schedule")}{t("pbs.status")}
{t(`pbs.${kind}`)} · {row.id}{row.store || "—"}{row.schedule || "—"}{row["last-run-state"] || "—"}
}
}