"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Globe, HardDrives, LinkSimple, SignOut, Warning, } from "@phosphor-icons/react"; import { isAuthenticated, listExports, issueMountProfile, logout, getMe, type StorageExport, type MountProfile, type User, ApiError, } from "@/lib/api"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; import { CopyField } from "./copy-field"; export default function Home() { const router = useRouter(); const [user, setUser] = useState(null); const [exports, setExports] = useState([]); const [selectedExportId, setSelectedExportId] = useState(null); const [mountProfile, setMountProfile] = useState(null); const [feedback, setFeedback] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { if (!isAuthenticated()) { router.replace("/login"); return; } async function load() { try { const [me, exps] = await Promise.all([getMe(), listExports()]); setUser(me); setExports(exps); } catch (err) { if (err instanceof ApiError && err.status === 401) { router.replace("/login"); return; } setFeedback(err instanceof Error ? err.message : "Failed to load"); } finally { setLoading(false); } } load(); }, [router]); async function handleSelectExport(exportId: string) { setSelectedExportId(exportId); setMountProfile(null); setFeedback(null); try { const profile = await issueMountProfile(exportId); setMountProfile(profile); } catch (err) { setFeedback(err instanceof Error ? err.message : "Failed to issue mount profile"); } } async function handleLogout() { await logout(); router.replace("/login"); } if (loading) { return (

Loading...

); } const selectedExport = selectedExportId ? exports.find((e) => e.id === selectedExportId) ?? null : null; return (

betterNAS

Control Plane

{user && (
{user.username}
)}
{process.env.NEXT_PUBLIC_BETTERNAS_API_URL || "local"} {exports.length === 1 ? "1 export" : `${exports.length} exports`}
{feedback !== null && ( Error {feedback} )}
Exports Storage exports registered with this control plane. {exports.length === 1 ? "1 export" : `${exports.length} exports`} {exports.length === 0 ? (

No exports registered yet. Start the node agent and connect it to this control plane.

) : (
{exports.map((storageExport) => { const isSelected = storageExport.id === selectedExportId; return ( ); })}
)}
{selectedExport !== null ? `Mount ${selectedExport.label}` : "Mount instructions"} {selectedExport !== null ? "Issued WebDAV credentials for Finder." : "Select an export to issue mount credentials."} {mountProfile === null ? (

Pick an export to issue WebDAV credentials for Finder.

) : (
Issued profile {mountProfile.readonly ? "Read-only" : "Read-write"}
Mode
{mountProfile.credential.mode}
Expires
{mountProfile.credential.expiresAt}

Finder steps

    {[ "Open Finder and choose Go, then Connect to Server.", "Paste the mount URL into the server address field.", "Enter the issued username and password when prompted.", "Save to Keychain only if the credential expiry suits your workflow.", ].map((step, index) => (
  1. {index + 1} {step}
  2. ))}
)}
); }