/* * SysDeck - Remote FS Panel (v0.0.35) * Author: Jeremy Anderson (https://dcos.net) * * v0.0.35 directive: "then a remote fs manager such as ceph, and * others but not nfs or amanada fs." Manages remote / distributed * filesystem backends as systemd services and surfaces cluster * status from each backend's CLI tool. Each backend card has * Start / Stop / Restart controls + a cluster-info viewer. * * Backends shipped (see bridge/remotefs.py BACKEND_REGISTRY): * Ceph — distributed object storage · LGPL-2.1 * GlusterFS — scale-out network filesystem · GPL-2.0 * MooseFS — distributed fault-tolerant FS · GPL-2.0 * BeeGFS — parallel cluster filesystem · BeeGFS EULA (free) * OrangeFS — parallel FS (PVFS2 successor) · BSD-3 * * EXCLUDED per directive — documented in the panel footer: * NFS — kernel-builtin; no cluster; no remote-FS-as-data-store * Amanda — backup system, not a remote/distributed filesystem * * Bridge surface (see shared/bridge.js → bridge.remotefs): * summary() → {backends, totalBackends, installedCount, runningCount, excluded} * status(id) → single-backend detail dict * start(id) → {action, rc, success, output, stderr} * stop(id) → same shape * restart(id) → same shape * clusterInfo(id) → backend-specific cluster status */ export async function mount(panel, { bridge, EventBus }) { panel.innerHTML = renderSkeleton(); let summary = {}; try { summary = await bridge.remotefs.summary(); } catch (err) { panel.innerHTML = renderError(err); return; } const backends = summary.backends || []; const installed = backends.filter((b) => b.status !== 'uninstalled'); const running = backends.filter((b) => b.status === 'running'); panel.innerHTML = `

Remote Filesystems

Distributed & cluster filesystem management · ${summary.totalBackends || backends.length} backends supported · ${summary.installedCount || installed.length} installed · ${summary.runningCount || running.length} running

Backends

${summary.totalBackends || backends.length}
total supported

Installed

${installed.length}
${installed.length ? 'detected on host' : 'none — install one'}

Running

${running.length}
${running.length ? 'clusters live' : 'all stopped'}
${renderBackendsGrid(backends, { bridge, EventBus })} ${renderExcludedCard(summary.excluded)}

Cluster Info

Click 🔍 Cluster Info on a backend card above to query its cluster status (ceph status --format=json, gluster pool list, moosefs-cli info, beegfs-ctl --listnodes, or pvfs2-server -m). The output renders below.

(no cluster info requested yet)
`; wireEvents(panel, { bridge, EventBus }); EventBus.emit('remotefs.loaded', { installed: installed.length, running: running.length }); } // ── Backend cards ────────────────────────────────────────────────── function renderBackendsGrid(backends, { bridge, EventBus }) { if (!backends.length) { return `

Backends (0)

No remote FS backends registered.

`; } return backends.map((b) => renderBackendCard(b)).join(''); } function renderBackendCard(b) { if (b.status === 'uninstalled') { return `

${escapeHtml(b.name)} · ${escapeHtml(b.id)}

${escapeHtml(b.family)}

Not installed. ${escapeHtml(b.name)} is ${escapeHtml(b.license)} licensed — ${escapeHtml(b.homepage)}

${escapeHtml(b.installHint)}

`; } const running = b.status === 'running'; return `

${escapeHtml(b.name)} · ${escapeHtml(b.id)}

Status${statusBadge(b.status)}
Service${escapeHtml(b.serviceUnit || '—')}
CLI tool${escapeHtml(b.cli || '—')}
Default port${b.port}
Config${escapeHtml(b.configPath || '—')}
License${escapeHtml(b.license)} · ${escapeHtml(b.homepage)}
`; } function renderExcludedCard(excluded) { if (!excluded || !Object.keys(excluded).length) return ''; const rows = Object.entries(excluded).map(([name, reason]) => ` ${escapeHtml(name)} ${escapeHtml(reason)} `).join(''); return `

Intentionally excluded

per directive

Per v0.0.35 directive: "and others but not nfs or amanada fs." The following backends are intentionally excluded from this module:

${rows}
BackendReason
`; } // ── Event wiring ──────────────────────────────────────────────────── function wireEvents(panel, { bridge, EventBus }) { const output = panel.querySelector('#remotefs-cluster-output'); const showCluster = (title, text, isError = false) => { if (!output) return; output.textContent = `${title}\n\n${text}`; output.style.color = isError ? 'var(--sysdeck-accent-danger)' : 'var(--sysdeck-fg)'; }; const action = async (btn, method, label) => { const id = btn.dataset.id; if (!id) return; btn.disabled = true; const original = btn.textContent; btn.textContent = `${label} ...`; try { const r = await bridge.remotefs[method](id); if (r?.error) { alert(`${label} ${id} failed:\n${r.error}`); } else if (!r?.success && method !== 'restart') { alert(`${label} ${id} failed:\n${r?.stderr || r?.output || 'unknown'}`); } setTimeout(() => mount(panel, { bridge, EventBus }), 1000); } catch (err) { btn.disabled = false; btn.textContent = original; alert(`${label} ${id} error: ${err.message || err}`); } }; panel.querySelectorAll('.btn-remotefs-start').forEach((btn) => { btn.addEventListener('click', (ev) => action(ev.currentTarget, 'start', 'Start')); }); panel.querySelectorAll('.btn-remotefs-stop').forEach((btn) => { btn.addEventListener('click', (ev) => action(ev.currentTarget, 'stop', 'Stop')); }); panel.querySelectorAll('.btn-remotefs-restart').forEach((btn) => { btn.addEventListener('click', (ev) => action(ev.currentTarget, 'restart', 'Restart')); }); panel.querySelectorAll('.btn-remotefs-cluster').forEach((btn) => { btn.addEventListener('click', async (ev) => { const id = ev.currentTarget.dataset.id; if (!id) return; showCluster(`Cluster Info: ${id}`, 'Loading ...'); try { const r = await bridge.remotefs.clusterInfo(id); if (r.error) { showCluster(`Cluster Info: ${id} — error`, r.error, true); return; } if (r.summary) { showCluster(`Cluster Info: ${id} (parsed)`, JSON.stringify(r.summary, null, 2) + '\n\n--- raw ---\n' + (r.rawText || '')); } else { showCluster(`Cluster Info: ${id} (raw)`, r.rawText || JSON.stringify(r, null, 2)); } } catch (err) { showCluster(`Cluster Info: ${id} — error`, String(err.message || err), true); } }); }); panel.querySelectorAll('.btn-remotefs-refresh').forEach((btn) => { btn.addEventListener('click', () => { mount(panel, { bridge, EventBus }); }); }); } // ── Utilities ─────────────────────────────────────────────────────── function statusBadge(status) { const map = { 'running': 'running', 'starting': 'starting', 'stopped': 'stopped', 'error': 'error', 'uninstalled': 'uninstalled', 'unknown': 'unknown', }; return map[status] || `${escapeHtml(status || 'unknown')}`; } function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function renderSkeleton() { return `
`; } function renderError(err) { return `

Remote FS bridge unavailable

${escapeHtml(String(err.message || err))}. Ensure the bridge helper is installed at /usr/lib/sysdeck/bridge/remotefs.py.

`; }