"use client" import { useEffect, useState } from "react" import { api, Dataset, SambaShare, NfsShare } from "@/lib/api" import { Header } from "@/components/Header" import { Button } from "@/components/ui/button" import { Dialog } from "@/components/ui/dialog" import { Plus, Trash2, RefreshCw, ChevronRight, ChevronDown } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" export default function DatasetsPage() { const [tab, setTab] = useState<"datasets" | "shares">("datasets") const [datasets, setDatasets] = useState([]) const [sambaShares, setSambaShares] = useState([]) const [nfsShares, setNfsShares] = useState([]) const [loading, setLoading] = useState(true) const [expandedDatasets, setExpandedDatasets] = useState>(new Set()) const [poolTabs, setPoolTabs] = useState>(new Map()) // Dialogs const [showCreateDataset, setShowCreateDataset] = useState(false) const [showCreateSambaShare, setShowCreateSambaShare] = useState(false) const [showCreateNfsShare, setShowCreateNfsShare] = useState(false) const [deleteDataset, setDeleteDataset] = useState(null) const [deleteSambaShare, setDeleteSambaShare] = useState(null) const [deleteNfsShare, setDeleteNfsShare] = useState(null) // Form states const [newDatasetName, setNewDatasetName] = useState("") const [newSambaName, setNewSambaName] = useState("") const [newSambaPath, setNewSambaPath] = useState("") const [newSambaComment, setNewSambaComment] = useState("") const [newNfsPath, setNewNfsPath] = useState("") const [newNfsClients, setNewNfsClients] = useState("") const [newNfsOptions, setNewNfsOptions] = useState("ro,sync,no_subtree_check") useEffect(() => { loadData() }, []) const loadData = async () => { setLoading(true) try { const [ds, samba, nfs] = await Promise.all([ api.getDatasets(), api.getSambaShares(), api.getNfsShares(), ]) setDatasets(ds) setSambaShares(samba) setNfsShares(nfs) } catch (err) { console.error("Failed to load data:", err) } finally { setLoading(false) } } const handleCreateDataset = async () => { if (!newDatasetName.trim()) return try { // Create dataset via API await api.createDataset(newDatasetName, {}) setNewDatasetName("") setShowCreateDataset(false) loadData() } catch (err) { console.error("Failed to create dataset:", err) } } const handleDeleteDataset = async (name: string) => { try { await api.deleteDataset(name) setDeleteDataset(null) loadData() } catch (err) { console.error("Failed to delete dataset:", err) } } const handleCreateSambaShare = async () => { if (!newSambaName.trim() || !newSambaPath.trim()) return try { await api.createSambaShare({ name: newSambaName, path: newSambaPath, comment: newSambaComment || undefined, }) setNewSambaName("") setNewSambaPath("") setNewSambaComment("") setShowCreateSambaShare(false) loadData() } catch (err) { console.error("Failed to create Samba share:", err) } } const handleDeleteSambaShare = async (name: string) => { try { await api.deleteSambaShare(name) setDeleteSambaShare(null) loadData() } catch (err) { console.error("Failed to delete Samba share:", err) } } const handleCreateNfsShare = async () => { if (!newNfsPath.trim() || !newNfsClients.trim()) return try { await api.createNfsShare({ path: newNfsPath, clients: newNfsClients, options: newNfsOptions || undefined, }) setNewNfsPath("") setNewNfsClients("") setNewNfsOptions("ro,sync,no_subtree_check") setShowCreateNfsShare(false) loadData() } catch (err) { console.error("Failed to create NFS share:", err) } } const handleDeleteNfsShare = async (path: string) => { try { await api.deleteNfsShare(path) setDeleteNfsShare(null) loadData() } catch (err) { console.error("Failed to delete NFS share:", err) } } const formatBytes = (bytes: number) => { if (bytes === 0) return "0 B" const k = 1024 const sizes = ["B", "KB", "MB", "GB", "TB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i] } const getDatasetDepth = (name: string): number => { return name.split("/").length - 1 } const getTopLevelDatasets = (): Dataset[] => { return datasets.filter((ds) => ds.name.split("/").length === 1) } const getPoolStats = (poolName: string) => { const poolDatasets = datasets.filter((ds) => ds.name === poolName || ds.name.startsWith(poolName + "/")) const totalUsed = poolDatasets.reduce((sum, ds) => sum + (ds.used || 0), 0) const totalAvail = poolDatasets[0]?.avail || 0 const totalSize = totalUsed + totalAvail const usagePercent = totalSize > 0 ? (totalUsed / totalSize) * 100 : 0 return { totalUsed, totalAvail, totalSize, usagePercent } } const getChildDatasets = (parent: string): Dataset[] => { const prefix = parent + "/" return datasets.filter((ds) => ds.name.startsWith(prefix) && ds.name !== parent) } const toggleExpand = (name: string) => { const newExpanded = new Set(expandedDatasets) if (newExpanded.has(name)) { newExpanded.delete(name) } else { newExpanded.add(name) } setExpandedDatasets(newExpanded) } const renderDatasetTree = (parent?: string): React.ReactNode[] => { const items: React.ReactNode[] = [] const datasetList = parent ? getChildDatasets(parent) : getTopLevelDatasets() datasetList.forEach((ds) => { const children = getChildDatasets(ds.name) const isExpanded = expandedDatasets.has(ds.name) const depth = getDatasetDepth(ds.name) items.push(
{children.length > 0 && ( )} {children.length === 0 &&
} {ds.name.split("/").pop()}
{ds.type} {formatBytes(ds.used || 0)} {ds.mountpoint || "—"} {ds.compression || "off"} ) if (isExpanded && children.length > 0) { items.push(...renderDatasetTree(ds.name)) } }) return items } if (loading) { return
Loading...
} return (

Datasets & Shares

{/* Tab Navigation */}
{/* Datasets Tab */} {tab === "datasets" && (
{getTopLevelDatasets().map((pool) => { const stats = getPoolStats(pool.name) const currentPoolTab = poolTabs.get(pool.name) || "filesystems" const childDatasets = getChildDatasets(pool.name) return ( {/* Pool Header */}
{pool.name} ONLINE
{/* Pool Stats Grid */}

Size

{formatBytes(stats.totalSize)}

Allocated

{formatBytes(stats.totalUsed)}

Free

{formatBytes(stats.totalAvail)}

Fragmentation

0%

Usage

{stats.usagePercent.toFixed(1)}%

{/* Usage Bar */}

{stats.usagePercent.toFixed(2)}% Allocated • {(100 - stats.usagePercent).toFixed(2)}% Free

{/* Tabs */}
{/* File Systems Tab */} {currentPoolTab === "filesystems" && (
{childDatasets.map((ds) => ( ))}
Name Type Used Available Mountpoint Compression
{pool.name} {pool.type} {formatBytes(pool.used || 0)} {formatBytes(pool.avail || 0)} {pool.mountpoint || "—"} {pool.compression || "off"}
{ds.name.split("/").pop()} {ds.type} {formatBytes(ds.used || 0)} {formatBytes(ds.avail || 0)} {ds.mountpoint || "—"} {ds.compression || "off"}
)} {/* Snapshots Tab */} {currentPoolTab === "snapshots" && (
See Snapshots page for detailed snapshot management
)} {/* Status Tab */} {currentPoolTab === "status" && (

Health

ONLINE

Mounted

Yes

Record Size

128 KiB

)}
) })} {datasets.length === 0 && (
No datasets found
)}
)} {/* Shares Tab */} {tab === "shares" && (

Samba Shares

{sambaShares.map((share) => ( ))}
Name Path Comment Action
{share.name} {share.path} {share.comment || "—"}
{sambaShares.length === 0 && (
No Samba shares
)}

NFS Shares

{nfsShares.map((share) => ( ))}
Path Clients Options Action
{share.path} {share.clients} {share.options || "—"}
{nfsShares.length === 0 && (
No NFS shares
)}
)}
{/* Create Dataset Dialog */} setShowCreateDataset(false)} title="Create Dataset" > setNewDatasetName(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-4 bg-background text-foreground" />
{/* Delete Dataset Dialog */} setDeleteDataset(null)} title="Delete Dataset" >

Are you sure you want to delete {deleteDataset}? This cannot be undone.

{/* Create Samba Share Dialog */} setShowCreateSambaShare(false)} title="Create Samba Share" > setNewSambaName(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-3 bg-background text-foreground text-sm" /> setNewSambaPath(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-3 bg-background text-foreground text-sm" /> setNewSambaComment(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-4 bg-background text-foreground text-sm" />
{/* Delete Samba Share Dialog */} setDeleteSambaShare(null)} title="Delete Samba Share" >

Are you sure you want to delete {deleteSambaShare}?

{/* Create NFS Share Dialog */} setShowCreateNfsShare(false)} title="Create NFS Share" > setNewNfsPath(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-3 bg-background text-foreground text-sm" /> setNewNfsClients(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-3 bg-background text-foreground text-sm" /> setNewNfsOptions(e.target.value)} className="w-full border border-input rounded px-3 py-2 mb-4 bg-background text-foreground text-sm" />
{/* Delete NFS Share Dialog */} setDeleteNfsShare(null)} title="Delete NFS Share" >

Are you sure you want to delete {deleteNfsShare}?

) }