mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-09 06:06:08 +00:00
feat(node-sync): push global client usage to nodes for display and local enforcement
A client attached to several panels has one aggregated row on each master, but a node only ever saw its local share: the node UI under-reported usage, and the node kept serving a client whose cross-panel total had already exceeded its quota — the master's disable push doesn't kill established connections unless the node restarts xray itself. Masters now push their aggregated per-client counters to each node from NodeTrafficSyncJob (throttled, scoped to the clients that node hosts). The node stores them in the new client_global_traffics side table keyed by (masterGuid, email), overwritten on every push so a master-side reset propagates, and: - overlays max(local, pushed) onto UI read paths (slim inbound list, inbound detail, clients list, WS stats, per-email lookups). The full /panel/api/inbounds/list stays un-overlaid on purpose: it doubles as the traffic snapshot masters poll, and overlaying it would corrupt every master's delta accounting; - trips disableInvalidClients when any master's pushed total exceeds the client's quota, so the existing RestartXrayOnClientDisable flow disconnects the client locally; - clears the side rows on traffic reset, auto-renew, and client delete, keeping a renewed quota window clean. Supersedes #5204, which folded pushed globals into client_traffics and compensated with read-back baselines — that double-counted first-sight emails and could not work with several masters sharing one node.
This commit is contained in:
@@ -2709,6 +2709,59 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/api/inbounds/pushClientTraffics": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Inbounds"
|
||||
],
|
||||
"summary": "Receive a master panel's aggregated per-client usage, keyed by the master's GUID. Stored in a side table used only for the UI display overlay and local quota enforcement — never folded into the local counters that masters poll, so delta accounting stays intact. Called panel-to-panel by the node traffic sync job.",
|
||||
"operationId": "post_panel_api_inbounds_pushClientTraffics",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
},
|
||||
"example": {
|
||||
"masterGuid": "9f6c2d-…",
|
||||
"traffics": [
|
||||
{
|
||||
"email": "alice",
|
||||
"up": 1048576,
|
||||
"down": 2097152
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"obj": {}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/api/inbounds/{id}/fallbacks": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
@@ -205,6 +205,17 @@ export const sections: readonly Section[] = [
|
||||
{ name: 'data', in: 'body (form)', type: 'string', desc: 'JSON-encoded inbound payload.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/panel/api/inbounds/pushClientTraffics',
|
||||
summary: 'Receive a master panel\'s aggregated per-client usage, keyed by the master\'s GUID. Stored in a side table used only for the UI display overlay and local quota enforcement — never folded into the local counters that masters poll, so delta accounting stays intact. Called panel-to-panel by the node traffic sync job.',
|
||||
params: [
|
||||
{ name: 'masterGuid', in: 'body (json)', type: 'string', desc: 'Stable GUID of the pushing master panel.' },
|
||||
{ name: 'traffics', in: 'body (json)', type: 'object[]', desc: 'Client traffic rows; only email/up/down are read.' },
|
||||
],
|
||||
body: '{\n "masterGuid": "9f6c2d-…",\n "traffics": [\n { "email": "alice", "up": 1048576, "down": 2097152 }\n ]\n}',
|
||||
response: '{\n "success": true\n}',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/panel/api/inbounds/:id/fallbacks',
|
||||
|
||||
Reference in New Issue
Block a user