fix(online): scope online status per node instead of a global union

The inbounds page and Nodes page checked each client's email against a
single deduped union of every node's online clients, so a client connected
to one node showed as online on every inbound across every node. The local
online set was also derived from the email-keyed client_traffics.last_online
column, which remote-node syncs bump too, leaking remote-only clients onto
local inbounds.

Track online clients per node: the local panel's own xray clients under key
0 (derived from live traffic-poll deltas via RefreshLocalOnline, kept in
memory and independent of the shared last_online column) and each remote
node under its id. Add GetOnlineClientsByNode plus a /clients/onlinesByNode
endpoint and onlineByNode WS field; node.go and the inbounds rollup now scope
online by node. The flat GetOnlineClients union is kept for client-centric and
total-count views (Clients page, dashboard, telegram).

Closes #4809
This commit is contained in:
MHSanaei
2026-06-02 18:33:21 +02:00
parent 6f6c7fc17a
commit 3af2da0142
12 changed files with 334 additions and 31 deletions
+43 -1
View File
@@ -3617,7 +3617,7 @@
"tags": [
"Clients"
],
"summary": "List the emails of currently connected clients (last seen within the heartbeat window).",
"summary": "List the emails of currently connected clients (last seen within the heartbeat window), deduped across every node.",
"operationId": "post_panel_api_clients_onlines",
"responses": {
"200": {
@@ -3649,6 +3649,48 @@
}
}
},
"/panel/api/clients/onlinesByNode": {
"post": {
"tags": [
"Clients"
],
"summary": "Online client emails grouped by the node that reported them. The local panel uses key \"0\"; each remote node uses its node id. Lets the inbounds page show online status per node instead of merging every node together.",
"operationId": "post_panel_api_clients_onlinesByNode",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"obj": {}
}
},
"example": {
"success": true,
"obj": {
"0": [
"user1"
],
"3": [
"user1",
"user2"
]
}
}
}
}
}
}
}
},
"/panel/api/clients/lastOnline": {
"post": {
"tags": [