Enhance dashboard UI for EEPROM update functionality

Update the home and index templates to include the new EEPROM update action in the device management interface. Modify status handling to reflect the addition of the 'eeprom_update' phase, ensuring proper user feedback during operations. Introduce a clear status button and improve the layout for device actions, enhancing overall user experience and interaction with the dashboard.
This commit is contained in:
nearxos
2026-02-21 16:18:07 +02:00
parent b39e73324f
commit a915e5a405
2 changed files with 21 additions and 10 deletions

View File

@@ -43,7 +43,7 @@
.status-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.status-pill { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; padding: 0.25rem 0.6rem; border-radius: 999px; }
.status-pill.idle { background: rgba(0,212,170,0.15); color: var(--accent); }
.status-pill.waiting_choice, .status-pill.rpiboot, .status-pill.flashing, .status-pill.backup { background: rgba(251,191,36,0.15); color: var(--warn); }
.status-pill.waiting_choice, .status-pill.rpiboot, .status-pill.flashing, .status-pill.backup, .status-pill.eeprom_update { background: rgba(251,191,36,0.15); color: var(--warn); }
.status-pill.done { background: rgba(0,212,170,0.15); color: var(--success); }
.status-pill.error { background: rgba(248,113,113,0.15); color: var(--danger); }
.status-msg { flex: 1; min-width: 0; font-size: 0.9rem; }
@@ -58,6 +58,8 @@
margin-bottom: 0.4rem;
}
.device-desc { font-size: 0.85rem; color: var(--text-dim); }
.device-actions-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.eeprom-preset { padding: 0.3rem 0.5rem; font-size: 0.75rem; font-family: inherit; background: var(--bg-primary); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); max-width: 11rem; }
.btn { padding: 0.4rem 0.8rem; font-size: 0.8rem; font-weight: 500; font-family: inherit; border-radius: var(--radius-sm); cursor: pointer; border: none; transition: background 0.15s, color 0.15s; }
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
@@ -100,9 +102,9 @@
<div id="status" class="status-row">
<span id="statusPill" class="status-pill idle">Idle</span>
<span id="statusMsg" class="status-msg">Waiting for device</span>
<button type="button" id="statusClearBtn" class="btn btn-outline btn-sm" style="margin-left: auto;">Clear status</button>
</div>
<div id="statusErr" class="status-err" style="display:none;"></div>
<div id="statusClearWrap" style="display:none; margin-top:0.5rem;"><button type="button" id="statusClearBtn" class="btn btn-outline btn-sm">Clear message</button></div>
<div id="progressWrap" class="progress-track" style="display:none;"><div id="progressFill" class="progress-fill"></div></div>
</div>
@@ -158,7 +160,7 @@
</div>
<script>
const phaseLabels = { idle: 'Idle', rpiboot: 'Connecting', waiting_choice: 'Choose action', flashing: 'Flashing', backup: 'Backing up', done: 'Done', error: 'Error' };
const phaseLabels = { idle: 'Idle', rpiboot: 'Connecting', waiting_choice: 'Choose action', flashing: 'Flashing', backup: 'Backing up', eeprom_update: 'Updating EEPROM', done: 'Done', error: 'Error' };
function renderStatus(data) {
const phase = data.phase || 'idle';
document.getElementById('statusPill').className = 'status-pill ' + phase;
@@ -166,11 +168,9 @@
document.getElementById('statusMsg').textContent = data.message || '';
const err = document.getElementById('statusErr');
if (data.error) { err.textContent = data.error; err.style.display = 'block'; } else { err.style.display = 'none'; }
var clearWrap = document.getElementById('statusClearWrap');
if (clearWrap) clearWrap.style.display = (phase === 'error' ? 'block' : 'none');
if (phase === 'done') scheduleDoneClear();
const progress = data.progress;
const inProgress = ['rpiboot', 'flashing', 'backup'].includes(phase);
const inProgress = ['rpiboot', 'flashing', 'backup', 'eeprom_update'].includes(phase);
const wrap = document.getElementById('progressWrap');
const fill = document.getElementById('progressFill');
if (inProgress || (phase === 'done' && progress != null)) {
@@ -191,7 +191,7 @@
shrinkWrap.style.display = 'block';
const el = document.createElement('div');
el.className = 'device-item';
el.innerHTML = '<div class="device-desc">USB device — choose action</div><div><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></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="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>';
container.appendChild(el);
} else shrinkWrap.style.display = 'none';
(network || []).forEach(function(d) {
@@ -206,6 +206,10 @@
btn.onclick = function() {
const body = { source: btn.getAttribute('data-source'), action: btn.getAttribute('data-action') };
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';
}
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(); })

View File

@@ -99,7 +99,8 @@
.status-pill.rpiboot,
.status-pill.waiting_choice,
.status-pill.flashing,
.status-pill.backup { background: var(--warn-bg); color: var(--warn); }
.status-pill.backup,
.status-pill.eeprom_update { background: var(--warn-bg); color: var(--warn); }
.status-pill.done { background: var(--success-bg); color: var(--success); }
.status-pill.error { background: var(--danger-bg); color: var(--danger); }
.status-msg {
@@ -367,11 +368,12 @@
<div id="status" class="status-row">
<span id="statusPill" class="status-pill idle">Idle</span>
<span id="statusMsg" class="status-msg">Waiting for device</span>
<button type="button" id="statusClearBtn" class="btn btn-outline btn-sm" style="margin-left: auto;">Clear status</button>
</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.
<button type="button" id="statusClearBtn" class="btn btn-outline btn-sm" style="margin-left:0.5rem;">Clear message</button>
<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>
<div id="progressWrap" class="progress-track" style="display:none;">
@@ -500,6 +502,7 @@
waiting_choice: 'Choose action',
flashing: 'Flashing',
backup: 'Backing up',
eeprom_update: 'Updating EEPROM',
done: 'Done',
error: 'Error'
};
@@ -531,7 +534,7 @@
}
const progress = data.progress;
const inProgress = ['rpiboot', 'flashing', 'backup'].includes(phase);
const inProgress = ['rpiboot', 'flashing', 'backup', 'eeprom_update'].includes(phase);
if (inProgress || (phase === 'done' && progress != null)) {
progressWrap.style.display = 'block';
progressFill.classList.remove('indeterminate');
@@ -928,6 +931,10 @@
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(); });
});
var statusClearHintBtn = document.getElementById('statusClearHintBtn');
if (statusClearHintBtn) statusClearHintBtn.addEventListener('click', function() {
fetch('/api/status-clear', { method: 'POST' }).then(function(r) { return r.json(); }).then(function(d) { if (d.ok) fetchStatus(); });
});
var refreshBackupsBtn = document.getElementById('refreshBackupsBtn');
if (refreshBackupsBtn) refreshBackupsBtn.onclick = function() { fetchBackups(); fetchGoldenInfo(); };
var uploadImageBtn = document.getElementById('uploadImageBtn');