Add dashboard IPs/restart/connections, SMB read-only and recycle bin
Dashboard: - Server IPs card showing all non-loopback IPv4 interfaces - Restart buttons next to smbd/nfsd status dots (POST /api/service/<name>/restart) - Active SMB connections table (smbstatus -b parsed in backend) SMB edit modal: - Read only toggle (writable vs read only in smb.conf) - Recycle Bin toggle (vfs objects = recycle, files go to .recycle folder) Docs: PLANNING.md phase 3.5, README recycle bin + read-only, CLAUDE.md JS overview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
1810d60d21
commit
1be6a05cef
7 files changed
+171
-21
No files matched your search
+40
-4
@@ -3,14 +3,20 @@ async function loadDashboard() {
|
||||
if (!res) return;
|
||||
const d = await res.json();
|
||||
|
||||
function setDaemon(id, running) {
|
||||
// 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">${running ? 'Running' : 'Stopped'}</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', d.status.smbd);
|
||||
setDaemon('statusNfsd', d.status.nfsd);
|
||||
setDaemon('statusSmbd', 'smbd', d.status.smbd);
|
||||
setDaemon('statusNfsd', 'nfsd', d.status.nfsd);
|
||||
|
||||
// Disk bar
|
||||
if (d.disk && !d.disk.error) {
|
||||
const p = d.disk.percent;
|
||||
const bar = document.getElementById('diskBar');
|
||||
@@ -22,6 +28,15 @@ async function loadDashboard() {
|
||||
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>`;
|
||||
@@ -36,5 +51,26 @@ async function loadDashboard() {
|
||||
</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();
|
||||
}
|
||||
Reference in new issue
Block a user