From 34cadd3a8e2916a22bf00dbb61180e90d593e9ee Mon Sep 17 00:00:00 2001 From: "Valentin V. Bartenev" Date: Mon, 11 May 2026 10:52:48 +0300 Subject: [PATCH] Web: poll for device reboot after OTA upload After a successful firmware upload the UI previously showed a static "Done. Device is rebooting..." message with no indication of progress. Replace the static message with a live polling loop. A dot is appended to the message every second as a visual progress indicator. After a 4-second initial delay (to allow the device to start rebooting), the loop fetches "/" to check whether the HTTPS server is back up. On a successful response the interval is cleared and the user is redirected to the login page after 1.5 s (the session token is invalidated by the reboot). On a network error the fetch is retried every 4 seconds. A guard value of 1000 prevents a second fetch from being started while the first is still in flight. If the device does not respond within 60 seconds the loop stops and an error message is shown. --- src/helpers/web/WebPanelServer.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index b118a2a8..12706de2 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -2641,7 +2641,23 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( } if (xhr.status === 200) { barEl.style.width = "100%"; - msgEl.textContent = "Done. Device is rebooting..."; + msgEl.textContent = "Done. Device is rebooting"; + let wait = 4; + const iv = setInterval(async () => { + msgEl.textContent += '.'; + if (msgEl.textContent.length > 85) { clearInterval(iv); msgEl.textContent = "Device did not come back online. =("; return; } + if (--wait) { return; } + wait = 1000; // prevent concurrent fetch + try { + if ((await fetch("/", { cache: "no-store" })).ok) { + clearInterval(iv); + msgEl.textContent = "Device is back online. Redirecting to login..."; + setTimeout(redirectToLogin, 1500); + return; + } + } catch (_) {} + wait = 4; + }, 1000); } else { msgEl.textContent = "Failed: " + (xhr.responseText || String(xhr.status)); document.getElementById("otaUploadBtn").disabled = false;