Added Extra Heartbeat Metrics and Separated HTML Page
This commit is contained in:
281
vesper/src/SettingsWebServer/SettingsPage.h
Normal file
281
vesper/src/SettingsWebServer/SettingsPage.h
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
* SETTINGSPAGE.H - HTML Content for Settings Web Interface
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* This file contains the HTML/CSS/JavaScript for the VESPER network settings page.
|
||||
* Separated from the main implementation for better maintainability.
|
||||
*
|
||||
* 📋 VERSION: 1.0
|
||||
* 📅 DATE: 2025-12-29
|
||||
* 👨💻 AUTHOR: Advanced Bell Systems
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// HTML template for the settings page
|
||||
// Use placeholders for dynamic content:
|
||||
// %MODE_BADGE_CLASS% - "ap" or "station"
|
||||
// %MODE_TEXT% - "AP Mode" or "Station Mode"
|
||||
// %CURRENT_IP% - Current IP address
|
||||
// %DEVICE_UID% - Device unique ID
|
||||
// %FW_VERSION% - Firmware version
|
||||
// %AP_ACTIVE_CLASS% - "active" if in AP mode, "" otherwise
|
||||
// %STATION_ACTIVE_CLASS% - "active" if in station mode, "" otherwise
|
||||
// %SELECTED_MODE% - "ap" or "station"
|
||||
|
||||
const char SETTINGS_PAGE_HTML[] PROGMEM = R"rawliteral(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VESPER Network Settings</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.header h1 {
|
||||
color: #667eea;
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.status-label {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-value {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-badge.ap {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
.mode-badge.station {
|
||||
background: #e8f5e9;
|
||||
color: #388e3c;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.mode-option {
|
||||
flex: 1;
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
.mode-option:hover {
|
||||
border-color: #667eea;
|
||||
background: #f0f4ff;
|
||||
}
|
||||
.mode-option.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.mode-option h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.mode-option p {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #f8f9fa;
|
||||
color: #666;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
.info-box {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: #856404;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>VESPER Settings</h1>
|
||||
<p>Network Configuration</p>
|
||||
</div>
|
||||
|
||||
<div class="status-card">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current Mode:</span>
|
||||
<span class="status-value">
|
||||
<span class="mode-badge %MODE_BADGE_CLASS%">
|
||||
%MODE_TEXT%
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">IP Address:</span>
|
||||
<span class="status-value">%CURRENT_IP%</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Device UID:</span>
|
||||
<span class="status-value">%DEVICE_UID%</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Firmware:</span>
|
||||
<span class="status-value">v%FW_VERSION%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Select Network Mode</div>
|
||||
|
||||
<div class="mode-selector">
|
||||
<div class="mode-option %AP_ACTIVE_CLASS%" onclick="selectMode('ap')">
|
||||
<h3>AP Mode</h3>
|
||||
<p>Direct Connection<br>192.168.4.1</p>
|
||||
</div>
|
||||
<div class="mode-option %STATION_ACTIVE_CLASS%" onclick="selectMode('station')">
|
||||
<h3>Router Mode</h3>
|
||||
<p>Connect via Router<br>WiFi/Ethernet</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="applyMode()">Apply & Reboot</button>
|
||||
<button class="btn btn-secondary" onclick="rebootDevice()">Reboot Device</button>
|
||||
|
||||
<div class="info-box">
|
||||
Device will reboot after applying changes. Make sure to reconnect to the correct network after reboot.
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
VESPER Bell Automation System<br>
|
||||
Advanced Bell Systems
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedMode = '%SELECTED_MODE%';
|
||||
|
||||
function selectMode(mode) {
|
||||
selectedMode = mode;
|
||||
document.querySelectorAll('.mode-option').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
event.target.closest('.mode-option').classList.add('active');
|
||||
}
|
||||
|
||||
function applyMode() {
|
||||
if (confirm('Device will reboot and switch to ' + (selectedMode === 'ap' ? 'AP Mode' : 'Router Mode') + '. Continue?')) {
|
||||
fetch('/api/set-mode', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'mode=' + selectedMode
|
||||
}).then(response => {
|
||||
alert('Rebooting... Please wait 10 seconds and reconnect.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function rebootDevice() {
|
||||
if (confirm('Reboot device now?')) {
|
||||
fetch('/api/reboot', {method: 'POST'}).then(() => {
|
||||
alert('Rebooting... Please wait 10 seconds.');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "SettingsWebServer.hpp"
|
||||
#include "SettingsPage.h"
|
||||
#include "../ConfigManager/ConfigManager.hpp"
|
||||
#include "../Networking/Networking.hpp"
|
||||
#include "../Logging/Logging.hpp"
|
||||
@@ -106,260 +107,18 @@ String SettingsWebServer::generateSettingsHTML() {
|
||||
String deviceUID = _configManager.getDeviceUID();
|
||||
String fwVersion = _configManager.getFwVersion();
|
||||
|
||||
String html = R"rawliteral(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VESPER Network Settings</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.header h1 {
|
||||
color: #667eea;
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.status-label {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-value {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-badge.ap {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
.mode-badge.station {
|
||||
background: #e8f5e9;
|
||||
color: #388e3c;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.mode-option {
|
||||
flex: 1;
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
.mode-option:hover {
|
||||
border-color: #667eea;
|
||||
background: #f0f4ff;
|
||||
}
|
||||
.mode-option.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.mode-option h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.mode-option p {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #f8f9fa;
|
||||
color: #666;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
.info-box {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: #856404;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>VESPER Settings</h1>
|
||||
<p>Network Configuration</p>
|
||||
</div>
|
||||
// Load HTML template from PROGMEM
|
||||
String html = String(FPSTR(SETTINGS_PAGE_HTML));
|
||||
|
||||
<div class="status-card">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current Mode:</span>
|
||||
<span class="status-value">
|
||||
<span class="mode-badge )rawliteral" + String(isAPMode ? "ap" : "station") + R"rawliteral(">
|
||||
)rawliteral" + String(isAPMode ? "AP Mode" : "Station Mode") + R"rawliteral(
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">IP Address:</span>
|
||||
<span class="status-value">)rawliteral" + currentIP + R"rawliteral(</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Device UID:</span>
|
||||
<span class="status-value">)rawliteral" + deviceUID + R"rawliteral(</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Firmware:</span>
|
||||
<span class="status-value">v)rawliteral" + fwVersion + R"rawliteral(</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Select Network Mode</div>
|
||||
|
||||
<div class="mode-selector">
|
||||
<div class="mode-option )rawliteral" + String(isAPMode ? "active" : "") + R"rawliteral(" onclick="selectMode('ap')">
|
||||
<h3>AP Mode</h3>
|
||||
<p>Direct Connection<br>192.168.4.1</p>
|
||||
</div>
|
||||
<div class="mode-option )rawliteral" + String(!isAPMode ? "active" : "") + R"rawliteral(" onclick="selectMode('station')">
|
||||
<h3>Router Mode</h3>
|
||||
<p>Connect via Router<br>WiFi/Ethernet</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="applyMode()">Apply & Reboot</button>
|
||||
<button class="btn btn-secondary" onclick="rebootDevice()">Reboot Device</button>
|
||||
|
||||
<div class="info-box">
|
||||
Device will reboot after applying changes. Make sure to reconnect to the correct network after reboot.
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
VESPER Bell Automation System<br>
|
||||
Advanced Bell Systems
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedMode = ')rawliteral" + String(isAPMode ? "ap" : "station") + R"rawliteral(';
|
||||
|
||||
function selectMode(mode) {
|
||||
selectedMode = mode;
|
||||
document.querySelectorAll('.mode-option').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
event.target.closest('.mode-option').classList.add('active');
|
||||
}
|
||||
|
||||
function applyMode() {
|
||||
if (confirm('Device will reboot and switch to ' + (selectedMode === 'ap' ? 'AP Mode' : 'Router Mode') + '. Continue?')) {
|
||||
fetch('/api/set-mode', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'mode=' + selectedMode
|
||||
}).then(response => {
|
||||
alert('Rebooting... Please wait 10 seconds and reconnect.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function rebootDevice() {
|
||||
if (confirm('Reboot device now?')) {
|
||||
fetch('/api/reboot', {method: 'POST'}).then(() => {
|
||||
alert('Rebooting... Please wait 10 seconds.');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
// Replace placeholders with dynamic values
|
||||
html.replace("%MODE_BADGE_CLASS%", isAPMode ? "ap" : "station");
|
||||
html.replace("%MODE_TEXT%", isAPMode ? "AP Mode" : "Station Mode");
|
||||
html.replace("%CURRENT_IP%", currentIP);
|
||||
html.replace("%DEVICE_UID%", deviceUID);
|
||||
html.replace("%FW_VERSION%", fwVersion);
|
||||
html.replace("%AP_ACTIVE_CLASS%", isAPMode ? "active" : "");
|
||||
html.replace("%STATION_ACTIVE_CLASS%", !isAPMode ? "active" : "");
|
||||
html.replace("%SELECTED_MODE%", isAPMode ? "ap" : "station");
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -480,7 +480,11 @@ void loop()
|
||||
// 🔥 DEBUG: Log every 10 seconds to verify we're still running
|
||||
static unsigned long lastLog = 0;
|
||||
if (millis() - lastLog > 10000) {
|
||||
LOG_DEBUG("❤️ Loop alive, free heap: %d", ESP.getFreeHeap());
|
||||
LOG_DEBUG("❤️ Loop alive | Free heap: %d bytes (%.1f KB) | Min free: %d | Largest block: %d",
|
||||
ESP.getFreeHeap(),
|
||||
ESP.getFreeHeap() / 1024.0,
|
||||
ESP.getMinFreeHeap(),
|
||||
ESP.getMaxAllocHeap());
|
||||
lastLog = millis();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user