diff --git a/html/portal.css b/html/portal.css index 4bbaaf0..3209927 100644 --- a/html/portal.css +++ b/html/portal.css @@ -70,7 +70,7 @@ label { margin-bottom: 0.5rem; font-weight: 600; } -input[type='text'], input[type='password'] { +input[type='text'], input[type='password'], input[type='url'] { width: 100%; padding: 8px; border: 1px solid #ddd; diff --git a/html/portal.html b/html/portal.html index c6aa8fd..96679ac 100644 --- a/html/portal.html +++ b/html/portal.html @@ -69,9 +69,31 @@
Loading available card types...
diff --git a/html/portal.js b/html/portal.js index 8578d16..d045003 100644 --- a/html/portal.js +++ b/html/portal.js @@ -135,6 +135,68 @@ function saveDeviceConfig() { return false; } +// Handle Home Assistant config form submission +function saveHomeAssistantConfig() { + const form = document.getElementById('home-assistant-form'); + const formData = new FormData(form); + const globalActionStatusEl = document.getElementById('global-action-status'); + + fetch('/save-ha-config', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data && data.success) { + console.log("Home Assistant config saved successfully"); + if (globalActionStatusEl) { + globalActionStatusEl.textContent = "Home Assistant configuration saved successfully."; + globalActionStatusEl.className = 'status-message success'; + globalActionStatusEl.style.display = 'block'; + setTimeout(() => { + if (globalActionStatusEl.textContent === "Home Assistant configuration saved successfully.") { + globalActionStatusEl.style.display = 'none'; + globalActionStatusEl.textContent = ''; + globalActionStatusEl.className = 'status-message'; + } + }, 5000); + } + } else { + const errorMessage = (data && data.message) ? data.message : "Failed to save Home Assistant configuration."; + console.error("Failed to save Home Assistant config:", errorMessage); + if (globalActionStatusEl) { + globalActionStatusEl.textContent = errorMessage; + globalActionStatusEl.className = 'status-message error'; + globalActionStatusEl.style.display = 'block'; + setTimeout(() => { + if (globalActionStatusEl.className.includes('error')) { + globalActionStatusEl.style.display = 'none'; + globalActionStatusEl.textContent = ''; + globalActionStatusEl.className = 'status-message'; + } + }, 7000); + } + } + }) + .catch(() => { + console.error("Communication error saving Home Assistant config."); + if (globalActionStatusEl) { + globalActionStatusEl.textContent = "Communication error saving Home Assistant config."; + globalActionStatusEl.className = 'status-message error'; + globalActionStatusEl.style.display = 'block'; + setTimeout(() => { + if (globalActionStatusEl.className.includes('error')) { + globalActionStatusEl.style.display = 'none'; + globalActionStatusEl.textContent = ''; + globalActionStatusEl.className = 'status-message'; + } + }, 7000); + } + }); + + return false; +} + // Toggle API key visibility function toggleApiKeyVisibility() { const apiKeyInput = document.getElementById('apiKey'); @@ -810,6 +872,11 @@ function pollApiStatus() { _updateDeviceConfigUI(data.device_config); } + // 3a. Update Home Assistant Config Info + if (data.ha_config && document.getElementById('haUrl').value === '') { + _updateHomeAssistantConfigUI(data.ha_config); + } + // 4. Update Insights List (legacy - remove if cards are working) if (data.insights) { _updateInsightsListUI(data.insights); @@ -862,6 +929,20 @@ function _updateDeviceConfigUI(config) { } } +// Load current Home Assistant configuration - UI update part will be in pollApiStatus +let initialHaConfigLoaded = false; +function _updateHomeAssistantConfigUI(config) { + if (!initialHaConfigLoaded) { + if (config.ha_url !== undefined) { + document.getElementById('haUrl').value = config.ha_url; + } + if (config.ha_api_key_display !== undefined) { + document.getElementById('haApiKey').value = config.ha_api_key_display; + } + initialHaConfigLoaded = true; + } +} + // Initialize page document.addEventListener('DOMContentLoaded', function() { const hash = window.location.hash.substr(1); @@ -895,7 +976,7 @@ document.addEventListener('DOMContentLoaded', function() { setTimeout(() => { loadConfiguredCards(); }, 500); - }, 1000); + }, 10000); }); // Enum for OtaManager::UpdateStatus::State (mirror from C++) diff --git a/include/EventQueue.h b/include/EventQueue.h index 818f753..fe05115 100644 --- a/include/EventQueue.h +++ b/include/EventQueue.h @@ -17,6 +17,8 @@ enum class EventType { INSIGHT_ADDED, INSIGHT_DELETED, INSIGHT_DATA_RECEIVED, + HA_ENTITY_STATE_RECEIVED, + HA_SERVICE_CALLED, WIFI_CREDENTIALS_FOUND, NEED_WIFI_CREDENTIALS, WIFI_CONNECTING, @@ -138,4 +140,4 @@ class EventQueue { * @brief Stop the event processing task */ void end(); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/include/html_portal.h b/include/html_portal.h index 7759c2f..2259d44 100644 --- a/include/html_portal.h +++ b/include/html_portal.h @@ -81,7 +81,7 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " margin-bottom: 0.5rem; \n" " font-weight: 600;\n" "}\n" -"input[type='text'], input[type='password'] { \n" +"input[type='text'], input[type='password'], input[type='url'] { \n" " width: 100%; \n" " padding: 8px; \n" " border: 1px solid #ddd; \n" @@ -516,6 +516,68 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " return false;\n" "}\n" "\n" +"// Handle Home Assistant config form submission\n" +"function saveHomeAssistantConfig() {\n" +" const form = document.getElementById('home-assistant-form');\n" +" const formData = new FormData(form);\n" +" const globalActionStatusEl = document.getElementById('global-action-status');\n" +" \n" +" fetch('/save-ha-config', { \n" +" method: 'POST',\n" +" body: formData\n" +" })\n" +" .then(response => response.json())\n" +" .then(data => {\n" +" if (data && data.success) {\n" +" console.log(\"Home Assistant config saved successfully\");\n" +" if (globalActionStatusEl) {\n" +" globalActionStatusEl.textContent = \"Home Assistant configuration saved successfully.\";\n" +" globalActionStatusEl.className = 'status-message success';\n" +" globalActionStatusEl.style.display = 'block';\n" +" setTimeout(() => {\n" +" if (globalActionStatusEl.textContent === \"Home Assistant configuration saved successfully.\") {\n" +" globalActionStatusEl.style.display = 'none';\n" +" globalActionStatusEl.textContent = '';\n" +" globalActionStatusEl.className = 'status-message';\n" +" }\n" +" }, 5000);\n" +" }\n" +" } else {\n" +" const errorMessage = (data && data.message) ? data.message : \"Failed to save Home Assistant configuration.\";\n" +" console.error(\"Failed to save Home Assistant config:\", errorMessage);\n" +" if (globalActionStatusEl) {\n" +" globalActionStatusEl.textContent = errorMessage;\n" +" globalActionStatusEl.className = 'status-message error';\n" +" globalActionStatusEl.style.display = 'block';\n" +" setTimeout(() => {\n" +" if (globalActionStatusEl.className.includes('error')) {\n" +" globalActionStatusEl.style.display = 'none';\n" +" globalActionStatusEl.textContent = '';\n" +" globalActionStatusEl.className = 'status-message';\n" +" }\n" +" }, 7000);\n" +" }\n" +" }\n" +" })\n" +" .catch(() => {\n" +" console.error(\"Communication error saving Home Assistant config.\");\n" +" if (globalActionStatusEl) {\n" +" globalActionStatusEl.textContent = \"Communication error saving Home Assistant config.\";\n" +" globalActionStatusEl.className = 'status-message error';\n" +" globalActionStatusEl.style.display = 'block';\n" +" setTimeout(() => {\n" +" if (globalActionStatusEl.className.includes('error')) {\n" +" globalActionStatusEl.style.display = 'none';\n" +" globalActionStatusEl.textContent = '';\n" +" globalActionStatusEl.className = 'status-message';\n" +" }\n" +" }, 7000);\n" +" }\n" +" });\n" +" \n" +" return false;\n" +"}\n" +"\n" "// Toggle API key visibility\n" "function toggleApiKeyVisibility() {\n" " const apiKeyInput = document.getElementById('apiKey');\n" @@ -1191,6 +1253,11 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " _updateDeviceConfigUI(data.device_config);\n" " }\n" "\n" +" // 3a. Update Home Assistant Config Info\n" +" if (data.ha_config && document.getElementById('haUrl').value === '') {\n" +" _updateHomeAssistantConfigUI(data.ha_config);\n" +" }\n" +"\n" " // 4. Update Insights List (legacy - remove if cards are working)\n" " if (data.insights) {\n" " _updateInsightsListUI(data.insights);\n" @@ -1243,6 +1310,20 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " }\n" "}\n" "\n" +"// Load current Home Assistant configuration - UI update part will be in pollApiStatus\n" +"let initialHaConfigLoaded = false;\n" +"function _updateHomeAssistantConfigUI(config) {\n" +" if (!initialHaConfigLoaded) {\n" +" if (config.ha_url !== undefined) {\n" +" document.getElementById('haUrl').value = config.ha_url;\n" +" }\n" +" if (config.ha_api_key_display !== undefined) { \n" +" document.getElementById('haApiKey').value = config.ha_api_key_display;\n" +" }\n" +" initialHaConfigLoaded = true;\n" +" }\n" +"}\n" +"\n" "// Initialize page\n" "document.addEventListener('DOMContentLoaded', function() {\n" " const hash = window.location.hash.substr(1);\n" @@ -1276,7 +1357,7 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " setTimeout(() => {\n" " loadConfiguredCards();\n" " }, 500);\n" -" }, 1000);\n" +" }, 10000);\n" "});\n" "\n" "// Enum for OtaManager::UpdateStatus::State (mirror from C++)\n" @@ -1502,9 +1583,31 @@ static const char PORTAL_HTML[] PROGMEM = "\n" " \n" "Loading available card types...
\n" diff --git a/src/ConfigManager.cpp b/src/ConfigManager.cpp index de7f78c..c06510f 100644 --- a/src/ConfigManager.cpp +++ b/src/ConfigManager.cpp @@ -324,4 +324,49 @@ bool ConfigManager::saveCardConfigs(const std::vector