mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(dashboard): Network connections name the web client's dashboard user
Web clients are one entry per address and signed-in account, with user and account_id; the user is shown without network_ips too, the address then masked as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,9 +112,11 @@ namespace NetworkView {
|
||||
for (const auto& c : server.connections) {
|
||||
const std::string kind = c.peer != TrafficStats::Peer::CLIENTS ? "server" : c.http ? "web" : "game";
|
||||
// One entry per player (their account), else per connection: an address alone is not enough, since the
|
||||
// servers' own links and players on the same machine or network share it
|
||||
// servers' own links and players on the same machine or network share it. Web clients: one per address
|
||||
// and signed-in dashboard account, so people behind one address are listed apart
|
||||
const std::string groupKey = kind == "game" && c.accountId ? "account:" + std::to_string(c.accountId)
|
||||
: kind + "|" + c.address + (c.http ? std::string() : ":" + std::to_string(c.port));
|
||||
: kind + "|" + c.address + (c.http ? std::string() : ":" + std::to_string(c.port))
|
||||
+ (c.http && c.accountId ? "|" + std::to_string(c.accountId) : std::string());
|
||||
auto& group = groups[groupKey];
|
||||
if (group.address.empty()) group.address = c.address;
|
||||
group.seconds = std::max(group.seconds, seconds);
|
||||
@@ -157,7 +159,11 @@ namespace NetworkView {
|
||||
entry["address"] = showAddresses ? group->address : MaskAddress(group->address, salt);
|
||||
entry["kind"] = group->kind;
|
||||
entry["servers"] = group->servers;
|
||||
if (group->accountId) {
|
||||
if (group->accountId && group->kind == "web") {
|
||||
// The dashboard account the requests were signed in as (a session or an API key)
|
||||
entry["account_id"] = group->accountId;
|
||||
entry["user"] = group->account;
|
||||
} else if (group->accountId) {
|
||||
entry["account_id"] = group->accountId;
|
||||
entry["account"] = group->account;
|
||||
if (group->characterId) {
|
||||
|
||||
@@ -26,9 +26,11 @@ namespace NetworkView {
|
||||
nlohmann::json Summary(const TrafficHistory& history, int64_t now, int64_t onlineSeconds, const Labeler& label);
|
||||
|
||||
/**
|
||||
* The remote ends of every server (from each one's last report) grouped by address: game clients by their RakNet
|
||||
* connections, HTTP clients of the dashboard and the UGC server, and the servers' own links. Rates per second.
|
||||
* Without `showAddresses` each address is replaced by a token that stays the same for the same address and `salt`.
|
||||
* The remote ends of every server (from each one's last report): game clients by player (else by RakNet connection),
|
||||
* HTTP clients of the dashboard and the UGC server by address and signed-in account (`user`, the dashboard account
|
||||
* the requests were signed in as), and the servers' own links. Rates per second. Account and user names are always
|
||||
* included; without `showAddresses` each address is replaced by a token that stays the same for the same address
|
||||
* and `salt`, and ports are left out.
|
||||
*/
|
||||
nlohmann::json Connections(const TrafficHistory& history, int64_t now, int64_t onlineSeconds, bool showAddresses, uint64_t salt, const Labeler& label);
|
||||
|
||||
|
||||
@@ -150,3 +150,36 @@ TEST(NetworkViewTest, ServerLinksOnThePlayersAddressStayApart) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkViewTest, WebClientsBySignedInAccountWithTheirUserName) {
|
||||
// Two staff members behind one address and a request that wasn't signed in, on the dashboard
|
||||
TrafficHistory history;
|
||||
Report dashboard;
|
||||
dashboard.seconds = { Second{ .time = NOW - 1 } };
|
||||
dashboard.connections = {
|
||||
Connection{ .address = "198.51.100.7", .peer = Peer::CLIENTS, .http = true, .packetsIn = 4, .packetsOut = 4, .bytesIn = 400, .bytesOut = 40000, .accountId = 7, .account = "alice" },
|
||||
Connection{ .address = "198.51.100.7", .peer = Peer::CLIENTS, .http = true, .packetsIn = 2, .packetsOut = 2, .bytesIn = 200, .bytesOut = 20000, .accountId = 9, .account = "bob" },
|
||||
Connection{ .address = "198.51.100.7", .peer = Peer::CLIENTS, .http = true, .packetsIn = 1, .packetsOut = 1, .bytesIn = 100, .bytesOut = 100 } };
|
||||
dashboard.hasConnections = true;
|
||||
history.Ingest(static_cast<uint16_t>(ServiceType::DASHBOARD), 0, 0, dashboard, NOW);
|
||||
|
||||
const auto shown = NetworkView::Connections(history, NOW, 20, true, 5, Label);
|
||||
ASSERT_EQ(shown["peers"].size(), 3u);
|
||||
std::vector<std::string> users;
|
||||
for (const auto& p : shown["peers"]) {
|
||||
EXPECT_EQ(p["kind"], "web");
|
||||
EXPECT_EQ(p["address"], "198.51.100.7");
|
||||
EXPECT_FALSE(p.contains("account")); // a web client's name is its dashboard user, not a player
|
||||
users.push_back(p.value("user", std::string("-")));
|
||||
}
|
||||
EXPECT_EQ(users, (std::vector<std::string>{ "alice", "bob", "-" })); // busiest first
|
||||
EXPECT_EQ(shown["peers"][0]["account_id"], 7);
|
||||
|
||||
// Without network_ips: still the user names, and the address as its token (the same for all three)
|
||||
const auto hidden = NetworkView::Connections(history, NOW, 20, false, 5, Label);
|
||||
EXPECT_EQ(hidden.dump().find("198.51.100.7"), std::string::npos);
|
||||
ASSERT_EQ(hidden["peers"].size(), 3u);
|
||||
EXPECT_EQ(hidden["peers"][0]["user"], "alice");
|
||||
EXPECT_EQ(hidden["peers"][1]["user"], "bob");
|
||||
for (const auto& p : hidden["peers"]) EXPECT_EQ(p["address"], NetworkView::MaskAddress("198.51.100.7", 5));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user