"use client" import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import { api } from "@/lib/api" import { Header } from "@/components/Header" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { RefreshCw, Plus, Trash2, AlertCircle } from "lucide-react" import CreateSambaDialog from "@/components/shares/CreateSambaDialog" import CreateNfsDialog from "@/components/shares/CreateNfsDialog" import DeleteConfirmDialog from "@/components/shares/DeleteConfirmDialog" export default function SharesPage() { const router = useRouter() const [activeTab, setActiveTab] = useState<"samba" | "nfs" | "config">("samba") const [sambaShares, setSambaShares] = useState([]) const [nfsShares, setNfsShares] = useState([]) const [sambaConfig, setSambaConfig] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showSambaDialog, setShowSambaDialog] = useState(false) const [showNfsDialog, setShowNfsDialog] = useState(false) const [deleteConfirm, setDeleteConfirm] = useState<{ type: "samba" | "nfs"; name: string } | null>(null) const [deleting, setDeleting] = useState(false) const [editMode, setEditMode] = useState(false) const [rawConfigText, setRawConfigText] = useState("") const [saving, setSaving] = useState(false) const [macosEnabled, setMacosEnabled] = useState(false) const [editingShare, setEditingShare] = useState(null) useEffect(() => { const token = localStorage.getItem("access_token") if (!token) { router.push("/login") return } loadShares() }, [router]) const loadShares = async () => { try { setLoading(true) setError(null) const [samba, nfs, config] = await Promise.all([ api.getSambaShares().catch(() => []), api.getNfsShares().catch(() => []), api.getSambaConfig().catch(() => ({ parameters: [] })), ]) setSambaShares(samba) setNfsShares(nfs) const params: { key: string; value: string }[] = config.parameters || [] setSambaConfig(params) const raw = params.map((p) => `${p.key} = ${p.value}`).join("\n") setRawConfigText(raw) const hasMacOS = ["fruit:encoding", "fruit:metadata", "fruit:zero_file_id", "fruit:nfs_aces"].every( (k) => params.some((p) => p.key === k) ) setMacosEnabled(hasMacOS) } catch (err) { setError(err instanceof Error ? err.message : "Failed to load shares") } finally { setLoading(false) } } const handleDeleteSamba = async (name: string) => { try { setDeleting(true) await api.deleteSambaShare(name) setSambaShares(sambaShares.filter((s) => s.name !== name)) setDeleteConfirm(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to delete share") } finally { setDeleting(false) } } const handleDeleteNfs = async (path: string) => { try { setDeleting(true) await api.deleteNfsShare(path) setNfsShares(nfsShares.filter((s) => s.path !== path)) setDeleteConfirm(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to delete share") } finally { setDeleting(false) } } const handleSambaCreated = (newShare: any) => { setSambaShares([...sambaShares, newShare]) setShowSambaDialog(false) } const handleSaveShare = async () => { if (!editingShare) return try { setSaving(true) await api.updateSambaShare(editingShare.oldName, { name: editingShare.name, path: editingShare.path, comment: editingShare.comment }) setSambaShares(sambaShares.map(s => s.name === editingShare.oldName ? { name: editingShare.name, path: editingShare.path, comment: editingShare.comment, ...s } : s )) setEditingShare(null) setError(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to save share") } finally { setSaving(false) } } const handleNfsCreated = (newShare: any) => { setNfsShares([...nfsShares, newShare]) setShowNfsDialog(false) } const MACOS_PARAMS: { [key: string]: string } = { "fruit:encoding": "native", "fruit:metadata": "stream", "fruit:zero_file_id": "yes", "fruit:nfs_aces": "no", } const MACOS_KEYS = Object.keys(MACOS_PARAMS) const handleSaveRaw = async () => { try { setSaving(true) const parsed: { [key: string]: string } = {} rawConfigText.split("\n").forEach((line) => { const eq = line.indexOf("=") if (eq === -1) return const key = line.slice(0, eq).trim() const value = line.slice(eq + 1).trim() if (key) parsed[key] = value }) await api.setSambaConfig(parsed) await loadShares() setEditMode(false) setError(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to save configuration") } finally { setSaving(false) } } const handleToggleMacOS = async (enable: boolean) => { try { setSaving(true) const current = sambaConfig.reduce((acc, p) => { acc[p.key] = p.value; return acc }, {} as { [key: string]: string }) if (enable) { await api.setSambaConfig({ ...current, ...MACOS_PARAMS } as { [key: string]: string }) } else { const filtered: { [key: string]: string } = {} Object.entries(current).forEach(([k, v]) => { if (!MACOS_KEYS.includes(k)) filtered[k] = v as string }) await api.setSambaConfig(filtered) } setMacosEnabled(enable) await loadShares() setError(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to update macOS settings") } finally { setSaving(false) } } return (

File Sharing

Manage Samba (SMB) and NFS network shares

{error && (

Error

{error}

)} {/* Tabs */}
{/* SAMBA TAB */} {activeTab === "samba" && (
Samba Shares
{sambaShares.length === 0 ? (

No Samba shares configured. Create one to get started.

) : (
{sambaShares.map((share) => { const isEditing = editingShare?.oldName === share.name return ( ) })}
Name Path Users Perms Comment Actions
{isEditing ? ( setEditingShare({ ...editingShare, name: e.target.value })} className="px-2 py-1 rounded border border-border bg-background text-xs font-mono w-full" disabled={saving} /> ) : ( {share.name} )} {isEditing ? ( setEditingShare({ ...editingShare, path: e.target.value })} className="px-2 py-1 rounded border border-border bg-background text-xs font-mono w-full" disabled={saving} /> ) : ( {share.path} )} {share.valid_users || "—"} {share.read_only ? "RO" : "RW"} {isEditing ? ( setEditingShare({ ...editingShare, comment: e.target.value })} className="px-2 py-1 rounded border border-border bg-background text-xs w-full" disabled={saving} placeholder="Optional comment" /> ) : ( share.comment || "—" )} {isEditing ? ( <> ) : ( <> )}
)}
)} {/* NFS TAB */} {activeTab === "nfs" && (
NFS Shares
{nfsShares.length === 0 ? (

No NFS shares configured. Create one to get started.

) : (
{nfsShares.map((share) => ( ))}
Path Clients Options Actions
{share.path} {share.clients} {share.options || "—"}
)}
)} {/* SAMBA CONFIG TAB */} {activeTab === "config" && (
{/* MacOS Toggle */}

Global MacOS Shares

Optimize all shares for MacOS

{macosEnabled && (
{Object.entries(MACOS_PARAMS).map(([k, v]) => ( {k} = {v} ))}
)}
{/* Advanced raw config */}
Advanced {!editMode ? ( ) : (
)}