mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-11 17:01:38 +07:00
fix(inbounds): refresh client rows live over websocket
Two bugs combined to leave per-client traffic / remained / all-time columns stuck at stale numbers while only the inbound-level row and the online badge refreshed: 1. Backend (xray + node sync traffic jobs) only included the per-client array in the client_stats broadcast when activeEmails / touched was non-empty. Cycles with no client deltas — or any node sync that failed to fetch a snapshot — shipped only the inbound summary, so the frontend had nothing to merge for clients. Replaced both code paths with a single GetAllClientTraffics() snapshot per cycle; the broadcast now always carries the full client list. 2. Frontend mutated dbInbound.clientStats[i] in place. DBInbound is a plain class instance (not wrapped in reactive()), so Vue could not see the field-level changes and ClientRowTable's statsMap computed stayed cached forever. Added a statsVersion tick bumped on every merge and read inside statsMap so the computed re-evaluates and the template pulls fresh up/down/allTime/expiryTime each push. Removed the now-dead emailSet helper from node_traffic_sync_job and the activeEmails filter from xray_traffic_job.
This commit is contained in:
@@ -95,18 +95,16 @@ func (j *XrayTrafficJob) Run() {
|
||||
"lastOnlineMap": lastOnlineMap,
|
||||
})
|
||||
|
||||
// Compact delta payload: per-client absolute counters for clients active
|
||||
// this cycle, plus inbound-level absolute totals. Frontend applies both
|
||||
// in-place — typical payload ~10–50KB even for 10k+ client deployments.
|
||||
// Replaces the old full-inbound-list broadcast that hit WS size limits
|
||||
// (5–10MB) and forced the frontend into a REST refetch.
|
||||
// Full snapshot every cycle: absolute per-client counters and inbound
|
||||
// totals. Frontend overwrites both in place. The previous delta path
|
||||
// (activeEmails -> GetActiveClientTraffics) silently omitted the
|
||||
// clients array whenever nobody moved bytes in the cycle, leaving the
|
||||
// client rows in the UI stuck at stale traffic/remained/all-time.
|
||||
clientStatsPayload := map[string]any{}
|
||||
if activeEmails := activeEmails(clientTraffics); len(activeEmails) > 0 {
|
||||
if stats, err := j.inboundService.GetActiveClientTraffics(activeEmails); err != nil {
|
||||
logger.Warning("get active client traffics for websocket failed:", err)
|
||||
} else if len(stats) > 0 {
|
||||
clientStatsPayload["clients"] = stats
|
||||
}
|
||||
if stats, err := j.inboundService.GetAllClientTraffics(); err != nil {
|
||||
logger.Warning("get all client traffics for websocket failed:", err)
|
||||
} else if len(stats) > 0 {
|
||||
clientStatsPayload["clients"] = stats
|
||||
}
|
||||
if inboundSummary, err := j.inboundService.GetInboundsTrafficSummary(); err != nil {
|
||||
logger.Warning("get inbounds traffic summary for websocket failed:", err)
|
||||
@@ -126,26 +124,6 @@ func (j *XrayTrafficJob) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// activeEmails returns the set of client emails that had non-zero traffic in
|
||||
// the current collection window. Idle clients are skipped — no need to push
|
||||
// their (unchanged) counters to the frontend.
|
||||
func activeEmails(clientTraffics []*xray.ClientTraffic) []string {
|
||||
if len(clientTraffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
emails := make([]string, 0, len(clientTraffics))
|
||||
for _, ct := range clientTraffics {
|
||||
if ct == nil || ct.Email == "" {
|
||||
continue
|
||||
}
|
||||
if ct.Up == 0 && ct.Down == 0 {
|
||||
continue
|
||||
}
|
||||
emails = append(emails, ct.Email)
|
||||
}
|
||||
return emails
|
||||
}
|
||||
|
||||
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
|
||||
informURL, err := j.settingService.GetExternalTrafficInformURI()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user