From 921a3179dc1451c66701c5751bf848ab94a1f575 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 12:52:46 +0200 Subject: [PATCH 1/6] Fixes typo in filename Corrects a typo in the header file include path. Ensures the correct WifiInterface header is included. --- src/ui/ProvisioningCard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/ProvisioningCard.h b/src/ui/ProvisioningCard.h index c9656c4..bf67510 100644 --- a/src/ui/ProvisioningCard.h +++ b/src/ui/ProvisioningCard.h @@ -2,7 +2,7 @@ #include // LVGL core library #include // For String class (or could be Arduino's String) -#include "hardware/WiFiInterface.h" // Custom WiFi interface class +#include "hardware/WifiInterface.h" // Custom WiFi interface class /** * @class ProvisioningCard From 743dc447e91b2bb94ba696f6ef7834448f4398e6 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 12:53:56 +0200 Subject: [PATCH 2/6] Enables custom API base URL configuration Adds the ability to configure a custom base URL for the PostHog API, e.g. data regions EU or US or self-hosted. This allows users to specify a different PostHog instance or a proxy server to be used for API requests. Validates that the base URL ends with `/api/projects/` and persists it in the configuration. The API key and base URL are now required for the system to be considered fully ready. --- html/portal.html | 7 ++++++- html/portal.js | 3 +++ include/html_portal.h | 10 +++++++++- src/ConfigManager.cpp | 37 +++++++++++++++++++++++++++++++++++ src/ConfigManager.h | 23 ++++++++++++++++++++++ src/posthog/PostHogClient.cpp | 7 +++---- src/posthog/PostHogClient.h | 1 - src/ui/CaptivePortal.cpp | 7 +++++++ 8 files changed, 88 insertions(+), 7 deletions(-) diff --git a/html/portal.html b/html/portal.html index 98601cd..a7ab798 100644 --- a/html/portal.html +++ b/html/portal.html @@ -54,7 +54,12 @@

API configuration

- + +
+ + +
+
diff --git a/html/portal.js b/html/portal.js index 649cee8..ae71542 100644 --- a/html/portal.js +++ b/html/portal.js @@ -228,6 +228,9 @@ function loadCurrentConfig() { if (data.apiKey) { document.getElementById('apiKey').value = data.apiKey; } + if (data.baseUrl) { + document.getElementById('baseUrl').value = data.baseUrl; + } }) .catch(error => { console.error('Error loading device config:', error); diff --git a/include/html_portal.h b/include/html_portal.h index 1b7c6e3..232d8e7 100644 --- a/include/html_portal.h +++ b/include/html_portal.h @@ -424,6 +424,9 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " if (data.apiKey) {\n" " document.getElementById('apiKey').value = data.apiKey;\n" " }\n" +" if (data.baseUrl) {\n" +" document.getElementById('baseUrl').value = data.baseUrl;\n" +" }\n" " })\n" " .catch(error => {\n" " console.error('Error loading device config:', error);\n" @@ -496,7 +499,12 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " \n" " \n" " \n" -" \n" +"\n" +"
\n" +" \n" +" \n" +"
\n" +"\n" "
\n" " \n" "
\n" diff --git a/src/ConfigManager.cpp b/src/ConfigManager.cpp index d31a1ca..b129de1 100644 --- a/src/ConfigManager.cpp +++ b/src/ConfigManager.cpp @@ -242,5 +242,42 @@ void ConfigManager::clearApiKey() { // Commit changes commit(); + SystemController::setApiState(ApiState::API_AWAITING_CONFIG); +} + +bool ConfigManager::setBaseUrl(const String& baseUrl) { + if (baseUrl.length() == 0 || baseUrl.length() > MAX_BASE_URL_LENGTH) { + SystemController::setApiState(ApiState::API_CONFIG_INVALID); + return false; + } + + if (!baseUrl.endsWith(PROJECTS_ENDPOINT)) { + SystemController::setApiState(ApiState::API_CONFIG_INVALID); + return false; + } + + _preferences.putString(_baseUrlKey, baseUrl); + + // Commit changes + commit(); + + updateApiConfigurationState(); + return true; +} + +String ConfigManager::getBaseUrl() { + String baseUrl = String(DEFAULT_BASE_URL) + String(PROJECTS_ENDPOINT); + if (!_preferences.isKey(_baseUrlKey)) { + return baseUrl; + } + return _preferences.getString(_baseUrlKey, baseUrl); +} + +void ConfigManager::clearBaseUrl() { + _preferences.remove(_baseUrlKey); + + // Commit changes + commit(); + SystemController::setApiState(ApiState::API_AWAITING_CONFIG); } \ No newline at end of file diff --git a/src/ConfigManager.h b/src/ConfigManager.h index 1b28885..d7e56f4 100644 --- a/src/ConfigManager.h +++ b/src/ConfigManager.h @@ -22,6 +22,8 @@ class ConfigManager { public: static const int NO_TEAM_ID = -1; // Sentinel value for no team ID + static constexpr const char* DEFAULT_BASE_URL = "https://us.posthog.com"; // Sentinel value for no base url + static constexpr const char* PROJECTS_ENDPOINT = "/api/projects/"; // Endpoint for projects /** * @brief Default constructor @@ -113,6 +115,24 @@ class ConfigManager { */ void clearApiKey(); + /** + * @brief Store base url + * @param baseUrl The base url to store + * @return true if saved successfully, false otherwise + */ + bool setBaseUrl(const String& baseUrl); + + /** + * @brief Retrieve stored base url + * @return The base url or DEFAULT_BASE_URL if not set + */ + String getBaseUrl(); + + /** + * @brief Remove stored base url + */ + void clearBaseUrl(); + /** * @brief Store insight configuration * @param id Unique insight identifier @@ -188,6 +208,7 @@ class ConfigManager { // Storage keys for API configuration const char* _teamIdKey = "team_id"; ///< Key for stored team ID const char* _apiKeyKey = "api_key"; ///< Key for stored API key + const char* _baseUrlKey = "base_url"; ///< Key for stored base url // Storage size limits /** @brief Maximum length for WiFi SSID (per IEEE 802.11 spec) */ @@ -198,6 +219,8 @@ class ConfigManager { static const size_t MAX_INSIGHT_LENGTH = 1024; /** @brief Maximum length for API key */ static const size_t MAX_API_KEY_LENGTH = 64; + /** @brief Maximum length for base url */ + static const size_t MAX_BASE_URL_LENGTH = 1024; /** @brief Maximum length for insight identifier */ static const size_t MAX_INSIGHT_ID_LENGTH = 64; diff --git a/src/posthog/PostHogClient.cpp b/src/posthog/PostHogClient.cpp index 2cdb1be..999d722 100644 --- a/src/posthog/PostHogClient.cpp +++ b/src/posthog/PostHogClient.cpp @@ -1,8 +1,6 @@ #include "PostHogClient.h" #include "../ConfigManager.h" -const char* PostHogClient::BASE_URL = "https://us.posthog.com/api/projects/"; - PostHogClient::PostHogClient(ConfigManager& config, EventQueue& eventQueue) : _config(config) , _eventQueue(eventQueue) @@ -26,7 +24,8 @@ void PostHogClient::requestInsightData(const String& insight_id) { bool PostHogClient::isReady() const { return SystemController::isSystemFullyReady() && _config.getTeamId() != ConfigManager::NO_TEAM_ID && - _config.getApiKey().length() > 0; + _config.getApiKey().length() > 0 && + _config.getBaseUrl().length() > 0; } void PostHogClient::process() { @@ -127,7 +126,7 @@ void PostHogClient::checkRefreshes() { } String PostHogClient::buildInsightUrl(const String& insight_id, const char* refresh_mode) const { - String url = String(BASE_URL); + String url = String(_config.getBaseUrl()); url += String(_config.getTeamId()); url += "/insights/?refresh="; url += refresh_mode; diff --git a/src/posthog/PostHogClient.h b/src/posthog/PostHogClient.h index 61ac942..754749a 100644 --- a/src/posthog/PostHogClient.h +++ b/src/posthog/PostHogClient.h @@ -87,7 +87,6 @@ class PostHogClient { unsigned long last_refresh_check; ///< Last refresh timestamp // Constants - static const char* BASE_URL; ///< PostHog API base URL static const unsigned long REFRESH_INTERVAL = 30000; ///< Refresh every 30s static const uint8_t MAX_RETRIES = 3; ///< Max retry attempts static const unsigned long RETRY_DELAY = 1000; ///< Delay between retries diff --git a/src/ui/CaptivePortal.cpp b/src/ui/CaptivePortal.cpp index 7c754ec..b73e79c 100644 --- a/src/ui/CaptivePortal.cpp +++ b/src/ui/CaptivePortal.cpp @@ -86,6 +86,7 @@ void CaptivePortal::handleGetDeviceConfig() { truncatedKey = truncatedKey.substring(0, 12) + "..."; } doc["apiKey"] = truncatedKey; + doc["baseUrl"] = _configManager.getBaseUrl(); String response; serializeJson(doc, response); @@ -113,6 +114,12 @@ void CaptivePortal::handleSaveDeviceConfig() { message = "Invalid API key"; } } + + // Handle base url + if (_server.hasArg("baseUrl")) { + String baseUrl = _server.arg("baseUrl"); + _configManager.setBaseUrl(baseUrl); + } // Create JSON response StaticJsonDocument<128> doc; From 828d326732551fc1137c5727064054eb29cec80b Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 14:26:40 +0200 Subject: [PATCH 3/6] Adds base URL input placeholder Improves user experience by providing a placeholder value for the base URL input field, guiding the user on the expected format. --- html/portal.html | 2 +- include/html_portal.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/html/portal.html b/html/portal.html index a7ab798..43c0e86 100644 --- a/html/portal.html +++ b/html/portal.html @@ -57,7 +57,7 @@

API configuration

- +
diff --git a/include/html_portal.h b/include/html_portal.h index 232d8e7..64d9042 100644 --- a/include/html_portal.h +++ b/include/html_portal.h @@ -502,7 +502,7 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "\n" "
\n" " \n" -" \n" +" \n" "
\n" "\n" "
\n" From 8e37fbe2c9787160a0abedd9038fa16fbe0c6d44 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 15:00:42 +0200 Subject: [PATCH 4/6] Includes stored Wi-Fi credentials in device config Adds the stored Wi-Fi SSID to the device configuration for pre-selection in the captive portal UI, enhancing user experience. The password field is masked for security. --- src/ui/CaptivePortal.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/CaptivePortal.cpp b/src/ui/CaptivePortal.cpp index b73e79c..016548c 100644 --- a/src/ui/CaptivePortal.cpp +++ b/src/ui/CaptivePortal.cpp @@ -87,6 +87,14 @@ void CaptivePortal::handleGetDeviceConfig() { } doc["apiKey"] = truncatedKey; doc["baseUrl"] = _configManager.getBaseUrl(); + + // Stored credentials + String _ssid; + String _password; + if (_configManager.getWiFiCredentials(_ssid, _password)) { + doc["ssid"] = _ssid; + doc["password"] = "********"; // Hide the password + } String response; serializeJson(doc, response); From d1ed08a55115337e6e6c5dcf4c10c99ed036d67d Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 15:03:14 +0200 Subject: [PATCH 5/6] Refactors use async/await and show configured network/ SSID as selected Converts form submission functions to use async/await for improved readability and error handling. Adds try/catch blocks to handle potential errors during fetch requests, displaying an error message to the user. The 'refreshNetworks' function now accepts config to preselect SSID if available. --- html/portal.js | 174 ++++++++++++++++++++++-------------------- include/html_portal.h | 174 ++++++++++++++++++++++-------------------- 2 files changed, 184 insertions(+), 164 deletions(-) diff --git a/html/portal.js b/html/portal.js index ae71542..4d0381f 100644 --- a/html/portal.js +++ b/html/portal.js @@ -19,50 +19,50 @@ function showScreen(screenId) { } // Handle WiFi form submission -function saveWifiConfig() { - const form = document.getElementById('wifi-form'); - const formData = new FormData(form); - - fetch('/save-wifi', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { +async function saveWifiConfig() { + try { + const form = document.getElementById('wifi-form'); + const formData = new FormData(form); + + const response = await fetch('/save-wifi', { + method: 'POST', + body: formData + }); + const data = await response.json(); + if (data.success) { showScreen('success-screen'); } else { showScreen('error-screen'); } - }) - .catch(() => { + } catch (error) { + console.error('Error saving WiFi config:', error); showScreen('error-screen'); - }); - + } return false; // Prevent default form submission } // Handle device config form submission -function saveDeviceConfig() { - const form = document.getElementById('device-form'); - const formData = new FormData(form); - - fetch('/save-device-config', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { +async function saveDeviceConfig() { + try { + const form = document.getElementById('device-form'); + const formData = new FormData(form); + + const response = await fetch('/save-device-config', { + method: 'POST', + body: formData + }); + const data = await response.json(); + if (data.success) { showScreen('success-screen'); } else { showScreen('error-screen'); } - }) - .catch(() => { + } catch (error) { + console.error('Error saving device config:', error); showScreen('error-screen'); - }); - + } return false; } @@ -73,61 +73,63 @@ function toggleApiKeyVisibility() { } // Add new insight -function addInsight() { - const form = document.getElementById('insight-form'); - const formData = new FormData(form); - - fetch('/save-insight', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { +async function addInsight() { + try { + const form = document.getElementById('insight-form'); + const formData = new FormData(form); + + const response = await fetch('/save-insight', { + method: 'POST', + body: formData + }); + const data = await response.json(); + if (data.success) { form.reset(); - loadInsights(); + await loadInsights(); } else { showScreen('error-screen'); } - }) - .catch(() => { + } catch (error) { + console.error('Error adding insight:', error); showScreen('error-screen'); - }); - + } return false; } // Delete insight -function deleteInsight(id) { +async function deleteInsight(id) { if (!confirm('Are you sure you want to delete this insight?')) { return; } - fetch('/delete-insight', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ id: id }) - }) - .then(response => response.json()) - .then(data => { + try { + const response = await fetch('/delete-insight', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id: id }) + }); + const data = await response.json(); + if (data.success) { - loadInsights(); + await loadInsights(); } else { showScreen('error-screen'); } - }) - .catch(() => { + } catch (error) { + console.error('Error deleting insight:', error); showScreen('error-screen'); - }); + } } // Load insights list -function loadInsights() { - fetch('/get-insights') - .then(response => response.json()) - .then(data => { +async function loadInsights() { + try { + const response = await fetch('/get-insights'); + const data = await response.json(); + const container = document.getElementById('insights-list'); container.innerHTML = ''; @@ -149,17 +151,20 @@ function loadInsights() { }); container.appendChild(list); - }) - .catch(error => { + } catch (error) { console.error('Error loading insights:', error); - }); + const container = document.getElementById('insights-list'); + container.innerHTML = '

Error loading insights

'; + } } // Refresh network list -function refreshNetworks() { - fetch('/scan-networks') - .then(response => response.json()) - .then(data => { +async function refreshNetworks(config) { + try { + const response = await fetch('/scan-networks'); + const data = await response.json(); + + const { ssid } = config; const select = document.getElementById('ssid'); select.innerHTML = ''; @@ -189,16 +194,19 @@ function refreshNetworks() { if (network.encrypted) { label += ' 🔒'; } + + if (ssid === network.ssid) { + option.selected = true; + } option.textContent = label; select.appendChild(option); }); - }) - .catch(error => { + } catch (error) { console.error('Error refreshing networks:', error); const select = document.getElementById('ssid'); select.innerHTML = ''; - }); + } } // Start countdown on success screen @@ -218,10 +226,11 @@ function startCountdown() { } // Load current configuration -function loadCurrentConfig() { - fetch('/get-device-config') - .then(response => response.json()) - .then(data => { +async function loadCurrentConfig() { + try { + const response = await fetch('/get-device-config'); + const data = await response.json(); + if (data.teamId !== undefined) { document.getElementById('teamId').value = data.teamId; } @@ -231,14 +240,15 @@ function loadCurrentConfig() { if (data.baseUrl) { document.getElementById('baseUrl').value = data.baseUrl; } - }) - .catch(error => { + return data; + } catch (error) { console.error('Error loading device config:', error); - }); + throw error; + } } // Initialize page -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', async function() { // Check if we need to show a specific screen based on URL hash const hash = window.location.hash.substr(1); if (hash && ['config-screen', 'success-screen', 'error-screen'].includes(hash)) { @@ -246,11 +256,11 @@ document.addEventListener('DOMContentLoaded', function() { } // Load current configuration - loadCurrentConfig(); + const config = await loadCurrentConfig(); // Load insights list - loadInsights(); + await loadInsights(); // Populate the networks list - refreshNetworks(); + await refreshNetworks(config); }); \ No newline at end of file diff --git a/include/html_portal.h b/include/html_portal.h index 64d9042..bd3351f 100644 --- a/include/html_portal.h +++ b/include/html_portal.h @@ -215,50 +215,50 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "}\n" "\n" "// Handle WiFi form submission\n" -"function saveWifiConfig() {\n" -" const form = document.getElementById('wifi-form');\n" -" const formData = new FormData(form);\n" -" \n" -" fetch('/save-wifi', {\n" -" method: 'POST',\n" -" body: formData\n" -" })\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function saveWifiConfig() {\n" +" try {\n" +" const form = document.getElementById('wifi-form');\n" +" const formData = new FormData(form);\n" +" \n" +" const response = await fetch('/save-wifi', {\n" +" method: 'POST',\n" +" body: formData\n" +" });\n" +" const data = await response.json();\n" +" \n" " if (data.success) {\n" " showScreen('success-screen');\n" " } else {\n" " showScreen('error-screen');\n" " }\n" -" })\n" -" .catch(() => {\n" +" } catch (error) {\n" +" console.error('Error saving WiFi config:', error);\n" " showScreen('error-screen');\n" -" });\n" -" \n" +" }\n" " return false; // Prevent default form submission\n" "}\n" "\n" "// Handle device config form submission\n" -"function saveDeviceConfig() {\n" -" const form = document.getElementById('device-form');\n" -" const formData = new FormData(form);\n" -" \n" -" fetch('/save-device-config', {\n" -" method: 'POST',\n" -" body: formData\n" -" })\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function saveDeviceConfig() {\n" +" try {\n" +" const form = document.getElementById('device-form');\n" +" const formData = new FormData(form);\n" +" \n" +" const response = await fetch('/save-device-config', {\n" +" method: 'POST',\n" +" body: formData\n" +" });\n" +" const data = await response.json();\n" +" \n" " if (data.success) {\n" " showScreen('success-screen');\n" " } else {\n" " showScreen('error-screen');\n" " }\n" -" })\n" -" .catch(() => {\n" +" } catch (error) {\n" +" console.error('Error saving device config:', error);\n" " showScreen('error-screen');\n" -" });\n" -" \n" +" }\n" " return false;\n" "}\n" "\n" @@ -269,61 +269,63 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "}\n" "\n" "// Add new insight\n" -"function addInsight() {\n" -" const form = document.getElementById('insight-form');\n" -" const formData = new FormData(form);\n" -" \n" -" fetch('/save-insight', {\n" -" method: 'POST',\n" -" body: formData\n" -" })\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function addInsight() {\n" +" try {\n" +" const form = document.getElementById('insight-form');\n" +" const formData = new FormData(form);\n" +" \n" +" const response = await fetch('/save-insight', {\n" +" method: 'POST',\n" +" body: formData\n" +" });\n" +" const data = await response.json();\n" +" \n" " if (data.success) {\n" " form.reset();\n" -" loadInsights();\n" +" await loadInsights();\n" " } else {\n" " showScreen('error-screen');\n" " }\n" -" })\n" -" .catch(() => {\n" +" } catch (error) {\n" +" console.error('Error adding insight:', error);\n" " showScreen('error-screen');\n" -" });\n" -" \n" +" }\n" " return false;\n" "}\n" "\n" "// Delete insight\n" -"function deleteInsight(id) {\n" +"async function deleteInsight(id) {\n" " if (!confirm('Are you sure you want to delete this insight?')) {\n" " return;\n" " }\n" " \n" -" fetch('/delete-insight', {\n" -" method: 'POST',\n" -" headers: {\n" -" 'Content-Type': 'application/json',\n" -" },\n" -" body: JSON.stringify({ id: id })\n" -" })\n" -" .then(response => response.json())\n" -" .then(data => {\n" +" try {\n" +" const response = await fetch('/delete-insight', {\n" +" method: 'POST',\n" +" headers: {\n" +" 'Content-Type': 'application/json',\n" +" },\n" +" body: JSON.stringify({ id: id })\n" +" });\n" +" const data = await response.json();\n" +" \n" " if (data.success) {\n" -" loadInsights();\n" +" await loadInsights();\n" " } else {\n" " showScreen('error-screen');\n" " }\n" -" })\n" -" .catch(() => {\n" +" } catch (error) {\n" +" console.error('Error deleting insight:', error);\n" " showScreen('error-screen');\n" -" });\n" +" }\n" "}\n" "\n" "// Load insights list\n" -"function loadInsights() {\n" -" fetch('/get-insights')\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function loadInsights() {\n" +" try {\n" +" const response = await fetch('/get-insights');\n" +" const data = await response.json();\n" +" \n" " const container = document.getElementById('insights-list');\n" " container.innerHTML = '';\n" " \n" @@ -345,17 +347,20 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " });\n" " \n" " container.appendChild(list);\n" -" })\n" -" .catch(error => {\n" +" } catch (error) {\n" " console.error('Error loading insights:', error);\n" -" });\n" +" const container = document.getElementById('insights-list');\n" +" container.innerHTML = '

Error loading insights

';\n" +" }\n" "}\n" "\n" "// Refresh network list\n" -"function refreshNetworks() {\n" -" fetch('/scan-networks')\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function refreshNetworks(config) {\n" +" try {\n" +" const response = await fetch('/scan-networks');\n" +" const data = await response.json();\n" +" \n" +" const { ssid } = config;\n" " const select = document.getElementById('ssid');\n" " select.innerHTML = '';\n" " \n" @@ -385,16 +390,19 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " if (network.encrypted) {\n" " label += ' 🔒';\n" " }\n" +"\n" +" if (ssid === network.ssid) {\n" +" option.selected = true;\n" +" }\n" " \n" " option.textContent = label;\n" " select.appendChild(option);\n" " });\n" -" })\n" -" .catch(error => {\n" +" } catch (error) {\n" " console.error('Error refreshing networks:', error);\n" " const select = document.getElementById('ssid');\n" " select.innerHTML = '';\n" -" });\n" +" }\n" "}\n" "\n" "// Start countdown on success screen\n" @@ -414,10 +422,11 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "}\n" "\n" "// Load current configuration\n" -"function loadCurrentConfig() {\n" -" fetch('/get-device-config')\n" -" .then(response => response.json())\n" -" .then(data => {\n" +"async function loadCurrentConfig() {\n" +" try {\n" +" const response = await fetch('/get-device-config');\n" +" const data = await response.json();\n" +" \n" " if (data.teamId !== undefined) {\n" " document.getElementById('teamId').value = data.teamId;\n" " }\n" @@ -427,14 +436,15 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " if (data.baseUrl) {\n" " document.getElementById('baseUrl').value = data.baseUrl;\n" " }\n" -" })\n" -" .catch(error => {\n" +" return data;\n" +" } catch (error) {\n" " console.error('Error loading device config:', error);\n" -" });\n" +" throw error;\n" +" }\n" "}\n" "\n" "// Initialize page\n" -"document.addEventListener('DOMContentLoaded', function() {\n" +"document.addEventListener('DOMContentLoaded', async function() {\n" " // Check if we need to show a specific screen based on URL hash\n" " const hash = window.location.hash.substr(1);\n" " if (hash && ['config-screen', 'success-screen', 'error-screen'].includes(hash)) {\n" @@ -442,13 +452,13 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " }\n" " \n" " // Load current configuration\n" -" loadCurrentConfig();\n" +" const config = await loadCurrentConfig();\n" " \n" " // Load insights list\n" -" loadInsights();\n" +" await loadInsights();\n" "\n" " // Populate the networks list\n" -" refreshNetworks();\n" +" await refreshNetworks(config);\n" "});\n" "\n" "\n" From 9aba4c6f3ccb0eeddbb0dbd7de8f4604fb52fd7f Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Sun, 11 May 2025 15:51:34 +0200 Subject: [PATCH 6/6] Prevents default form submissions in portal Modifies form submission handlers to prevent default form submission, ensuring JavaScript functions handle the submission process. This change avoids unexpected page reloads and ensures proper form data handling. --- html/portal.html | 6 +++--- html/portal.js | 14 ++++++++++---- include/html_portal.h | 20 +++++++++++++------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/html/portal.html b/html/portal.html index 43c0e86..a4e9681 100644 --- a/html/portal.html +++ b/html/portal.html @@ -17,7 +17,7 @@

Configure your DeskHog

WiFi

-
+
@@ -69,7 +69,7 @@

API configuration

Insights

- +
diff --git a/html/portal.js b/html/portal.js index 4d0381f..db8e8e8 100644 --- a/html/portal.js +++ b/html/portal.js @@ -19,7 +19,9 @@ function showScreen(screenId) { } // Handle WiFi form submission -async function saveWifiConfig() { +async function saveWifiConfig(event) { + event.preventDefault(); // Prevent form submission immediately + try { const form = document.getElementById('wifi-form'); const formData = new FormData(form); @@ -39,11 +41,13 @@ async function saveWifiConfig() { console.error('Error saving WiFi config:', error); showScreen('error-screen'); } - return false; // Prevent default form submission + return false; } // Handle device config form submission -async function saveDeviceConfig() { +async function saveDeviceConfig(event) { + event.preventDefault(); // Prevent form submission immediately + try { const form = document.getElementById('device-form'); const formData = new FormData(form); @@ -73,7 +77,9 @@ function toggleApiKeyVisibility() { } // Add new insight -async function addInsight() { +async function addInsight(event) { + event.preventDefault(); // Prevent form submission immediately + try { const form = document.getElementById('insight-form'); const formData = new FormData(form); diff --git a/include/html_portal.h b/include/html_portal.h index bd3351f..055aad1 100644 --- a/include/html_portal.h +++ b/include/html_portal.h @@ -215,7 +215,9 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "}\n" "\n" "// Handle WiFi form submission\n" -"async function saveWifiConfig() {\n" +"async function saveWifiConfig(event) {\n" +" event.preventDefault(); // Prevent form submission immediately\n" +" \n" " try {\n" " const form = document.getElementById('wifi-form');\n" " const formData = new FormData(form);\n" @@ -235,11 +237,13 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " console.error('Error saving WiFi config:', error);\n" " showScreen('error-screen');\n" " }\n" -" return false; // Prevent default form submission\n" +" return false;\n" "}\n" "\n" "// Handle device config form submission\n" -"async function saveDeviceConfig() {\n" +"async function saveDeviceConfig(event) {\n" +" event.preventDefault(); // Prevent form submission immediately\n" +" \n" " try {\n" " const form = document.getElementById('device-form');\n" " const formData = new FormData(form);\n" @@ -269,7 +273,9 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "}\n" "\n" "// Add new insight\n" -"async function addInsight() {\n" +"async function addInsight(event) {\n" +" event.preventDefault(); // Prevent form submission immediately\n" +" \n" " try {\n" " const form = document.getElementById('insight-form');\n" " const formData = new FormData(form);\n" @@ -472,7 +478,7 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "

WiFi

\n" "
\n" " \n" -" \n" +" \n" "
\n" " \n" @@ -524,7 +530,7 @@ static const char PORTAL_HTML[] PROGMEM = "\n" "

Insights

\n" "\n" "
\n" -" \n" +" \n" "
\n" " \n" " \n"