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
Cette révision appartient à :
@@ -2511,7 +2511,10 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
|||||||
};
|
};
|
||||||
document.getElementById("otaBtn").onclick = async () => {
|
document.getElementById("otaBtn").onclick = async () => {
|
||||||
if (confirm("Start OTA mode now?")) {
|
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;
|
const AUTO_REFRESH_INTERVAL = 60;
|
||||||
@@ -2620,7 +2623,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
WebPanelServer::WebPanelServer()
|
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) {
|
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 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 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 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, &index_uri);
|
||||||
httpd_register_uri_handler(_server, &app_uri);
|
httpd_register_uri_handler(_server, &app_uri);
|
||||||
httpd_register_uri_handler(_server, &stats_page_uri);
|
httpd_register_uri_handler(_server, &stats_page_uri);
|
||||||
httpd_register_uri_handler(_server, &login_uri);
|
httpd_register_uri_handler(_server, &login_uri);
|
||||||
httpd_register_uri_handler(_server, &command_uri);
|
httpd_register_uri_handler(_server, &command_uri);
|
||||||
httpd_register_uri_handler(_server, &stats_uri);
|
httpd_register_uri_handler(_server, &stats_uri);
|
||||||
|
httpd_register_uri_handler(_server, &update);
|
||||||
|
|
||||||
httpd_config_t redirect_config = HTTPD_DEFAULT_CONFIG();
|
httpd_config_t redirect_config = HTTPD_DEFAULT_CONFIG();
|
||||||
redirect_config.server_port = 80;
|
redirect_config.server_port = 80;
|
||||||
@@ -2745,6 +2750,10 @@ void WebPanelServer::lockSession() {
|
|||||||
_last_activity_ms = 0;
|
_last_activity_ms = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebPanelServer::notifyOtaStarted() {
|
||||||
|
_ota_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t WebPanelServer::handleIndex(httpd_req_t* req) {
|
esp_err_t WebPanelServer::handleIndex(httpd_req_t* req) {
|
||||||
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
||||||
if (ctx == nullptr || ctx->self == nullptr) {
|
if (ctx == nullptr || ctx->self == nullptr) {
|
||||||
@@ -2794,6 +2803,34 @@ esp_err_t WebPanelServer::handleStatsPage(httpd_req_t* req) {
|
|||||||
return sendProgmem(req, kWebPanelAppHtml);
|
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) {
|
esp_err_t WebPanelServer::handleLogin(httpd_req_t* req) {
|
||||||
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
auto* ctx = static_cast<RouteContext*>(req->user_ctx);
|
||||||
if (ctx == nullptr || ctx->self == nullptr || ctx->self->_runner == nullptr) {
|
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::lockSession() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebPanelServer::notifyOtaStarted() {
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ public:
|
|||||||
bool hasSessionToken() const;
|
bool hasSessionToken() const;
|
||||||
bool shouldAutoLock(unsigned long now_ms) const;
|
bool shouldAutoLock(unsigned long now_ms) const;
|
||||||
void lockSession();
|
void lockSession();
|
||||||
|
void notifyOtaStarted();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||||
@@ -59,6 +60,7 @@ private:
|
|||||||
char _token[33];
|
char _token[33];
|
||||||
unsigned long _last_activity_ms;
|
unsigned long _last_activity_ms;
|
||||||
RouteContext _route_context;
|
RouteContext _route_context;
|
||||||
|
bool _ota_started;
|
||||||
|
|
||||||
static esp_err_t handleIndex(httpd_req_t* req);
|
static esp_err_t handleIndex(httpd_req_t* req);
|
||||||
static esp_err_t handleHttpRedirect(httpd_req_t* req);
|
static esp_err_t handleHttpRedirect(httpd_req_t* req);
|
||||||
@@ -67,6 +69,7 @@ private:
|
|||||||
static esp_err_t handleLogin(httpd_req_t* req);
|
static esp_err_t handleLogin(httpd_req_t* req);
|
||||||
static esp_err_t handleCommand(httpd_req_t* req);
|
static esp_err_t handleCommand(httpd_req_t* req);
|
||||||
static esp_err_t handleStats(httpd_req_t* req);
|
static esp_err_t handleStats(httpd_req_t* req);
|
||||||
|
static esp_err_t handleOtaRedirect(httpd_req_t* req);
|
||||||
|
|
||||||
bool readRequestBody(httpd_req_t* req, char* buffer, size_t buffer_size) const;
|
bool readRequestBody(httpd_req_t* req, char* buffer, size_t buffer_size) const;
|
||||||
void refreshToken();
|
void refreshToken();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ void WebService::prepareForOTAStart() {
|
|||||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||||
_suspended_for_ota = true;
|
_suspended_for_ota = true;
|
||||||
_panel.stopRedirectServer();
|
_panel.stopRedirectServer();
|
||||||
|
_panel.notifyOtaStarted();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Référencer dans un nouveau ticket
Bloquer un utilisateur