Add dashboard audit log and configuration management

- Implemented dashboard audit logging with InsertAuditLog, GetRecentAuditLogs, GetAuditLogsByIP, and CleanupOldAuditLogs methods.
- Created dashboard configuration management with GetDashboardConfig and SetDashboardConfig methods.
- Added new tables for dashboard_audit_log and dashboard_config in both MySQL and SQLite migrations.
- Updated CMakeLists to include Crow and ASIO for dashboard server functionality.
- Enhanced existing database classes to support new dashboard features, including character, play key, and property management.
- Added new methods for retrieving and managing play keys, properties, and pet names.
- Updated TestSQLDatabase to include stubs for new dashboard-related methods.
- Modified shared and dashboard configuration files for new settings.
This commit is contained in:
Aaron Kimbrell
2026-04-22 11:01:41 -05:00
parent d532a9b063
commit e3467465b4
92 changed files with 9133 additions and 77 deletions

View File

@@ -1,59 +1,153 @@
#include "DashboardWeb.h"
#include "DashboardShared.h"
// Blueprint includes
#include "blueprints/AuthBlueprint.h"
#include "blueprints/ApiBlueprint.h"
#include "blueprints/PageBlueprint.h"
#include "blueprints/PlayKeysBlueprint.h"
#include "blueprints/CharactersBlueprint.h"
#include "blueprints/MailBlueprint.h"
#include "blueprints/BugReportsBlueprint.h"
#include "blueprints/ModerationBlueprint.h"
// Crow headers - must come before ASIO to avoid conflicts
#include "crow.h"
#include "crow/middlewares/session.h"
// thanks bill gates
#ifdef _WIN32
#undef min
#undef max
#endif
#include "inja.hpp"
#include "eHTTPMethod.h"
// simple home page with inja
void HandleHTTPHomeRequest(HTTPReply& reply, std::string body) {
try {
inja::Environment env;
env.set_trim_blocks(true);
env.set_lstrip_blocks(true);
nlohmann::json data;
data["title"] = "Darkflame Universe Dashboard";
data["header"] = "Welcome to the Darkflame Universe Dashboard";
data["message"] = "This is a simple dashboard page served using Inja templating engine.";
const std::string template_str = R"(
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>{{ header }}</h1>
<p>{{ message }}</p>
</body>
</html>
)";
std::string rendered = env.render(template_str, data);
reply.message = rendered;
reply.status = eHTTPStatusCode::OK;
reply.contentType = ContentType::HTML;
} catch (const std::exception& e) {
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
reply.message = "Internal Server Error";
reply.contentType = ContentType::PLAIN;
}
}
#include <memory>
#include <thread>
#include <chrono>
#include <iostream>
namespace DashboardWeb {
void RegisterRoutes() {
Game::web.RegisterHTTPRoute({
.path = "/",
.method = eHTTPMethod::GET,
.handle = HandleHTTPHomeRequest
});
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
static crow::App<crow::CookieParser, Session> g_App {
Session{
// cookie config: use "session" cookie name, 24h max_age
crow::CookieParser::Cookie("session").max_age(24 * 60 * 60).path("/"),
// session id length
32,
// storage backend (InMemoryStore)
crow::InMemoryStore{}
}
};
static std::future<void> g_ServerFuture;
static bool g_Running = false;
static bool g_Initialized = false;
void SetupRoutes() {
static bool setupCalled = false;
if (setupCalled) {
std::cerr << "WARNING: SetupRoutes() called multiple times!" << std::endl;
return;
}
setupCalled = true;
std::cerr << "Setting up dashboard routes..." << std::endl;
// Set mustache template base directory
crow::mustache::set_base("./templates");
// Setup all blueprint routes
try {
std::cerr << " - Setting up AuthBlueprint..." << std::endl;
AuthBlueprint::Setup(g_App);
std::cerr << " - Setting up ApiBlueprint..." << std::endl;
ApiBlueprint::Setup(g_App);
std::cerr << " - Setting up PageBlueprint..." << std::endl;
PageBlueprint::Setup(g_App);
std::cerr << " - Setting up PlayKeysBlueprint..." << std::endl;
PlayKeysBlueprint::Setup(g_App);
std::cerr << " - Setting up CharactersBlueprint..." << std::endl;
CharactersBlueprint::Setup(g_App);
std::cerr << " - Setting up MailBlueprint..." << std::endl;
MailBlueprint::Setup(g_App);
std::cerr << " - Setting up BugReportsBlueprint..." << std::endl;
BugReportsBlueprint::Setup(g_App);
std::cerr << " - Setting up ModerationBlueprint..." << std::endl;
ModerationBlueprint::Setup(g_App);
std::cerr << "All routes set up successfully!" << std::endl;
} catch (const std::exception& e) {
// Print to stderr since LOG might not be available
std::cerr << "Error setting up routes: " << e.what() << std::endl;
throw;
}
}
}
void Initialize(uint32_t port) {
// Only allow initialization once per process lifetime
// Crow apps cannot be restarted once stopped
if (g_Initialized) {
std::cerr << "Dashboard web server already initialized. Cannot reinitialize." << std::endl;
return;
}
try {
// Setup routes (only happens once)
SetupRoutes();
// Configure Crow app
g_App.loglevel(crow::LogLevel::Info); // Changed to Info to see startup messages
// Start the server in a separate thread
g_ServerFuture = std::async(std::launch::async, [port]() {
try {
g_App.port(port).multithreaded().run();
} catch (const std::exception& e) {
std::cerr << "Error running Crow server: " << e.what() << std::endl;
}
});
g_Running = true;
g_Initialized = true;
// Give the server a moment to start
std::this_thread::sleep_for(std::chrono::milliseconds(500));
} catch (const std::exception& e) {
std::cerr << "Error initializing dashboard web server: " << e.what() << std::endl;
throw;
}
}
void Update() {
// Crow runs in its own thread, nothing to update here
}
void Stop() {
if (!g_Running) {
return;
}
g_App.stop();
// Wait for the server thread to finish (with timeout)
if (g_ServerFuture.valid()) {
auto status = g_ServerFuture.wait_for(std::chrono::seconds(5));
if (status == std::future_status::timeout) {
std::cerr << "Warning: Dashboard web server did not stop gracefully" << std::endl;
}
}
g_Running = false;
}
} // namespace DashboardWeb