mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-21 04:02:48 +00:00
feat(dashboard): browse every client system info report, per account and across accounts
The account page lists the account's reports as a table and the Client System Info page lists every report (or each account's newest), sortable and searchable by account name or video card; a row opens every field. Values that say little about the real system are marked, and every caveat is a tooltip on the value and column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
34
dDatabase/GameDatabase/ITables/ClientSysInfoSql.h
Normal file
34
dDatabase/GameDatabase/ITables/ClientSysInfoSql.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "IClientSysInfo.h"
|
||||
|
||||
// The SQL both databases use to browse client_sysinfo; `like` is the pattern for "contains ?". Parameters, in order:
|
||||
// accountId, accountId, search, pattern, pattern.
|
||||
namespace ClientSysInfoSql {
|
||||
inline std::string From(const IClientSysInfo::SysInfoQuery& q, const std::string& like) {
|
||||
std::string from = " FROM client_sysinfo AS c LEFT JOIN accounts AS a ON a.id = c.account_id";
|
||||
if (q.latestOnly) from += " JOIN (SELECT MAX(id) AS id FROM client_sysinfo GROUP BY account_id) AS n ON n.id = c.id";
|
||||
return from + " WHERE (? = 0 OR c.account_id = ?) AND (? = '' OR a.name LIKE " + like + " ESCAPE '!' OR c.video_card LIKE " + like + " ESCAPE '!')";
|
||||
}
|
||||
|
||||
inline std::string Order(const IClientSysInfo::SysInfoQuery& q) {
|
||||
using eSysInfoOrder = IClientSysInfo::eSysInfoOrder;
|
||||
const std::string dir = q.ascending ? " ASC" : " DESC";
|
||||
std::string columns;
|
||||
switch (q.order) {
|
||||
case eSysInfoOrder::LAST_SEEN: columns = "c.last_seen" + dir; break;
|
||||
case eSysInfoOrder::ACCOUNT: columns = "a.name" + dir; break;
|
||||
case eSysInfoOrder::LOGINS: columns = "c.logins" + dir; break;
|
||||
case eSysInfoOrder::OS_VERSION: columns = "c.os_major_version" + dir + ", c.os_minor_version" + dir + ", c.os_build_number" + dir; break;
|
||||
case eSysInfoOrder::VIDEO_CARD: columns = "c.video_card" + dir; break;
|
||||
case eSysInfoOrder::PROCESSORS: columns = "c.number_of_processors" + dir; break;
|
||||
case eSysInfoOrder::MEMORY: columns = "c.memory_total_kb" + dir; break;
|
||||
case eSysInfoOrder::CLIENT_OS: columns = "c.client_os" + dir; break;
|
||||
case eSysInfoOrder::FIRST_SEEN: columns = "c.first_seen" + dir; break;
|
||||
}
|
||||
// Equal values come in row order, the same on every database
|
||||
return " ORDER BY " + columns + ", c.id" + dir;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
uint32_t minorVersion{};
|
||||
uint32_t buildNumber{};
|
||||
uint32_t platformId{};
|
||||
std::string accountName; // only filled by ListClientSysInfo
|
||||
|
||||
// Whether two logins describe the same client (memory in use and the time are left out: they change each login)
|
||||
bool SameAs(const SysInfoRow& other) const {
|
||||
@@ -53,6 +54,23 @@ public:
|
||||
|
||||
// Each account's newest row, for the spread across players
|
||||
virtual std::vector<SysInfoRow> GetLatestClientSysInfo(uint32_t limit) = 0;
|
||||
|
||||
// What ListClientSysInfo sorts by
|
||||
enum class eSysInfoOrder : uint8_t { LAST_SEEN, ACCOUNT, LOGINS, OS_VERSION, VIDEO_CARD, PROCESSORS, MEMORY, CLIENT_OS, FIRST_SEEN };
|
||||
|
||||
struct SysInfoQuery {
|
||||
std::string search; // part of the account name or video card
|
||||
uint32_t accountId{}; // 0: every account
|
||||
bool latestOnly{}; // only each account's newest row
|
||||
eSysInfoOrder order{ eSysInfoOrder::LAST_SEEN };
|
||||
bool ascending{};
|
||||
uint32_t offset{};
|
||||
uint32_t limit{ 25 };
|
||||
};
|
||||
|
||||
// Rows across accounts for browsing, with the account's name
|
||||
virtual std::vector<SysInfoRow> ListClientSysInfo(const SysInfoQuery& query) = 0;
|
||||
virtual uint64_t CountClientSysInfo(const SysInfoQuery& query) = 0;
|
||||
};
|
||||
|
||||
#endif //!__ICLIENTSYSINFO__H__
|
||||
|
||||
@@ -351,6 +351,8 @@ public:
|
||||
void RecordClientSysInfo(const SysInfoRow& info) override;
|
||||
std::vector<SysInfoRow> GetClientSysInfo(uint32_t accountId, uint32_t limit) override;
|
||||
std::vector<SysInfoRow> GetLatestClientSysInfo(uint32_t limit) override;
|
||||
std::vector<SysInfoRow> ListClientSysInfo(const SysInfoQuery& query) override;
|
||||
uint64_t CountClientSysInfo(const SysInfoQuery& query) override;
|
||||
void InsertModerationDecision(const std::string& kind, int64_t subjectId, const std::string& subject, bool approved, const std::string& reason, int64_t time) override;
|
||||
nlohmann::json GetModerationDecisions(const std::string& kind, int64_t subjectId, uint32_t limit) override;
|
||||
Totp GetTotp(uint32_t accountId) override;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
#include "ClientSysInfoSql.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
namespace {
|
||||
IClientSysInfo::SysInfoRow Row(sql::ResultSet& r) {
|
||||
IClientSysInfo::SysInfoRow Row(sql::ResultSet& r, const bool withName = false) {
|
||||
IClientSysInfo::SysInfoRow s;
|
||||
s.id = r.getUInt64("id");
|
||||
s.accountId = r.getUInt("account_id");
|
||||
@@ -22,6 +25,7 @@ namespace {
|
||||
s.minorVersion = r.getUInt("os_minor_version");
|
||||
s.buildNumber = r.getUInt("os_build_number");
|
||||
s.platformId = r.getUInt("os_platform_id");
|
||||
if (withName) s.accountName = r.getString("account_name").c_str();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -56,3 +60,19 @@ std::vector<IClientSysInfo::SysInfoRow> MySQLDatabase::GetLatestClientSysInfo(co
|
||||
while (result->next()) rows.push_back(Row(*result.m_resultSet));
|
||||
return rows;
|
||||
}
|
||||
|
||||
std::vector<IClientSysInfo::SysInfoRow> MySQLDatabase::ListClientSysInfo(const SysInfoQuery& q) {
|
||||
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto result = ExecuteSelect("SELECT c.*, COALESCE(a.name, '') AS account_name" + ClientSysInfoSql::From(q, "CONCAT('%', ?, '%')") +
|
||||
ClientSysInfoSql::Order(q) + " LIMIT ? OFFSET ?;", q.accountId, q.accountId, q.search, pattern, pattern, q.limit, q.offset);
|
||||
std::vector<SysInfoRow> rows;
|
||||
while (result->next()) rows.push_back(Row(*result.m_resultSet, true));
|
||||
return rows;
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::CountClientSysInfo(const SysInfoQuery& q) {
|
||||
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto result = ExecuteSelect("SELECT COUNT(*) AS count" + ClientSysInfoSql::From(q, "CONCAT('%', ?, '%')") + ";",
|
||||
q.accountId, q.accountId, q.search, pattern, pattern);
|
||||
return result->next() ? result->getUInt64("count") : 0;
|
||||
}
|
||||
|
||||
@@ -335,6 +335,8 @@ public:
|
||||
void RecordClientSysInfo(const SysInfoRow& info) override;
|
||||
std::vector<SysInfoRow> GetClientSysInfo(uint32_t accountId, uint32_t limit) override;
|
||||
std::vector<SysInfoRow> GetLatestClientSysInfo(uint32_t limit) override;
|
||||
std::vector<SysInfoRow> ListClientSysInfo(const SysInfoQuery& query) override;
|
||||
uint64_t CountClientSysInfo(const SysInfoQuery& query) override;
|
||||
void InsertModerationDecision(const std::string& kind, int64_t subjectId, const std::string& subject, bool approved, const std::string& reason, int64_t time) override;
|
||||
nlohmann::json GetModerationDecisions(const std::string& kind, int64_t subjectId, uint32_t limit) override;
|
||||
Totp GetTotp(uint32_t accountId) override;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
#include "ClientSysInfoSql.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
namespace {
|
||||
IClientSysInfo::SysInfoRow Row(CppSQLite3Query& r) {
|
||||
IClientSysInfo::SysInfoRow Row(CppSQLite3Query& r, const bool withName = false) {
|
||||
IClientSysInfo::SysInfoRow s;
|
||||
s.id = static_cast<uint64_t>(r.getInt64Field("id"));
|
||||
s.accountId = static_cast<uint32_t>(r.getInt64Field("account_id"));
|
||||
@@ -22,6 +25,7 @@ namespace {
|
||||
s.minorVersion = static_cast<uint32_t>(r.getInt64Field("os_minor_version"));
|
||||
s.buildNumber = static_cast<uint32_t>(r.getInt64Field("os_build_number"));
|
||||
s.platformId = static_cast<uint32_t>(r.getInt64Field("os_platform_id"));
|
||||
if (withName) s.accountName = r.getStringField("account_name");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -58,3 +62,19 @@ std::vector<IClientSysInfo::SysInfoRow> SQLiteDatabase::GetLatestClientSysInfo(c
|
||||
for (; !result.eof(); result.nextRow()) rows.push_back(Row(result));
|
||||
return rows;
|
||||
}
|
||||
|
||||
std::vector<IClientSysInfo::SysInfoRow> SQLiteDatabase::ListClientSysInfo(const SysInfoQuery& q) {
|
||||
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto [_, result] = ExecuteSelect("SELECT c.*, COALESCE(a.name, '') AS account_name" + ClientSysInfoSql::From(q, "'%' || ? || '%'") +
|
||||
ClientSysInfoSql::Order(q) + " LIMIT ? OFFSET ?;", q.accountId, q.accountId, q.search, pattern, pattern, q.limit, q.offset);
|
||||
std::vector<SysInfoRow> rows;
|
||||
for (; !result.eof(); result.nextRow()) rows.push_back(Row(result, true));
|
||||
return rows;
|
||||
}
|
||||
|
||||
uint64_t SQLiteDatabase::CountClientSysInfo(const SysInfoQuery& q) {
|
||||
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto [_, result] = ExecuteSelect("SELECT COUNT(*) AS count" + ClientSysInfoSql::From(q, "'%' || ? || '%'") + ";",
|
||||
q.accountId, q.accountId, q.search, pattern, pattern);
|
||||
return result.eof() ? 0 : static_cast<uint64_t>(result.getInt64Field("count"));
|
||||
}
|
||||
|
||||
@@ -384,6 +384,8 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void RecordClientSysInfo(const SysInfoRow& info) override {}
|
||||
std::vector<SysInfoRow> GetClientSysInfo(uint32_t accountId, uint32_t limit) override { return {}; }
|
||||
std::vector<SysInfoRow> GetLatestClientSysInfo(uint32_t limit) override { return {}; }
|
||||
std::vector<SysInfoRow> ListClientSysInfo(const SysInfoQuery& query) override { return {}; }
|
||||
uint64_t CountClientSysInfo(const SysInfoQuery& query) override { return 0; }
|
||||
void InsertModerationDecision(const std::string& kind, int64_t subjectId, const std::string& subject, bool approved, const std::string& reason, int64_t time) override {}
|
||||
nlohmann::json GetModerationDecisions(const std::string& kind, int64_t subjectId, uint32_t limit) override { return nlohmann::json::array(); }
|
||||
Totp GetTotp(uint32_t accountId) override { return {}; }
|
||||
|
||||
Reference in New Issue
Block a user