Merge pull request #60 from jpirnay/fix-undue-delay
refactor: Change webserver layout due avoid undue startup delays
This commit is contained in:
@@ -44,6 +44,12 @@ STR_CONNECT_WIFI_HINT: "Connect your device to this WiFi network"
|
||||
STR_OPEN_URL_HINT: "Open this URL in your browser"
|
||||
STR_OR_HTTP_PREFIX: "or http://"
|
||||
STR_SCAN_QR_HINT: "or scan QR code with your phone:"
|
||||
STR_RSSI: "RSSI"
|
||||
STR_NO_SIGNAL: "No signal"
|
||||
STR_SIGNAL_QUALITY_POOR: "Poor"
|
||||
STR_SIGNAL_QUALITY_WEAK: "Weak"
|
||||
STR_SIGNAL_QUALITY_GOOD: "Good"
|
||||
STR_SIGNAL_QUALITY_EXCELLENT: "Excellent"
|
||||
STR_CALIBRE_WIRELESS: "Calibre Wireless"
|
||||
STR_CALIBRE_WEB_URL: "Calibre Web URL"
|
||||
STR_NETWORK_LEGEND: "* = Encrypted | + = Saved"
|
||||
|
||||
@@ -44,6 +44,12 @@ STR_CONNECT_WIFI_HINT: "Connectez un appareil à ce WiFi"
|
||||
STR_OPEN_URL_HINT: "Ouvrez cette URL dans un navigateur"
|
||||
STR_OR_HTTP_PREFIX: "ou http://"
|
||||
STR_SCAN_QR_HINT: "ou scannez le QR code :"
|
||||
STR_RSSI: "RSSI"
|
||||
STR_NO_SIGNAL: "Pas de signal"
|
||||
STR_SIGNAL_QUALITY_POOR: "Faible"
|
||||
STR_SIGNAL_QUALITY_WEAK: "Très faible"
|
||||
STR_SIGNAL_QUALITY_GOOD: "Bon"
|
||||
STR_SIGNAL_QUALITY_EXCELLENT: "Excellent"
|
||||
STR_CALIBRE_WIRELESS: "Connexion Calibre sans fil"
|
||||
STR_CALIBRE_WEB_URL: "URL Web Calibre"
|
||||
STR_NETWORK_LEGEND: "* = Sécurisé | + = Sauvegardé"
|
||||
|
||||
@@ -44,6 +44,12 @@ STR_CONNECT_WIFI_HINT: "Gerät mit diesem WLAN verbinden"
|
||||
STR_OPEN_URL_HINT: "Diese URL im Browser öffnen"
|
||||
STR_OR_HTTP_PREFIX: "oder http://"
|
||||
STR_SCAN_QR_HINT: "oder QR-Code mit dem Handy scannen:"
|
||||
STR_RSSI: "RSSI"
|
||||
STR_NO_SIGNAL: "Kein Signal"
|
||||
STR_SIGNAL_QUALITY_POOR: "Schlecht"
|
||||
STR_SIGNAL_QUALITY_WEAK: "Schwach"
|
||||
STR_SIGNAL_QUALITY_GOOD: "Gut"
|
||||
STR_SIGNAL_QUALITY_EXCELLENT: "Ausgezeichnet"
|
||||
STR_CALIBRE_WIRELESS: "Calibre Wireless"
|
||||
STR_CALIBRE_WEB_URL: "Calibre-Web-URL"
|
||||
STR_NETWORK_LEGEND: "* = Verschlüsselt | + = Gespeichert"
|
||||
|
||||
@@ -44,6 +44,12 @@ STR_CONNECT_WIFI_HINT: "Connettere il dispositivo a questa rete WiFi"
|
||||
STR_OPEN_URL_HINT: "Aprire questo URL nel browser"
|
||||
STR_OR_HTTP_PREFIX: "o http://"
|
||||
STR_SCAN_QR_HINT: "oppure scansionare il codice QR col telefono:"
|
||||
STR_RSSI: "RSSI"
|
||||
STR_NO_SIGNAL: "Nessun segnale"
|
||||
STR_SIGNAL_QUALITY_POOR: "Scarso"
|
||||
STR_SIGNAL_QUALITY_WEAK: "Debole"
|
||||
STR_SIGNAL_QUALITY_GOOD: "Buono"
|
||||
STR_SIGNAL_QUALITY_EXCELLENT: "Eccellente"
|
||||
STR_CALIBRE_WIRELESS: "Calibre wireless"
|
||||
STR_CALIBRE_WEB_URL: "URL Web Calibre"
|
||||
STR_NETWORK_LEGEND: "* = Protetta | + = Salvata"
|
||||
|
||||
@@ -44,6 +44,12 @@ STR_CONNECT_WIFI_HINT: "Подключите устройство к этой с
|
||||
STR_OPEN_URL_HINT: "Откройте этот адрес в браузере"
|
||||
STR_OR_HTTP_PREFIX: "или http://"
|
||||
STR_SCAN_QR_HINT: "или отсканируйте QR-код:"
|
||||
STR_RSSI: "RSSI"
|
||||
STR_NO_SIGNAL: "Нет сигнала"
|
||||
STR_SIGNAL_QUALITY_POOR: "Плохо"
|
||||
STR_SIGNAL_QUALITY_WEAK: "Слабо"
|
||||
STR_SIGNAL_QUALITY_GOOD: "Хорошо"
|
||||
STR_SIGNAL_QUALITY_EXCELLENT: "Отлично"
|
||||
STR_CALIBRE_WIRELESS: "Calibre по Wi-Fi"
|
||||
STR_CALIBRE_WEB_URL: "Web-адрес Calibre"
|
||||
STR_NETWORK_LEGEND: "* = Защищена | + = Сохранена"
|
||||
|
||||
+95
-1
@@ -1,10 +1,85 @@
|
||||
import configparser
|
||||
import os
|
||||
import re
|
||||
import gzip
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
SRC_DIR = "src"
|
||||
|
||||
|
||||
CROSSPOINT_NAME = "CrossPoint Reader"
|
||||
PLACEHOLDERS = {
|
||||
"%%CROSSPOINT%%": CROSSPOINT_NAME,
|
||||
}
|
||||
|
||||
|
||||
def warn(msg: str) -> None:
|
||||
print(f"WARNING [build_html.py]: {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def get_base_version(project_dir: str) -> str:
|
||||
ini_path = os.path.join(project_dir, "platformio.ini")
|
||||
if not os.path.isfile(ini_path):
|
||||
warn(f"platformio.ini not found at {ini_path}; using 0.0.0")
|
||||
return "0.0.0"
|
||||
config = configparser.ConfigParser()
|
||||
config.read(ini_path)
|
||||
if not config.has_option("crosspoint", "version"):
|
||||
warn("No [crosspoint] section or version in platformio.ini; using 0.0.0")
|
||||
return "0.0.0"
|
||||
return config.get("crosspoint", "version")
|
||||
|
||||
|
||||
def get_git_branch(project_dir: str) -> str:
|
||||
try:
|
||||
branch = subprocess.check_output(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||
text=True,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
).strip()
|
||||
if branch == "HEAD":
|
||||
branch = subprocess.check_output(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
text=True,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
).strip()
|
||||
return "".join(c for c in branch if c not in '"\\')
|
||||
except FileNotFoundError:
|
||||
warn('git not found on PATH; branch suffix will be "unknown"')
|
||||
return "unknown"
|
||||
except subprocess.CalledProcessError as e:
|
||||
warn(
|
||||
f'git command failed (exit {e.returncode}): {e.stderr.strip()}; branch suffix will be "unknown"'
|
||||
)
|
||||
return "unknown"
|
||||
except Exception as e:
|
||||
warn(
|
||||
f'Unexpected error reading git branch: {e}; branch suffix will be "unknown"'
|
||||
)
|
||||
return "unknown"
|
||||
|
||||
|
||||
def get_version_string(project_dir: str) -> str:
|
||||
env_version = os.environ.get("CROSSPOINT_VERSION")
|
||||
if env_version:
|
||||
return env_version.strip('"')
|
||||
base_version = get_base_version(project_dir)
|
||||
pioenv = os.environ.get("PIOENV", "default")
|
||||
if pioenv == "default":
|
||||
branch = get_git_branch(project_dir)
|
||||
return f"{base_version}-dev+{branch}"
|
||||
return base_version
|
||||
|
||||
|
||||
def replace_placeholders(html: str, replacements: dict) -> str:
|
||||
for placeholder, replacement in replacements.items():
|
||||
html = html.replace(placeholder, replacement)
|
||||
return html
|
||||
|
||||
|
||||
def strip_js_comments(js: str) -> str:
|
||||
"""Remove JS comments while preserving string literals and URLs."""
|
||||
result = []
|
||||
@@ -159,6 +234,24 @@ def sanitize_identifier(name: str) -> str:
|
||||
return sanitized
|
||||
|
||||
|
||||
def get_project_dir() -> str:
|
||||
try:
|
||||
Import("env") # type: ignore[name-defined]
|
||||
return env["PROJECT_DIR"]
|
||||
except NameError:
|
||||
if "__file__" in globals():
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
elif sys.argv and sys.argv[0]:
|
||||
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
else:
|
||||
script_dir = os.getcwd()
|
||||
return os.path.dirname(script_dir)
|
||||
|
||||
|
||||
project_dir = get_project_dir()
|
||||
version_string = get_version_string(project_dir)
|
||||
PLACEHOLDERS["%%VERSION%%"] = version_string
|
||||
|
||||
for root, _, files in os.walk(SRC_DIR):
|
||||
for file in files:
|
||||
if file.endswith(".html") or file.endswith(".js"):
|
||||
@@ -166,8 +259,9 @@ for root, _, files in os.walk(SRC_DIR):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Only minify HTML files; JS files are typically pre-minified (e.g., jszip.min.js)
|
||||
# Replace build-time placeholders only in HTML files
|
||||
if file.endswith(".html"):
|
||||
content = replace_placeholders(content, PLACEHOLDERS)
|
||||
processed = minify_html(content)
|
||||
else:
|
||||
processed = content
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
@@ -72,9 +73,11 @@ struct SystemStatus {
|
||||
}
|
||||
|
||||
static void fillSdStatus(SystemStatus& s) {
|
||||
uint32_t t0 = millis();
|
||||
s.sdTotalBytes = Storage.sdTotalBytes();
|
||||
s.sdUsedBytes = Storage.sdUsedBytes();
|
||||
s.sdFreeBytes = Storage.sdFreeBytes();
|
||||
LOG_DBG("SYSINFO", "Filled SD status in %u ms", millis() - t0);
|
||||
}
|
||||
|
||||
static SystemStatus collect() {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "WifiSelectionActivity.h"
|
||||
#include "activities/network/SignalStrengthWidget.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
@@ -29,6 +30,8 @@ void CalibreConnectActivity::onEnter() {
|
||||
lastCompleteName.clear();
|
||||
lastCompleteAt = 0;
|
||||
lastProcessedCompleteAt = 0;
|
||||
currentRssi = 0;
|
||||
lastRssiUpdateTime = 0;
|
||||
exitRequested = false;
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
@@ -126,6 +129,12 @@ void CalibreConnectActivity::loop() {
|
||||
}
|
||||
lastHandleClientTime = millis();
|
||||
|
||||
if (millis() - lastRssiUpdateTime > 5000) {
|
||||
lastRssiUpdateTime = millis();
|
||||
currentRssi = WiFi.RSSI();
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
const auto status = webServer->getWsUploadStatus();
|
||||
bool changed = false;
|
||||
if (status.inProgress) {
|
||||
@@ -190,8 +199,15 @@ void CalibreConnectActivity::render(RenderLock&&) {
|
||||
|
||||
const int textX = contentRect.x + metrics.contentSidePadding;
|
||||
const int textWidth = contentRect.width - metrics.contentSidePadding * 2;
|
||||
int y = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing * 4;
|
||||
int y = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing * 2;
|
||||
const auto heightText12 = renderer.getTextHeight(UI_12_FONT_ID);
|
||||
const auto lineHeightSmall = renderer.getLineHeight(SMALL_FONT_ID);
|
||||
|
||||
const int signalHeight = 22;
|
||||
drawWifiSignalStrength(renderer, textX, y, textWidth, signalHeight, currentRssi);
|
||||
renderer.drawText(SMALL_FONT_ID, textX, y + signalHeight + 2, rssiLabel(currentRssi).c_str());
|
||||
y += signalHeight + lineHeightSmall + metrics.verticalSpacing * 3;
|
||||
|
||||
renderer.drawText(UI_12_FONT_ID, textX, y, tr(STR_CALIBRE_SETUP), true, EpdFontFamily::BOLD);
|
||||
y += heightText12 + metrics.verticalSpacing * 2;
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ class CalibreConnectActivity final : public Activity {
|
||||
std::string lastCompleteName;
|
||||
unsigned long lastCompleteAt = 0;
|
||||
unsigned long lastProcessedCompleteAt = 0; // Track which server value we've already processed
|
||||
int currentRssi = 0;
|
||||
unsigned long lastRssiUpdateTime = 0;
|
||||
bool exitRequested = false;
|
||||
|
||||
void renderServerRunning() const;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "NetworkModeSelectionActivity.h"
|
||||
#include "WifiSelectionActivity.h"
|
||||
#include "activities/network/CalibreConnectActivity.h"
|
||||
#include "activities/network/SignalStrengthWidget.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/QrUtils.h"
|
||||
@@ -244,6 +245,10 @@ void CrossPointWebServerActivity::startWebServer() {
|
||||
|
||||
if (webServer->isRunning()) {
|
||||
state = WebServerActivityState::SERVER_RUNNING;
|
||||
if (!isApMode) {
|
||||
currentRssi = WiFi.RSSI();
|
||||
lastRssiUpdateTime = millis();
|
||||
}
|
||||
LOG_DBG("WEBACT", "Web server started successfully");
|
||||
|
||||
// Force an immediate render since we're transitioning from a subactivity
|
||||
@@ -293,6 +298,12 @@ void CrossPointWebServerActivity::loop() {
|
||||
LOG_DBG("WEBACT", "Warning: Weak WiFi signal: %d dBm", rssi);
|
||||
}
|
||||
}
|
||||
|
||||
if (millis() - lastRssiUpdateTime > 5000) { // Refresh signal indicator every 5 seconds
|
||||
lastRssiUpdateTime = millis();
|
||||
currentRssi = WiFi.RSSI();
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle web server requests - maximize throughput with watchdog safety
|
||||
@@ -366,6 +377,8 @@ void CrossPointWebServerActivity::render(RenderLock&&) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {} // namespace
|
||||
|
||||
void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
|
||||
@@ -412,6 +425,12 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
hostnameUrl.c_str());
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding + QR_CODE_WIDTH + metrics.verticalSpacing, startY + 100,
|
||||
ipUrl.c_str());
|
||||
|
||||
const int signalHeight = 22;
|
||||
const int signalWidth = contentRect.width - metrics.contentSidePadding * 2;
|
||||
const int signalY = startY + 120;
|
||||
drawWifiSignalStrength(renderer, contentRect.x + metrics.contentSidePadding, signalY, signalWidth, signalHeight, 0);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, signalY + signalHeight + 2, tr(STR_HOTSPOT_MODE));
|
||||
} else {
|
||||
startY += metrics.verticalSpacing * 2;
|
||||
|
||||
@@ -435,8 +454,19 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
// Also show hostname URL
|
||||
std::string hostnameUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY, hostnameUrl.c_str(), true);
|
||||
|
||||
// AP mode: no external RSSI metric available, but keep UI spacing consistent.
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
if (!isApMode) {
|
||||
const int signalHeight = 22;
|
||||
const int signalWidth = contentRect.width - metrics.contentSidePadding * 2;
|
||||
const int signalY = startY + height10 + metrics.verticalSpacing * 2;
|
||||
drawWifiSignalStrength(renderer, contentRect.x + metrics.contentSidePadding, signalY, signalWidth, signalHeight,
|
||||
currentRssi);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, signalY + signalHeight + 2, rssiLabel(currentRssi).c_str(), true);
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ class CrossPointWebServerActivity final : public Activity {
|
||||
// Server status
|
||||
std::string connectedIP;
|
||||
std::string connectedSSID; // For STA mode: network name, For AP mode: AP name
|
||||
int currentRssi = 0;
|
||||
unsigned long lastRssiUpdateTime = 0;
|
||||
|
||||
// Performance monitoring
|
||||
unsigned long lastHandleClientTime = 0;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
inline StrId getRssiQualityStrId(int rssi) {
|
||||
if (rssi <= -90) {
|
||||
return StrId::STR_SIGNAL_QUALITY_POOR;
|
||||
}
|
||||
if (rssi <= -75) {
|
||||
return StrId::STR_SIGNAL_QUALITY_WEAK;
|
||||
}
|
||||
if (rssi <= -60) {
|
||||
return StrId::STR_SIGNAL_QUALITY_GOOD;
|
||||
}
|
||||
return StrId::STR_SIGNAL_QUALITY_EXCELLENT;
|
||||
}
|
||||
|
||||
inline void drawWifiSignalStrength(const GfxRenderer& renderer, int x, int y, int width, int height, int rssi) {
|
||||
const int barCount = 4;
|
||||
const int gap = 6;
|
||||
const int barWidth = std::max(1, std::min(10, (width - (barCount - 1) * gap) / barCount));
|
||||
const int totalWidth = barCount * barWidth + (barCount - 1) * gap;
|
||||
const int startX = x + (width - totalWidth) / 2;
|
||||
const int maxBarHeight = std::max(1, height - 8);
|
||||
const int bars = rssi == 0 ? 0 : (rssi <= -90 ? 1 : (rssi <= -75 ? 2 : (rssi <= -60 ? 3 : 4)));
|
||||
const int baseY = y + height - 2;
|
||||
for (int i = 0; i < barCount; ++i) {
|
||||
const int barHeight = std::max(0, ((i + 1) * maxBarHeight) / barCount);
|
||||
if (barHeight <= 0 || barWidth <= 0) {
|
||||
continue;
|
||||
}
|
||||
const int barX = startX + i * (barWidth + gap);
|
||||
const int barY = baseY - barHeight;
|
||||
renderer.drawRect(barX, barY, barWidth, barHeight, true);
|
||||
if (i < bars && barWidth > 2 && barHeight > 2) {
|
||||
renderer.fillRect(barX + 1, barY + 1, barWidth - 2, barHeight - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string rssiLabel(int rssi) {
|
||||
if (rssi == 0) {
|
||||
return std::string(tr(STR_NO_SIGNAL));
|
||||
}
|
||||
const char* quality = I18N.get(getRssiQualityStrId(rssi));
|
||||
return std::string(tr(STR_RSSI)) + ": " + std::to_string(rssi) + " dBm (" + quality + ")";
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%%CROSSPOINT%%</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%%</h1>
|
||||
<div class="subtitle">Welcome to your reader</div>
|
||||
<div class="version">Version %%VERSION%%</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>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user