Refactor dashboard to remove network boot support and update related UI elements</message>

<message>Eliminate network boot options from the dashboard, including API endpoints and UI elements, to streamline the provisioning process for USB boot only. Update messages and documentation to reflect the removal of network boot functionality, ensuring clarity for users. Adjust the cloud-init build process and related templates to focus solely on USB boot mode, enhancing the overall user experience and simplifying the workflow.
This commit is contained in:
nearxos
2026-02-23 11:08:52 +02:00
parent 55b8661a2e
commit ca27727137
5 changed files with 54 additions and 198 deletions

View File

@@ -146,7 +146,7 @@
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
if (btn) btn.disabled = busy;
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
if (dismissEl) dismissEl.style.display = (d.phase === 'done' || d.phase === 'error' || d.phase === 'cancelled') ? 'inline' : 'none';
if (dismissEl) dismissEl.style.display = (d.phase !== 'idle' || (d.message && d.message.trim())) ? 'inline' : 'none';
if (d.phase === 'idle' && !d.message) el.textContent = '';
else if (d.phase === 'done') { el.textContent = 'Done: ' + (d.output_name || '') + ' — see Admin page Cloud-init images.'; }
else if (d.phase === 'error') el.textContent = 'Error: ' + (d.error || '');

View File

@@ -129,10 +129,9 @@
<div class="card">
<h2 class="card-title">Capture or deploy</h2>
<p id="dhcpNetbootWrap" class="status-row" style="margin-bottom: 0.5rem; font-size: 0.85rem;"><span class="text-dim">Network boot (DHCP):</span> <span id="dhcpNetbootState"></span> <button type="button" id="dhcpNetbootDisableBtn" class="btn btn-outline btn-sm" style="display:none;">Disable network boot</button> <button type="button" id="dhcpNetbootEnableBtn" class="btn btn-outline btn-sm" style="display:none;">Enable network boot</button></p>
<p id="shrinkOptionWrap" style="display:none; margin-bottom: 0.5rem; font-size: 0.8rem;"><label><input type="checkbox" id="shrinkAfterBackup" /> Shrink after backup</label></p>
<div id="pendingDevices"></div>
<p id="noPending" class="empty-msg" style="display:none;">No device connected. Use USB boot mode or register over network.</p>
<p id="noPending" class="empty-msg" style="display:none;">No device connected. Use USB boot mode.</p>
</div>
</div>
@@ -167,11 +166,6 @@
<li><span class="num">2</span> Connect USB to host; choose <strong>Backup</strong> or <strong>Deploy</strong> above.</li>
<li><span class="num">3</span> Remove jumper and power cycle when done.</li>
</ol>
<p class="help-sub">Network</p>
<ol class="steps-list">
<li><span class="num">1</span> Enable network boot; device must reach this server.</li>
<li><span class="num">2</span> Boot with provisioning client; choose <strong>Backup</strong> or <strong>Deploy</strong>.</li>
</ol>
</div>
</details>
</div>
@@ -210,16 +204,9 @@
shrinkWrap.style.display = 'block';
const el = document.createElement('div');
el.className = 'device-item';
el.innerHTML = '<div class="device-desc">USB device — choose Backup, Deploy, or Update EEPROM</div><div class="device-actions-row"><button type="button" class="btn btn-outline btn-sm" data-source="usb" data-action="backup">Backup</button> <button type="button" class="btn btn-primary btn-sm" data-source="usb" data-action="deploy">Deploy</button> <select class="eeprom-preset" title="Boot order"><option value="0xf21">eMMC first, then network</option><option value="0x1">eMMC only</option><option value="0xf12">Network first, then eMMC</option></select> <button type="button" class="btn btn-outline btn-sm" data-source="usb" data-action="eeprom_update">Update EEPROM</button></div>';
el.innerHTML = '<div class="device-desc">USB device — choose Backup, Deploy, or Update EEPROM</div><div class="device-actions-row"><button type="button" class="btn btn-outline btn-sm" data-source="usb" data-action="backup">Backup</button> <button type="button" class="btn btn-primary btn-sm" data-source="usb" data-action="deploy">Deploy</button> <select class="eeprom-preset" title="Boot order"><option value="0x1">eMMC only</option></select> <button type="button" class="btn btn-outline btn-sm" data-source="usb" data-action="eeprom_update">Update EEPROM</button></div>';
container.appendChild(el);
} else shrinkWrap.style.display = 'none';
(network || []).forEach(function(d) {
hasAny = true;
const el = document.createElement('div');
el.className = 'device-item';
el.innerHTML = '<div class="device-desc">' + escapeHtml(d.ip || '') + ' · ' + escapeHtml(d.mac || '') + '</div><div><button type="button" class="btn btn-outline btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="backup">Backup</button> <button type="button" class="btn btn-primary btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="deploy">Deploy</button> <button type="button" class="btn btn-outline btn-sm btn-disable-netboot" title="Stop advertising network boot via DHCP so devices boot from eMMC">Disable network boot</button></div>';
container.appendChild(el);
});
noPending.style.display = hasAny ? 'none' : 'block';
container.querySelectorAll('button[data-action]').forEach(function(btn) {
btn.onclick = function() {
@@ -227,20 +214,12 @@
if (body.source === 'network') body.mac = btn.getAttribute('data-mac');
if (body.action === 'eeprom_update' && body.source === 'usb') {
const presetEl = btn.closest('.device-item') && btn.closest('.device-item').querySelector('.eeprom-preset');
body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0xf21';
body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0x1';
}
if (body.action === 'backup' && document.getElementById('shrinkAfterBackup').checked) body.shrink = true;
fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
.then(function(r) { return r.json(); })
.then(function(data) { if (data.ok) { fetchPending(); fetchStatus(); fetchDhcpNetboot(); } else alert(data.error || 'Failed'); })
.catch(function() { alert('Request failed'); });
};
});
container.querySelectorAll('button.btn-disable-netboot').forEach(function(btn) {
btn.onclick = function() {
fetch('/api/dhcp-network-boot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: false }) })
.then(function(r) { return r.json(); })
.then(function(data) { if (data.ok) { fetchDhcpNetboot(); } else alert(data.error || 'Failed'); })
.then(function(data) { if (data.ok) { fetchPending(); fetchStatus(); } else alert(data.error || 'Failed'); })
.catch(function() { alert('Request failed'); });
};
});
@@ -298,17 +277,6 @@
el.innerHTML = t;
}).catch(function(){ document.getElementById('goldenInfo').textContent = 'Could not load.'; });
}
function fetchDhcpNetboot() {
fetch('/api/dhcp-network-boot').then(function(r){ return r.json(); }).then(function(d){
const stateEl = document.getElementById('dhcpNetbootState');
const disableBtn = document.getElementById('dhcpNetbootDisableBtn');
const enableBtn = document.getElementById('dhcpNetbootEnableBtn');
if (d.error) { stateEl.textContent = '—'; if (disableBtn) disableBtn.style.display = 'none'; if (enableBtn) enableBtn.style.display = 'none'; return; }
stateEl.textContent = d.enabled ? 'on' : 'off';
if (disableBtn) disableBtn.style.display = d.enabled ? 'inline-block' : 'none';
if (enableBtn) enableBtn.style.display = d.enabled ? 'none' : 'inline-block';
}).catch(function(){ document.getElementById('dhcpNetbootState').textContent = '—'; });
}
var doneClearTimer = null;
function scheduleDoneClear() {
if (doneClearTimer) return;
@@ -331,25 +299,12 @@
}).catch(function(){ document.getElementById('leasesError').textContent = 'Could not load leases.'; document.getElementById('leasesError').style.display = 'block'; });
}
document.getElementById('statusClearBtn').addEventListener('click', function(){ fetch('/api/status-clear', { method: 'POST' }).then(function(r){ return r.json(); }).then(function(d){ if(d.ok) fetchStatus(); }); });
document.getElementById('dhcpNetbootDisableBtn').addEventListener('click', function(){
fetch('/api/dhcp-network-boot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: false }) })
.then(function(r){ return r.json(); })
.then(function(d){ if(d.ok) fetchDhcpNetboot(); else alert(d.error || 'Failed'); })
.catch(function(){ alert('Request failed'); });
});
document.getElementById('dhcpNetbootEnableBtn').addEventListener('click', function(){
fetch('/api/dhcp-network-boot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: true }) })
.then(function(r){ return r.json(); })
.then(function(d){ if(d.ok) fetchDhcpNetboot(); else alert(d.error || 'Failed'); })
.catch(function(){ alert('Request failed'); });
});
fetchStatus(); fetchLog(); fetchPending(); fetchGolden(); fetchDhcpNetboot(); fetchDhcpLeases(); fetchFirstBootStatus();
fetchStatus(); fetchLog(); fetchPending(); fetchGolden(); fetchDhcpLeases(); fetchFirstBootStatus();
setInterval(fetchStatus, 2000);
setInterval(fetchLog, 4000);
setInterval(fetchPending, 2000);
setInterval(fetchFirstBootStatus, 3000);
setInterval(fetchGolden, 10000);
setInterval(fetchDhcpNetboot, 10000);
setInterval(fetchDhcpLeases, 10000);
</script>
</body>

View File

@@ -360,7 +360,7 @@
<div class="wrap">
<header class="header">
<h1>CM4 eMMC Provisioning</h1>
<p>Deploy or backup reTerminal via USB boot mode or network</p>
<p>Deploy or backup reTerminal via USB boot mode</p>
</header>
<!-- 1. Current status -->
@@ -373,7 +373,7 @@
</div>
<div id="statusErr" class="status-err" style="display:none;"></div>
<div id="statusGoldenHint" class="backup-deploy-hint" style="display:none; margin-top:0.75rem;">
No golden image is required to <strong>capture</strong>. Connect a device in USB boot mode (or register over network); when it appears under “Capture image or deploy”, click <strong>Backup</strong> to save its image. Then set that backup as golden in the list below.
No golden image is required to <strong>capture</strong>. Connect a device in USB boot mode; when it appears under “Capture image or deploy”, click <strong>Backup</strong> to save its image. Then set that backup as golden in the list below.
<button type="button" id="statusClearHintBtn" class="btn btn-outline btn-sm" style="margin-left:0.5rem;">Clear message</button>
</div>
<div id="statusMeta" class="status-meta" style="display:none;"></div>
@@ -385,7 +385,7 @@
<!-- 2. Capture (Backup) or Deploy -->
<section class="section">
<h2 class="section-title">Capture image or deploy</h2>
<p class="backup-deploy-hint">To <strong>capture (backup)</strong> an image from a device: connect it in USB boot mode or register over network. When the device appears below, click <strong>Backup</strong> to save its eMMC to a file. To write an image to a device, click <strong>Deploy</strong> (requires a golden image).</p>
<p class="backup-deploy-hint">To <strong>capture (backup)</strong> an image from a device: connect it in USB boot mode. When the device appears below, click <strong>Backup</strong> to save its eMMC to a file. To write an image to a device, click <strong>Deploy</strong> (requires a golden image).</p>
<p id="shrinkOptionWrap" class="backup-deploy-hint" style="display:none; margin-top:0.5rem;">
<label><input type="checkbox" id="shrinkAfterBackup" /> Shrink after backup</label> <span class="backups-mono" style="font-size:0.8rem;">(reduces size; requires PiShrink on host)</span>
</p>
@@ -396,9 +396,9 @@
<button type="button" class="btn btn-outline btn-sm" disabled>Backup</button>
<button type="button" class="btn btn-primary btn-sm" disabled>Deploy</button>
</span>
<span>— USB boot mode or network registration</span>
<span>— USB boot mode</span>
</div>
<p id="noPending" class="empty-msg" style="display:none;">No device connected. Connect reTerminal in USB boot mode (eMMC disable jumper + USB to host) or register a network-booted device — then the <strong>Backup</strong> and <strong>Deploy</strong> buttons will appear above.</p>
<p id="noPending" class="empty-msg" style="display:none;">No device connected. Connect reTerminal in USB boot mode (eMMC disable jumper + USB to host) — then the <strong>Backup</strong> and <strong>Deploy</strong> buttons will appear above.</p>
</section>
<!-- 3. Saved backups -->
@@ -481,11 +481,6 @@
<li><span class="num">2</span> Connect <strong>USB slave</strong> to the host and power on. The device appears above; choose <strong>Backup</strong> or <strong>Deploy</strong>.</li>
<li><span class="num">3</span> When done, remove the jumper and power cycle to boot from eMMC.</li>
</ol>
<p class="help-sub">Network boot</p>
<ol class="steps-list">
<li><span class="num">1</span> Enable network boot (e.g. <code style="background:var(--bg-tertiary);padding:0.15rem 0.35rem;border-radius:4px;">BOOT_ORDER=0xf21</code>) and ensure the device can reach this server.</li>
<li><span class="num">2</span> Boot with the provisioning client; it will show above. Choose <strong>Backup</strong> or <strong>Deploy</strong>.</li>
</ol>
</div>
</details>
@@ -571,18 +566,11 @@
if (shrinkWrap) shrinkWrap.style.display = 'block';
const el = document.createElement('div');
el.className = 'device-item';
el.innerHTML = '<div class="device-info"><div class="device-type">USB boot</div><div class="device-desc">Device connected — choose Backup, Deploy, or Update EEPROM</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="usb" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="usb" data-action="deploy">Deploy</button><select class="eeprom-preset" title="Boot order"><option value="0xf21">eMMC first, then network</option><option value="0x1">eMMC only</option><option value="0xf12">Network first, then eMMC</option></select><button type="button" class="btn btn-outline" data-source="usb" data-action="eeprom_update">Update EEPROM</button></div>';
el.innerHTML = '<div class="device-info"><div class="device-type">USB boot</div><div class="device-desc">Device connected — choose Backup, Deploy, or Update EEPROM</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="usb" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="usb" data-action="deploy">Deploy</button><select class="eeprom-preset" title="Boot order"><option value="0x1">eMMC only</option></select><button type="button" class="btn btn-outline" data-source="usb" data-action="eeprom_update">Update EEPROM</button></div>';
container.appendChild(el);
} else {
if (shrinkWrap) shrinkWrap.style.display = 'none';
}
(network || []).forEach(function(d) {
hasAny = true;
const el = document.createElement('div');
el.className = 'device-item';
el.innerHTML = '<div class="device-info"><div class="device-type">Network</div><div class="device-desc">' + escapeHtml(d.ip || '') + ' · ' + escapeHtml(d.mac || '') + '</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="deploy">Deploy</button></div>';
container.appendChild(el);
});
const placeholder = document.getElementById('noPendingPlaceholder');
noPending.style.display = hasAny ? 'none' : 'block';
@@ -597,7 +585,7 @@
if (mac) body.mac = mac;
if (action === 'eeprom_update' && source === 'usb') {
const presetEl = btn.closest('.device-item') && btn.closest('.device-item').querySelector('.eeprom-preset');
body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0xf21';
body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0x1';
}
const shrinkCb = document.getElementById('shrinkAfterBackup');
if (action === 'backup' && shrinkCb && shrinkCb.checked) body.shrink = true;
@@ -902,7 +890,7 @@
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
if (btn) btn.disabled = busy;
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
if (dismissEl) dismissEl.style.display = (d.phase === 'done' || d.phase === 'error' || d.phase === 'cancelled') ? 'inline' : 'none';
if (dismissEl) dismissEl.style.display = (d.phase !== 'idle' || (d.message && d.message.trim())) ? 'inline' : 'none';
if (d.phase === 'idle' && !d.message) {
el.textContent = '';
} else if (d.phase === 'done') {