Web: open OTA update page automatically after starting OTA mode
Add a /update endpoint to the HTTPS web panel server that issues a
302 redirect to the OTA update server (http://<host>/update, port 80).
When the user confirms the OTA prompt in the web panel, the browser
now opens the update page in a new tab automatically instead of
requiring manual navigation. The redirect also helps users who
manually navigate to https://<host>/update and forget to switch to
http — they are transparently forwarded to the correct URL.
The JS handler opens a relative URL (window.location.origin + "/update")
rather than a hardcoded device IP, so it works correctly when the device
is behind a reverse proxy. Similarly, handleOtaRedirect builds the
redirect target from the request's Host header rather than WiFi.localIP(),
preserving the proxy hostname end-to-end.
The new tab is opened only after runCommand("start ota") returns a
successful response, meaning the OTA server is already up and listening
on port 80 by the time the browser navigates to /update — no race
condition or polling needed.
The /update endpoint returns 404 until notifyOtaStarted() is called,
ensuring the redirect is only served once OTA mode is actually active.
- Add _ota_started flag to WebPanelServer, initialized to false;
set to true via notifyOtaStarted()
- Register GET /update route; returns 302 to http://<host>/update
only after OTA has been started (404 otherwise)
- Call notifyOtaStarted() from WebService::prepareForOTAStart() after
stopping the HTTP redirect server, freeing port 80 for the OTA server
- Update OTA button JS handler to open window.location.origin+"/update"
in a new tab on successful "start ota" command response
This commit is contained in:
@@ -2511,7 +2511,10 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
};
|
||||
document.getElementById("otaBtn").onclick = async () => {
|
||||
if (confirm("Start OTA mode now?")) {
|
||||
await runCommand("start ota");
|
||||
const result = await runCommand("start ota");
|
||||
if (result && result.ok) {
|
||||
window.open(window.location.origin + "/update", "_blank");
|
||||
}
|
||||
}
|
||||
};
|
||||
const AUTO_REFRESH_INTERVAL = 60;
|
||||
@@ -2620,7 +2623,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
} // namespace
|
||||
|
||||
WebPanelServer::WebPanelServer()
|
||||
: _runner(nullptr), _server(nullptr), _redirect_server(nullptr), _token{0}, _last_activity_ms(0), _route_context{this} {
|
||||
: _runner(nullptr), _server(nullptr), _redirect_server(nullptr), _token{0}, _last_activity_ms(0), _route_context{this}, _ota_started(false) {
|
||||
}
|
||||
|
||||
void WebPanelServer::setCommandRunner(WebPanelCommandRunner* runner) {
|
||||
@@ -2669,12 +2672,14 @@ bool WebPanelServer::start() {
|
||||
httpd_uri_t login_uri = {.uri = "/login", .method = HTTP_POST, .handler = &WebPanelServer::handleLogin, .user_ctx = &_route_context};
|
||||
httpd_uri_t command_uri = {.uri = "/api/command", .method = HTTP_POST, .handler = &WebPanelServer::handleCommand, .user_ctx = &_route_context};
|
||||
httpd_uri_t stats_uri = {.uri = "/api/stats", .method = HTTP_GET, .handler = &WebPanelServer::handleStats, .user_ctx = &_route_context};
|
||||
httpd_uri_t update = {.uri = "/update", .method = HTTP_GET, .handler = &WebPanelServer::handleOtaRedirect, .user_ctx = &_route_context};
|
||||
httpd_register_uri_handler(_server, &index_uri);
|
||||
httpd_register_uri_handler(_server, &app_uri);
|
||||
httpd_register_uri_handler(_server, &stats_page_uri);
|
||||
httpd_register_uri_handler(_server, &login_uri);
|
||||
httpd_register_uri_handler(_server, &command_uri);
|
||||
httpd_register_uri_handler(_server, &stats_uri);
|
||||
httpd_register_uri_handler(_server, &update);
|
||||
|
||||
httpd_config_t redirect_config = HTTPD_DEFAULT_CONFIG();
|
||||
redirect_config.server_port = 80;
|
||||
@@ -2745,6 +2750,10 @@ void WebPanelServer::lockSession() {
|
||||
_last_activity_ms = 0;
|
||||
}
|
||||
|
||||
void WebPanelServer::notifyOtaStarted() {
|
||||
_ota_started = true;
|
||||
}
|
||||
|
||||
esp_err_t WebPanelServer::handleIndex(httpd_req_t* req) {
|
||||
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
||||
if (ctx == nullptr || ctx->self == nullptr) {
|
||||
@@ -2794,6 +2803,34 @@ esp_err_t WebPanelServer::handleStatsPage(httpd_req_t* req) {
|
||||
return sendProgmem(req, kWebPanelAppHtml);
|
||||
}
|
||||
|
||||
esp_err_t WebPanelServer::handleOtaRedirect(httpd_req_t* req) {
|
||||
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
||||
if (ctx == nullptr || ctx->self == nullptr) {
|
||||
return httpd_resp_send_500(req);
|
||||
}
|
||||
if (!ctx->self->_ota_started) {
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "OTA not started");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
char host[64] = {};
|
||||
if (httpd_req_get_hdr_value_str(req, "Host", host, sizeof(host)) == ESP_OK && host[0] != '\0') {
|
||||
char* colon = strchr(host, ':');
|
||||
if (colon != nullptr) *colon = '\0';
|
||||
} else {
|
||||
strncpy(host, WiFi.localIP().toString().c_str(), sizeof(host) - 1);
|
||||
host[sizeof(host) - 1] = '\0';
|
||||
}
|
||||
char location[80];
|
||||
snprintf(location, sizeof(location), "http://%s/update", host);
|
||||
|
||||
httpd_resp_set_status(req, "302 Found");
|
||||
httpd_resp_set_hdr(req, "Location", location);
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "no-store");
|
||||
httpd_resp_set_hdr(req, "Connection", "close");
|
||||
return httpd_resp_send(req, "", 0);
|
||||
}
|
||||
|
||||
esp_err_t WebPanelServer::handleLogin(httpd_req_t* req) {
|
||||
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
||||
if (ctx == nullptr || ctx->self == nullptr || ctx->self->_runner == nullptr) {
|
||||
@@ -2984,4 +3021,7 @@ bool WebPanelServer::shouldAutoLock(unsigned long) const {
|
||||
void WebPanelServer::lockSession() {
|
||||
}
|
||||
|
||||
void WebPanelServer::notifyOtaStarted() {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user