Backend: - WebDAV config managed at /etc/apache2/conf-available/micronas-webdav.conf with BEGIN/END markers per share; reload apache2 on every write - htpasswd file at /etc/micronas-webdav.passwd synced automatically on create_user, change_password, delete_user - Full CRUD: POST/PATCH/DELETE /api/share/webdav[/<name>] - _validate_webdav_users(): space-separated, no @group (not supported by Apache) - Fix: _validate_users() now allows @groupname for SMB (was incorrectly rejected) - Dashboard: apache2 status + restart button; webdav shares in /api/dashboard Frontend: - WebDAV tab: create form + table with clickable access URLs - Edit modal supports 'webdav' type (name, path, users, permissions section) - badge-webdav CSS class (green tint) in app.css - dashboard.js renders badge-<type> dynamically (smb/nfs/webdav) Docs: README WebDAV mount guide (Linux davfs2, Windows, Mac), PLANNING phase 4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
3.3 KiB
JavaScript
78 lines
3.3 KiB
JavaScript
async function loadDashboard() {
|
|
const res = await apiFetch('/api/dashboard');
|
|
if (!res) return;
|
|
const d = await res.json();
|
|
|
|
// Service status cards with restart buttons
|
|
function setDaemon(id, service, running) {
|
|
document.getElementById(id).innerHTML =
|
|
`<span class="dot ${running ? 'dot-green' : 'dot-red'}"></span>
|
|
<span class="small me-2">${running ? 'Running' : 'Stopped'}</span>
|
|
<button class="btn btn-sm ms-auto" style="border:1px solid var(--border); color:var(--muted); padding:1px 8px; font-size:.75rem"
|
|
onclick="restartService('${service}')">Restart</button>`;
|
|
document.getElementById(id).style.display = 'flex';
|
|
document.getElementById(id).style.alignItems = 'center';
|
|
}
|
|
setDaemon('statusSmbd', 'smbd', d.status.smbd);
|
|
setDaemon('statusNfsd', 'nfsd', d.status.nfsd);
|
|
setDaemon('statusApache2', 'apache2', d.status.apache2);
|
|
|
|
// Disk bar
|
|
if (d.disk && !d.disk.error) {
|
|
const p = d.disk.percent;
|
|
const bar = document.getElementById('diskBar');
|
|
bar.style.width = p + '%';
|
|
bar.style.background = p > 90 ? 'var(--red)' : p > 75 ? 'var(--yellow)' : 'var(--accent)';
|
|
document.getElementById('diskLabel').textContent =
|
|
`${d.disk.used_gb} GB / ${d.disk.total_gb} GB — ${d.disk.free_gb} GB free`;
|
|
} else if (d.disk?.error) {
|
|
document.getElementById('diskLabel').textContent = 'Error: ' + d.disk.error;
|
|
}
|
|
|
|
// Server IPs
|
|
const ipEl = document.getElementById('serverIps');
|
|
if (ipEl) {
|
|
ipEl.innerHTML = d.ips?.length
|
|
? d.ips.map(i => `<code class="me-3">${esc(i.iface)}: ${esc(i.ip)}</code>`).join('')
|
|
: '<span style="color:var(--muted)">—</span>';
|
|
}
|
|
|
|
// Shares table
|
|
const sb = document.getElementById('dashSharesBody');
|
|
if (!d.shares?.length) {
|
|
sb.innerHTML = `<tr><td colspan="5" style="color:var(--muted)">No shares configured.</td></tr>`;
|
|
} else {
|
|
sb.innerHTML = d.shares.map(s => `
|
|
<tr>
|
|
<td><span class="badge rounded-pill badge-${s.type}">${s.type.toUpperCase()}</span></td>
|
|
<td>${esc(s.name ?? '—')}</td>
|
|
<td><code>${esc(s.path)}</code></td>
|
|
<td style="color:var(--muted); font-size:.87em">${esc(s.access || '—')}</td>
|
|
<td class="text-end" style="color:var(--muted)">${s.size_gb} GB</td>
|
|
</tr>`).join('');
|
|
}
|
|
|
|
// Active connections
|
|
const connEl = document.getElementById('activeConnectionsBody');
|
|
if (connEl) {
|
|
connEl.innerHTML = d.connections?.length
|
|
? d.connections.map(c => `
|
|
<tr>
|
|
<td>${esc(c.user)}</td>
|
|
<td style="color:var(--muted)">${esc(c.machine)}</td>
|
|
</tr>`).join('')
|
|
: `<tr><td colspan="2" style="color:var(--muted)">No active connections.</td></tr>`;
|
|
}
|
|
|
|
_startRefreshCountdown();
|
|
}
|
|
|
|
async function restartService(name) {
|
|
const res = await apiFetch(`/api/service/${name}/restart`, { method: 'POST' });
|
|
if (!res) return;
|
|
const data = await res.json();
|
|
if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger');
|
|
showToast(`${name} restarted.`);
|
|
loadDashboard();
|
|
}
|