diff --git a/app/templates/index.html b/app/templates/index.html
index 1f4f068..fdad425 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -283,14 +283,26 @@
});
// ── Single reboot ──────────────────────────────────────────────────────────
+ function lockApRow(mac) {
+ const rowBtn = document.querySelector(`.btn-reboot[data-mac="${CSS.escape(mac)}"]`);
+ if (rowBtn) {
+ rowBtn.disabled = true;
+ rowBtn.className = 'btn-reboot px-3 py-1.5 text-xs rounded-lg font-medium bg-gray-100 border border-gray-200 text-gray-400 cursor-not-allowed opacity-50';
+ }
+ const cb = document.querySelector(`.ap-checkbox[data-mac="${CSS.escape(mac)}"]`);
+ if (cb) { cb.disabled = true; cb.checked = false; }
+ updateBulkButton();
+ }
+
document.querySelectorAll('.btn-reboot').forEach(btn => {
btn.addEventListener('click', () => {
+ if (btn.disabled) return;
const mac = btn.dataset.mac;
const name = btn.dataset.name;
const ip = btn.dataset.ip;
const site_key = btn.dataset.siteKey;
showModal(`Reboot AP "${name}"?\nThis will disconnect all clients connected to this AP.`, async () => {
- btn.disabled = true;
+ lockApRow(mac);
btn.textContent = 'Rebooting…';
try {
const res = await fetch('/api/reboot', {
@@ -299,17 +311,16 @@
body: JSON.stringify({ mac, name, ip, site_key, csrf_token: CSRF_TOKEN }),
});
if (res.ok) {
+ btn.textContent = 'Rebooted';
showToast(`Reboot command sent to "${name}"`, 'success');
} else {
const err = await res.json();
+ btn.textContent = 'Failed';
showToast(`Error: ${err.detail || 'Unknown error'}`, 'error');
- btn.disabled = false;
- btn.textContent = 'Reboot';
}
} catch (e) {
+ btn.textContent = 'Failed';
showToast('Network error', 'error');
- btn.disabled = false;
- btn.textContent = 'Reboot';
}
});
});
@@ -321,6 +332,8 @@
const aps = checked.map(cb => ({ mac: cb.dataset.mac, name: cb.dataset.name, ip: cb.dataset.ip, site_key: cb.dataset.siteKey }));
const names = aps.map(a => a.name).join(', ');
showModal(`Reboot ${aps.length} AP${aps.length > 1 ? 's' : ''}?\n${names}`, async () => {
+ // Lock all selected AP rows immediately on confirm
+ aps.forEach(ap => lockApRow(ap.mac));
btnRebootSel.disabled = true;
btnRebootSel.querySelector('span').textContent = '0';
try {
@@ -331,17 +344,22 @@
});
const data = await res.json();
const errors = (data.results || []).filter(r => r.result === 'error');
+ // Update button text per AP based on result
+ (data.results || []).forEach(r => {
+ const b = document.querySelector(`.btn-reboot[data-mac="${CSS.escape(r.mac)}"]`);
+ if (b) b.textContent = r.result === 'success' ? 'Rebooted' : 'Failed';
+ });
if (errors.length === 0) {
showToast(`Reboot sent to ${aps.length} AP${aps.length > 1 ? 's' : ''}`, 'success');
} else {
showToast(`${errors.length} AP(s) failed to reboot`, 'error');
}
- document.querySelectorAll('.ap-checkbox').forEach(cb => { cb.checked = false; });
- if (chkAll) chkAll.checked = false;
- updateBulkButton();
} catch (e) {
+ aps.forEach(ap => {
+ const b = document.querySelector(`.btn-reboot[data-mac="${CSS.escape(ap.mac)}"]`);
+ if (b) b.textContent = 'Failed';
+ });
showToast('Network error', 'error');
- updateBulkButton();
}
});
});