v0.20.0:0 - per-spark ssh-key copy + wireguard status badge
This commit is contained in:
+44
-4
@@ -305,6 +305,32 @@ async function wakeSpark(name) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate-if-missing + copy this Spark's OUTBOUND ssh public key (the key the
|
||||
// Spark uses to log in to other machines, e.g. the Mac). Distinct from the
|
||||
// package's own key in the StartOS "Show Public Key" action.
|
||||
async function copySparkSshKey(name, btn) {
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const r = await fetchJSON(`/api/spark/${name}/ssh-key`, { method: 'POST' });
|
||||
// Best-effort clipboard copy; on plain-HTTP this no-ops, but the dialog
|
||||
// below always shows the key for manual selection.
|
||||
await copyText(r.pubkey, btn);
|
||||
const label = r.host ? `${name} (${r.host})` : name;
|
||||
el('#sshkey-title').textContent = `${name} — SSH public key`;
|
||||
el('#sshkey-intro').textContent = r.created
|
||||
? `Generated a new SSH key on ${label} and copied it to your clipboard. This is the key ${name} uses to log in to OTHER machines.`
|
||||
: `${label} already had an SSH key; copied its public key to your clipboard. This is the key ${name} uses to log in to OTHER machines.`;
|
||||
el('#sshkey-value').textContent = r.pubkey;
|
||||
el('#sshkey-install').textContent =
|
||||
`mkdir -p ~/.ssh && echo '${r.pubkey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`;
|
||||
el('#sshkey-dialog').showModal();
|
||||
} catch (e) {
|
||||
alert(`Couldn't get the SSH key for ${name}: ${e.message}`);
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderHardware() {
|
||||
const panel = el('#hardware-panel');
|
||||
const grid = el('#hardware-grid');
|
||||
@@ -358,11 +384,21 @@ function renderHardware() {
|
||||
if (s.gpu_temp_c != null) gpuExtras.push(`${s.gpu_temp_c}°C`);
|
||||
if (s.gpu_power_w != null) gpuExtras.push(`${s.gpu_power_w.toFixed(0)}W`);
|
||||
const gpuExtrasStr = gpuExtras.length ? ` · ${gpuExtras.join(' · ')}` : '';
|
||||
// Read-only WireGuard badge: shown only when the Spark has a wg interface up.
|
||||
// "VPN <ip>" means it's a peer on that tunnel (reachable off-LAN when the
|
||||
// tunnel is up); it reflects interface presence, not live peer reachability.
|
||||
const wgIp = s.wg_addr ? String(s.wg_addr).split('/')[0] : '';
|
||||
const wgBadge = s.wg_iface
|
||||
? ` · <span class="wg-badge" title="On WireGuard tunnel '${escapeHtml(s.wg_iface)}'${wgIp ? ' as ' + escapeHtml(wgIp) : ''} — reachable off-LAN while the tunnel is up">VPN${wgIp ? ' ' + escapeHtml(wgIp) : ''}</span>`
|
||||
: '';
|
||||
card.className = 'hw-card';
|
||||
card.innerHTML = `
|
||||
<div class="head">
|
||||
<span class="name">${escapeHtml(s.hostname || key)}</span>
|
||||
<span class="meta">${escapeHtml(key)} · ${escapeHtml(s.gpu_name || '')} · ${escapeHtml(s.uptime || '')}</span>
|
||||
<span class="meta">${escapeHtml(key)} · ${escapeHtml(s.gpu_name || '')} · ${escapeHtml(s.uptime || '')}${wgBadge}</span>
|
||||
<button class="icon-btn ssh-key-btn" data-ssh-key="${escapeHtml(key)}" title="Copy this Spark's SSH public key (creates one if it doesn't have one) — e.g. to let it log in to your Mac" aria-label="Copy SSH public key">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hw-metric">
|
||||
<span class="label">CPU</span>
|
||||
@@ -1849,11 +1885,15 @@ async function init() {
|
||||
el('#nim-prog-close').addEventListener('click', () => el('#nim-progress-dialog').close());
|
||||
el('#open-connectivity').addEventListener('click', openConnectivityDialog);
|
||||
el('#connectivity-close').addEventListener('click', () => el('#connectivity-dialog').close());
|
||||
// Wake-on-LAN buttons live on unreachable hardware cards; delegate.
|
||||
// Hardware-card buttons (Wake-on-LAN on unreachable cards; SSH-key copy on
|
||||
// reachable ones) are rendered dynamically, so delegate from the grid.
|
||||
el('#hardware-grid').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-wake]');
|
||||
if (btn) wakeSpark(btn.dataset.wake);
|
||||
const wbtn = e.target.closest('[data-wake]');
|
||||
if (wbtn) { wakeSpark(wbtn.dataset.wake); return; }
|
||||
const kbtn = e.target.closest('[data-ssh-key]');
|
||||
if (kbtn) { copySparkSshKey(kbtn.dataset.sshKey, kbtn); return; }
|
||||
});
|
||||
el('#sshkey-close').addEventListener('click', () => el('#sshkey-dialog').close());
|
||||
setupCatalogDialog();
|
||||
setupAdvancedDialog();
|
||||
// Open WebUI link from /api/config
|
||||
|
||||
Reference in New Issue
Block a user