Redesign web server to avoid lengthy system stats retrieval

This commit is contained in:
jpirnay
2026-04-11 08:28:06 +02:00
parent c0399185ce
commit d37621574c
14 changed files with 349 additions and 10 deletions
+63 -5
View File
@@ -17,6 +17,7 @@
#include "html/FilesPageHtml.generated.h"
#include "html/HomePageHtml.generated.h"
#include "html/SettingsPageHtml.generated.h"
#include "html/WelcomePageHtml.generated.h"
#include "html/js/jszip_minJs.generated.h"
namespace {
@@ -162,11 +163,13 @@ void CrossPointWebServer::begin() {
// Setup routes
LOG_DBG("WEB", "Setting up routes...");
server->on("/", HTTP_GET, [this] { handleRoot(); });
server->on("/files", HTTP_GET, [this] { handleFileList(); });
server->on("/", HTTP_GET, [this] { handleWelcomePage(); });
server->on("/systeminfo", HTTP_GET, [this] { handleSystemInfoPage(); });
server->on("/files", HTTP_GET, [this] { handleRoot(); });
server->on("/js/jszip.min.js", HTTP_GET, [this] { handleJszip(); });
server->on("/api/status", HTTP_GET, [this] { handleStatus(); });
server->on("/api/status/fast", HTTP_GET, [this] { handleStatusFast(); });
server->on("/api/files", HTTP_GET, [this] { handleFileListData(); });
server->on("/download", HTTP_GET, [this] { handleDownload(); });
@@ -353,8 +356,24 @@ static void sendHtmlContent(WebServer* server, const char* data, size_t len) {
}
void CrossPointWebServer::handleRoot() const {
int32_t t0 = millis();
sendHtmlContent(server.get(), FilesPageHtml, sizeof(FilesPageHtml));
int32_t t1 = millis();
LOG_DBG("WEB", "Served file manager page in %d ms", t1 - t0);
}
void CrossPointWebServer::handleWelcomePage() const {
int32_t t0 = millis();
sendHtmlContent(server.get(), WelcomePageHtml, sizeof(WelcomePageHtml));
int32_t t1 = millis();
LOG_DBG("WEB", "Served welcome page in %d ms", t1 - t0);
}
void CrossPointWebServer::handleSystemInfoPage() const {
int32_t t0 = millis();
sendHtmlContent(server.get(), HomePageHtml, sizeof(HomePageHtml));
LOG_DBG("WEB", "Served root page");
int32_t t1 = millis();
LOG_DBG("WEB", "Served system info page in %d ms", t1 - t0);
}
void CrossPointWebServer::handleJszip() const {
@@ -370,10 +389,14 @@ void CrossPointWebServer::handleNotFound() const {
}
void CrossPointWebServer::handleStatus() const {
const bool fastOnly = server->hasArg("phase") && server->arg("phase") == "fast";
const bool fastOnly = server->hasArg("phase") && server->arg("phase").equalsIgnoreCase("fast");
LOG_DBG("SYSINFO", "handleStatus request received (fastOnly=%d)", fastOnly);
int32_t t0 = millis();
SystemStatus status = SystemStatus::collectFast();
int32_t t1 = millis();
LOG_DBG("SYSINFO", "Collected fast status in %d ms (fastOnly=%d)", t1 - t0, fastOnly);
if (!fastOnly) {
LOG_DBG("SYSINFO", "handleStatus will collect SD stats");
SystemStatus::fillSdStatus(status);
}
@@ -404,6 +427,41 @@ void CrossPointWebServer::handleStatus() const {
server->send(200, "application/json", json);
}
void CrossPointWebServer::handleStatusFast() const {
LOG_DBG("SYSINFO", "handleStatusFast request received");
int32_t t0 = millis();
SystemStatus status = SystemStatus::collectFast();
int32_t t1 = millis();
LOG_DBG("SYSINFO", "Collected fast-only status in %d ms", t1 - t0);
JsonDocument doc;
doc["version"] = status.version;
doc["chipVersion"] = status.chipVersion;
doc["cpuMHz"] = status.cpuFreqMHz;
doc["ip"] = status.ip;
doc["mode"] = status.wifiMode;
doc["rssi"] = status.rssi;
doc["macAddress"] = status.macAddress;
doc["freeHeap"] = status.freeHeapBytes;
doc["minFreeHeap"] = status.minFreeHeapBytes;
doc["maxAllocHeap"] = status.maxAllocHeapBytes;
doc["flashTotal"] = status.flashBytes;
doc["flashAppUsed"] = status.flashAppUsedBytes;
doc["flashAppFree"] = status.flashAppFreeBytes;
doc["batteryPercent"] = status.batteryPercent;
doc["charging"] = status.charging;
doc["uptime"] = status.uptimeSeconds;
doc["sdReady"] = false;
// Still include SD stats in response, but client should ignore them when sdReady=false
doc["sdTotal"] = status.sdTotalBytes;
doc["sdUsed"] = status.sdUsedBytes;
doc["sdFree"] = status.sdFreeBytes;
String json;
serializeJson(doc, json);
server->send(200, "application/json", json);
}
void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const {
FsFile root = Storage.open(path);
if (!root) {
+3
View File
@@ -89,9 +89,12 @@ class CrossPointWebServer {
// Request handlers
void handleRoot() const;
void handleWelcomePage() const;
void handleSystemInfoPage() const;
void handleJszip() const;
void handleNotFound() const;
void handleStatus() const;
void handleStatusFast() const;
void handleFileList() const;
void handleFileListData() const;
void handleDownload() const;
+1 -1
View File
@@ -1742,9 +1742,9 @@
<body>
<div class="nav-links">
<a href="/">Home</a>
<a href="/files" class="active">File Manager</a>
<a href="/settings">Settings</a>
<a href="/systeminfo">System Info</a>
</div>
<div class="page-header">
+22 -2
View File
@@ -137,9 +137,9 @@
<h1>📚 CrossPoint Reader</h1>
<div class="nav-links">
<a href="/" class="active">Home</a>
<a href="/files">File Manager</a>
<a href="/settings">Settings</a>
<a href="/systeminfo" class="active">System Info</a>
</div>
<div class="card">
@@ -192,6 +192,14 @@
<span class="label">Uptime</span>
<span class="value" id="uptime">Loading...</span>
</div>
<div class="info-row">
<span class="label">Fast status fetch</span>
<span class="value" id="fast-status-duration">Loading...</span>
</div>
<div class="info-row">
<span class="label">Full status fetch</span>
<span class="value" id="full-status-duration">Not loaded</span>
</div>
<div class="info-row">
<span class="label">SD Card</span>
<span class="value" id="sd-space">Not loaded</span>
@@ -232,6 +240,10 @@
return h + 'h ' + String(m).padStart(2, '0') + 'm ' + String(s).padStart(2, '0') + 's';
}
function formatDuration(ms) {
return ms == null ? 'N/A' : ms + ' ms';
}
function applyStatus(data, includeSd) {
document.getElementById('version').textContent = data.version || 'N/A';
document.getElementById('chip-version').textContent = data.chipVersion || 'N/A';
@@ -269,16 +281,19 @@
}
async function fetchStatus() {
const start = performance.now();
try {
const fastResponse = await fetch('/api/status?phase=fast');
const fastResponse = await fetch('/api/status/fast');
if (!fastResponse.ok) {
throw new Error('Failed to fetch fast status: ' + fastResponse.status + ' ' + fastResponse.statusText);
}
const fastData = await fastResponse.json();
applyStatus(fastData, false);
document.getElementById('fast-status-duration').textContent = formatDuration(Math.round(performance.now() - start));
} catch (error) {
console.error('Error fetching status:', error);
document.getElementById('sd-space').textContent = SD_LABELS.ERROR;
document.getElementById('fast-status-duration').textContent = 'Error';
} finally {
if (!sdLoading) {
setSdButtonState(true);
@@ -296,6 +311,9 @@
button.disabled = true;
button.textContent = SD_LABELS.LOADING;
document.getElementById('sd-space').textContent = SD_LABELS.LOADING;
document.getElementById('full-status-duration').textContent = 'Loading...';
const start = performance.now();
try {
const response = await fetch('/api/status');
@@ -305,11 +323,13 @@
const data = await response.json();
applyStatus(data, true);
button.style.display = 'none';
document.getElementById('full-status-duration').textContent = formatDuration(Math.round(performance.now() - start));
} catch (error) {
console.error('Error fetching SD status:', error);
button.disabled = false;
button.textContent = SD_LABELS.LOAD_BUTTON;
document.getElementById('sd-space').textContent = SD_LABELS.ERROR;
document.getElementById('full-status-duration').textContent = 'Error';
} finally {
sdLoading = false;
}
+1 -1
View File
@@ -240,9 +240,9 @@
<h1>⚙️ Settings</h1>
<div class="nav-links">
<a href="/">Home</a>
<a href="/files">File Manager</a>
<a href="/settings" class="active">Settings</a>
<a href="/systeminfo">System Info</a>
</div>
<div id="message" class="message"></div>
+132
View File
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CrossPoint Reader</title>
<style>
:root {
--font-color: #333;
--bg: #f5f5f5;
--card-bg: #fff;
--accent-color: rgb(110, 154, 130);
--accent-color-dark: #5a8c73;
--subtitle-color: #7f8c8d;
}
@media (prefers-color-scheme: dark) {
:root {
--font-color: #f5f5f5;
--bg: #222;
--card-bg: #333;
--accent-color: #6fae83;
--accent-color-dark: #5a8c73;
--subtitle-color: #b0bfc7;
}
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;
background: radial-gradient(circle at top, rgba(110, 154, 130, 0.15), transparent 35%), var(--bg);
color: var(--font-color);
}
.card {
width: min(100%, 420px);
background: var(--card-bg);
border-radius: 20px;
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.09);
padding: 36px 28px;
text-align: center;
}
.logo {
font-size: 4rem;
margin-bottom: 16px;
}
h1 {
margin: 0;
letter-spacing: -0.04em;
font-size: 2rem;
}
.subtitle {
margin: 10px 0 24px;
color: var(--subtitle-color);
font-size: 1rem;
}
.version {
margin: 14px 0 24px;
font-size: 0.95rem;
color: var(--accent-color-dark);
}
.nav-links {
display: grid;
gap: 12px;
margin-top: 24px;
}
.nav-links a {
display: block;
padding: 14px 18px;
border-radius: 12px;
background: var(--accent-color);
color: white;
text-decoration: none;
font-weight: 600;
transition: background-color 0.15s ease;
}
.nav-links a:hover {
background: var(--accent-color-dark);
}
.footer {
margin-top: 28px;
color: var(--subtitle-color);
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="card">
<div class="logo">📚</div>
<h1>CrossPoint Reader</h1>
<div class="subtitle">Welcome to your reader</div>
<div class="version">Version <span id="version">Loading...</span></div>
<div class="nav-links">
<a href="/files">Open File Manager</a>
<a href="/settings">Settings</a>
<a href="/systeminfo">System Info</a>
</div>
<div class="footer">Fast start page designed for weak connections</div>
</div>
<script>
async function loadVersion() {
try {
const response = await fetch('/api/status/fast');
if (!response.ok) {
throw new Error('Failed to fetch version');
}
const data = await response.json();
document.getElementById('version').textContent = data.version || 'N/A';
} catch (error) {
document.getElementById('version').textContent = 'Unavailable';
}
}
document.addEventListener('DOMContentLoaded', loadVersion);
</script>
</body>
</html>