feat: add SD-backed stats archive and dedicated /stats web console
Tento commit je obsažen v:
Rozdílový obsah nebyl zobrazen, protože je příliš veliký
Načíst rozdílové porovnání
@@ -17,6 +17,20 @@ public:
|
||||
virtual ~WebPanelCommandRunner() = default;
|
||||
virtual void runWebCommand(const char* command, char* reply, size_t reply_size) = 0;
|
||||
virtual const char* getWebAdminPassword() const = 0;
|
||||
virtual bool isWebStatsEnabled() const { return false; }
|
||||
virtual bool formatWebStatsSummaryJson(char* reply, size_t reply_size) {
|
||||
if (reply != nullptr && reply_size > 0) {
|
||||
reply[0] = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool formatWebStatsSeriesJson(const char* series, char* reply, size_t reply_size) {
|
||||
(void)series;
|
||||
if (reply != nullptr && reply_size > 0) {
|
||||
reply[0] = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class WebPanelServer {
|
||||
@@ -45,6 +59,7 @@ private:
|
||||
|
||||
static esp_err_t handleIndex(httpd_req_t* req);
|
||||
static esp_err_t handleApp(httpd_req_t* req);
|
||||
static esp_err_t handleStatsPage(httpd_req_t* req);
|
||||
static esp_err_t handleLogin(httpd_req_t* req);
|
||||
static esp_err_t handleCommand(httpd_req_t* req);
|
||||
static esp_err_t handleStats(httpd_req_t* req);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "WebPrefs.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void WebPrefsStore::setDefaults(WebPrefs& prefs) {
|
||||
memset(&prefs, 0, sizeof(prefs));
|
||||
prefs.magic = kMagic;
|
||||
prefs.web_enabled = 1;
|
||||
prefs.web_stats_enabled = 1;
|
||||
}
|
||||
|
||||
bool WebPrefsStore::load(FILESYSTEM* fs, WebPrefs& prefs) {
|
||||
setDefaults(prefs);
|
||||
if (fs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fs->exists(kFilename)) {
|
||||
save(fs, prefs);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(kFilename, "r");
|
||||
#else
|
||||
File file = fs->open(kFilename);
|
||||
#endif
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WebPrefs persisted{};
|
||||
size_t bytes_to_read = min(static_cast<size_t>(file.size()), sizeof(persisted));
|
||||
bool ok = bytes_to_read >= sizeof(persisted.magic) &&
|
||||
file.read(reinterpret_cast<uint8_t*>(&persisted), bytes_to_read) == bytes_to_read;
|
||||
file.close();
|
||||
|
||||
if (!ok || persisted.magic != kMagic) {
|
||||
fs->remove(kFilename);
|
||||
save(fs, prefs);
|
||||
return false;
|
||||
}
|
||||
|
||||
prefs = persisted;
|
||||
prefs.web_enabled = prefs.web_enabled ? 1 : 0;
|
||||
prefs.web_stats_enabled = prefs.web_stats_enabled ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPrefsStore::save(FILESYSTEM* fs, const WebPrefs& prefs) {
|
||||
if (fs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (fs->exists(kFilename) && !fs->remove(kFilename)) {
|
||||
return false;
|
||||
}
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(kFilename, "w");
|
||||
#else
|
||||
File file = fs->open(kFilename, "w", true);
|
||||
#endif
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
bool ok = file.write(reinterpret_cast<const uint8_t*>(&prefs), sizeof(prefs)) == sizeof(prefs);
|
||||
file.close();
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct WebPrefs {
|
||||
uint32_t magic;
|
||||
uint8_t web_enabled;
|
||||
uint8_t web_stats_enabled;
|
||||
uint8_t reserved[2];
|
||||
};
|
||||
|
||||
class WebPrefsStore {
|
||||
public:
|
||||
static void setDefaults(WebPrefs& prefs);
|
||||
static bool load(FILESYSTEM* fs, WebPrefs& prefs);
|
||||
static bool save(FILESYSTEM* fs, const WebPrefs& prefs);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kMagic = 0x57454250;
|
||||
static constexpr const char* kFilename = "/web_prefs";
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "WebService.h"
|
||||
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
WebService::WebService() : _fs(nullptr), _prefs{}, _runner(nullptr), _network(nullptr) {
|
||||
WebPrefsStore::setDefaults(_prefs);
|
||||
}
|
||||
|
||||
void WebService::begin(FILESYSTEM* fs) {
|
||||
_fs = fs;
|
||||
WebPrefsStore::load(_fs, _prefs);
|
||||
}
|
||||
|
||||
void WebService::end() {
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
_panel.stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebService::loop() {
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
ensureWebServer();
|
||||
if (_panel.isRunning() && _panel.shouldAutoLock(millis())) {
|
||||
_panel.lockSession();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebService::setCommandRunner(WebPanelCommandRunner* runner) {
|
||||
_runner = runner;
|
||||
_panel.setCommandRunner(runner);
|
||||
}
|
||||
|
||||
bool WebService::savePrefs() {
|
||||
return WebPrefsStore::save(_fs, _prefs);
|
||||
}
|
||||
|
||||
bool WebService::setWebEnabled(bool enabled) {
|
||||
_prefs.web_enabled = enabled ? 1 : 0;
|
||||
bool ok = savePrefs();
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
if (_prefs.web_enabled != 0) {
|
||||
ensureWebServer();
|
||||
} else {
|
||||
_panel.stop();
|
||||
}
|
||||
#endif
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool WebService::setWebStatsEnabled(bool enabled) {
|
||||
_prefs.web_stats_enabled = enabled ? 1 : 0;
|
||||
return savePrefs();
|
||||
}
|
||||
|
||||
void WebService::formatWebStatusReply(char* reply, size_t reply_size) const {
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
if (_runner == nullptr || _prefs.web_enabled == 0) {
|
||||
snprintf(reply, reply_size, "> web:off");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_panel.isRunning() || _network == nullptr || !_network->isWifiConnected()) {
|
||||
snprintf(reply, reply_size, "> web:down");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(reply, reply_size, "> web:up url:https://%s/ auth:%s", WiFi.localIP().toString().c_str(),
|
||||
_panel.hasSessionToken() ? "unlocked" : "locked");
|
||||
#else
|
||||
snprintf(reply, reply_size, "> web:unsupported");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
void WebService::ensureWebServer() {
|
||||
if (_runner == nullptr || _prefs.web_enabled == 0 || _network == nullptr || !_network->isWifiConnected()) {
|
||||
_panel.stop();
|
||||
return;
|
||||
}
|
||||
_panel.start();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/NetworkStateProvider.h>
|
||||
|
||||
#include "WebPanelServer.h"
|
||||
#include "WebPrefs.h"
|
||||
|
||||
class WebService {
|
||||
public:
|
||||
WebService();
|
||||
|
||||
void begin(FILESYSTEM* fs);
|
||||
void end();
|
||||
void loop();
|
||||
|
||||
void setCommandRunner(WebPanelCommandRunner* runner);
|
||||
void setNetworkStateProvider(NetworkStateProvider* network) { _network = network; }
|
||||
|
||||
bool setWebEnabled(bool enabled);
|
||||
bool isWebEnabled() const { return _prefs.web_enabled != 0; }
|
||||
bool setWebStatsEnabled(bool enabled);
|
||||
bool isWebStatsEnabled() const { return _prefs.web_stats_enabled != 0; }
|
||||
|
||||
void formatWebStatusReply(char* reply, size_t reply_size) const;
|
||||
bool isPanelRunning() const { return _panel.isRunning(); }
|
||||
bool isPanelUnlocked() const { return _panel.hasSessionToken(); }
|
||||
|
||||
private:
|
||||
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
||||
void ensureWebServer();
|
||||
#endif
|
||||
bool savePrefs();
|
||||
|
||||
FILESYSTEM* _fs;
|
||||
WebPrefs _prefs;
|
||||
WebPanelCommandRunner* _runner;
|
||||
NetworkStateProvider* _network;
|
||||
WebPanelServer _panel;
|
||||
};
|
||||
Odkázat v novém úkolu
Zablokovat Uživatele