async function loadShares() { const res = await apiFetch('/api/shares'); if (!res) return; const shares = await res.json(); const sb = document.getElementById('smbTableBody'); if (!shares.smb?.length) { sb.innerHTML = `No SMB shares.`; } else { sb.innerHTML = shares.smb.map(s => { const enabled = s.enabled !== false; const dot = ``; // esc() HTML-encodes JSON so " inside onclick="..." doesn't break the attribute const shareJson = esc(JSON.stringify(s)); const nameJson = esc(JSON.stringify(s.name)); return ` ${esc(s.name)} ${esc(s.path)} ${esc(s.valid_users || '—')} ${dot} `; }).join(''); } const nb = document.getElementById('nfsTableBody'); if (!shares.nfs?.length) { nb.innerHTML = `No NFS exports.`; } else { nb.innerHTML = shares.nfs.map(s => { const enabled = s.enabled !== false; const dot = ``; const shareJson = esc(JSON.stringify(s)); const pathJson = esc(JSON.stringify(s.path)); return ` ${esc(s.path)} ${esc(s.clients)} ${dot} `; }).join(''); } } async function addSMBShare() { const name = document.getElementById('smbName').value.trim(); const path = document.getElementById('smbPath').value.trim(); const validUsers = document.getElementById('smbUsers').value.trim(); if (!name || !path) return showToast('Name and path are required.', 'danger'); const res = await apiFetch('/api/share/smb', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({name, path, validUsers}), }); if (!res) return; const data = await res.json(); if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger'); showToast('SMB share created.'); document.getElementById('smbName').value = ''; document.getElementById('smbPath').value = ''; document.getElementById('smbUsers').value = ''; loadShares(); loadDashboard(); } async function addNFSShare() { const path = document.getElementById('nfsPath').value.trim(); const clients = document.getElementById('nfsClients').value.trim() || '*'; const readOnly = document.getElementById('nfsReadOnly').checked; if (!path) return showToast('Path is required.', 'danger'); const res = await apiFetch('/api/share/nfs', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({path, clients, readOnly}), }); if (!res) return; const data = await res.json(); if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger'); showToast('NFS export created.'); document.getElementById('nfsPath').value = ''; document.getElementById('nfsClients').value = ''; document.getElementById('nfsReadOnly').checked = false; loadShares(); loadDashboard(); } // ===== EDIT ===== let _editCtx = null; async function openEditModal(type, share) { _editCtx = { type, original: share }; document.getElementById('editModalTitle').textContent = type === 'smb' ? `Edit SMB Share — ${esc(share.name)}` : `Edit NFS Export — ${esc(share.path)}`; document.getElementById('editModalBody').innerHTML = `
Loading…
`; new bootstrap.Modal(document.getElementById('editModal')).show(); const [ur, gr] = await Promise.all([apiFetch('/api/users'), apiFetch('/api/groups')]); const users = ur ? await ur.json() : []; const groups = gr ? await gr.json() : []; const ownerOptions = ['root', ...users.map(u => u.name)] .map(n => `