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:
Aaron Kimbrell
2026-09-30 08:06:02 -05:00
parent bd13297947
commit d94d84b5cb
16 changed files with 363 additions and 40 deletions

View 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;
}
}

View File

@@ -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__