import { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { sharesAPI, type Share, type CreateShareRequest, type UpdateShareRequest } from '@/api/shares' import { zfsApi, type ZFSDataset } from '@/api/storage' import { Button } from '@/components/ui/button' import { Plus, Search, FolderOpen, Share as ShareIcon, Cloud, Settings, X, ChevronRight, FolderSymlink, Network, Lock, History, Save, Gauge, Server, ChevronDown, Ban } from 'lucide-react' export default function SharesPage() { const queryClient = useQueryClient() const [selectedShare, setSelectedShare] = useState(null) const [searchQuery, setSearchQuery] = useState('') const [showCreateForm, setShowCreateForm] = useState(false) const [activeTab, setActiveTab] = useState<'configuration' | 'permissions' | 'clients'>('configuration') const [showAdvanced, setShowAdvanced] = useState(false) const [formData, setFormData] = useState({ dataset_id: '', nfs_enabled: false, nfs_options: 'rw,sync,no_subtree_check', nfs_clients: [], smb_enabled: false, smb_share_name: '', smb_comment: '', smb_guest_ok: false, smb_read_only: false, smb_browseable: true, }) const [nfsClientInput, setNfsClientInput] = useState('') const { data: shares = [], isLoading } = useQuery({ queryKey: ['shares'], queryFn: sharesAPI.listShares, refetchInterval: 5000, staleTime: 0, }) // Get all datasets for create form const { data: pools = [] } = useQuery({ queryKey: ['storage', 'zfs', 'pools'], queryFn: zfsApi.listPools, }) const [datasets, setDatasets] = useState([]) useEffect(() => { const fetchDatasets = async () => { const allDatasets: ZFSDataset[] = [] for (const pool of pools) { try { const poolDatasets = await zfsApi.listDatasets(pool.id) // Filter only filesystem datasets const filesystems = poolDatasets.filter(ds => ds.type === 'filesystem') allDatasets.push(...filesystems) } catch (err) { console.error(`Failed to fetch datasets for pool ${pool.id}:`, err) } } setDatasets(allDatasets) } if (pools.length > 0) { fetchDatasets() } }, [pools]) const createMutation = useMutation({ mutationFn: sharesAPI.createShare, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shares'] }) setShowCreateForm(false) setFormData({ dataset_id: '', nfs_enabled: false, nfs_options: 'rw,sync,no_subtree_check', nfs_clients: [], smb_enabled: false, smb_share_name: '', smb_comment: '', smb_guest_ok: false, smb_read_only: false, smb_browseable: true, }) alert('Share created successfully!') }, onError: (error: any) => { const errorMessage = error?.response?.data?.error || error?.message || 'Failed to create share' alert(`Error: ${errorMessage}`) console.error('Failed to create share:', error) }, }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateShareRequest }) => sharesAPI.updateShare(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shares'] }) }, }) const filteredShares = shares.filter(share => share.dataset_name.toLowerCase().includes(searchQuery.toLowerCase()) || share.mount_point.toLowerCase().includes(searchQuery.toLowerCase()) ) const handleCreateShare = (e?: React.MouseEvent) => { e?.preventDefault() e?.stopPropagation() console.log('Creating share with data:', formData) if (!formData.dataset_id) { alert('Please select a dataset') return } if (!formData.nfs_enabled && !formData.smb_enabled) { alert('At least one protocol (NFS or SMB) must be enabled') return } // Prepare the data to send const submitData: CreateShareRequest = { dataset_id: formData.dataset_id, nfs_enabled: formData.nfs_enabled, smb_enabled: formData.smb_enabled, } if (formData.nfs_enabled) { submitData.nfs_options = formData.nfs_options || 'rw,sync,no_subtree_check' submitData.nfs_clients = formData.nfs_clients || [] } if (formData.smb_enabled) { submitData.smb_share_name = formData.smb_share_name || '' submitData.smb_comment = formData.smb_comment || '' submitData.smb_guest_ok = formData.smb_guest_ok || false submitData.smb_read_only = formData.smb_read_only || false submitData.smb_browseable = formData.smb_browseable !== undefined ? formData.smb_browseable : true } console.log('Submitting share data:', submitData) createMutation.mutate(submitData) } const handleToggleNFS = (share: Share) => { updateMutation.mutate({ id: share.id, data: { nfs_enabled: !share.nfs_enabled }, }) } const handleToggleSMB = (share: Share) => { updateMutation.mutate({ id: share.id, data: { smb_enabled: !share.smb_enabled }, }) } const handleAddNFSClient = (share: Share) => { if (!nfsClientInput.trim()) return const newClients = [...(share.nfs_clients || []), nfsClientInput.trim()] updateMutation.mutate({ id: share.id, data: { nfs_clients: newClients }, }) setNfsClientInput('') } const handleRemoveNFSClient = (share: Share, client: string) => { const newClients = (share.nfs_clients || []).filter(c => c !== client) updateMutation.mutate({ id: share.id, data: { nfs_clients: newClients }, }) } return (
{/* Header */}

Shares Management

Storage Shares Overview
{/* Status Stats */}

SMB Service

Running

Port 445 Active

NFS Service

Running

Port 2049 Active

Throughput

565 MB/s

14 Clients Connected

{/* Master-Detail Layout */}
{/* Left Panel: Shares List */}
{/* Search */}
setSearchQuery(e.target.value)} />
{/* List Items */}
{isLoading ? (
Loading shares...
) : filteredShares.length === 0 ? (
No shares found
) : ( filteredShares.map((share) => (
setSelectedShare(share)} className={`group flex flex-col border-b border-border-dark/50 cursor-pointer transition-colors ${ selectedShare?.id === share.id ? 'bg-primary/10 border-l-4 border-l-primary' : 'hover:bg-surface-dark' }`} >
{selectedShare?.id === share.id ? ( ) : ( )}

{share.dataset_name}

{share.mount_point || 'No mount point'}

{share.smb_enabled ? ( SMB ) : null} {share.nfs_enabled && ( NFS )}
{selectedShare?.id !== share.id && ( )}
)) )}

Showing {filteredShares.length} of {shares.length} shares

{/* Right Panel: Share Details */}
{selectedShare ? ( <> {/* Detail Header */}

{selectedShare.dataset_name.split('/').pop() || selectedShare.dataset_name}

{selectedShare.dataset_name}

{/* Protocol Toggles */}
{/* SMB Toggle */}
SMB Protocol Windows File Sharing
{/* NFS Toggle */}
NFS Protocol Unix/Linux File Sharing
{/* Tabs */}
{/* Tab Content */}
{activeTab === 'configuration' && ( <> {/* NFS Settings Card */} {selectedShare.nfs_enabled && (

NFS Configuration

Active
setNfsClientInput(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter') { handleAddNFSClient(selectedShare) } }} />

CIDR notation supported. Use comma for multiple entries.

{selectedShare.nfs_clients && selectedShare.nfs_clients.length > 0 && (
{selectedShare.nfs_clients.map((client) => ( {client} ))}
)}
)} {/* SMB Settings Card */} {selectedShare.smb_enabled && (

SMB Configuration

Active
{ updateMutation.mutate({ id: selectedShare.id, data: { smb_share_name: e.target.value }, }) }} />
{ updateMutation.mutate({ id: selectedShare.id, data: { smb_comment: e.target.value }, }) }} />
)} {/* Advanced Attributes */}
{showAdvanced && (
)}
{/* Connected Clients Preview */}

Top Active Clients

View all clients
IP Address User Protocol Throughput Action
192.168.10.105 esxi-host-01 NFS 420 MB/s
192.168.10.106 esxi-host-02 NFS 105 MB/s
)} {activeTab === 'permissions' && (

Permissions (ACL) configuration coming soon

)} {activeTab === 'clients' && (

Connected Clients

IP Address User Protocol Throughput Action
192.168.10.105 esxi-host-01 NFS 420 MB/s
192.168.10.106 esxi-host-02 NFS 105 MB/s
)}
) : (

Select a share to view details

)}
{/* Create Share Modal */} {showCreateForm && (
setShowCreateForm(false)} >
e.stopPropagation()} >

Create New Share

Enable NFS
setFormData({ ...formData, nfs_enabled: e.target.checked })} className="h-4 w-4 rounded border-border-dark bg-background-dark text-primary focus:ring-primary" />
Enable SMB
setFormData({ ...formData, smb_enabled: e.target.checked })} className="h-4 w-4 rounded border-border-dark bg-background-dark text-primary focus:ring-primary" />
{formData.nfs_enabled && (
setFormData({ ...formData, nfs_options: e.target.value })} />
)} {formData.smb_enabled && ( <>
setFormData({ ...formData, smb_share_name: e.target.value })} />
setFormData({ ...formData, smb_comment: e.target.value })} />
)}
)}
) }