diff --git a/web/job/node_traffic_sync_job.go b/web/job/node_traffic_sync_job.go index 63de5018..1cf32323 100644 --- a/web/job/node_traffic_sync_job.go +++ b/web/job/node_traffic_sync_job.go @@ -101,10 +101,6 @@ func (j *NodeTrafficSyncJob) Run() { j.structural.set() } - if !websocket.HasClients() { - return - } - lastOnline, err := j.inboundService.GetClientsLastOnline() if err != nil { logger.Warning("node traffic sync: get last-online failed:", err) @@ -115,6 +111,10 @@ func (j *NodeTrafficSyncJob) Run() { j.inboundService.RefreshOnlineClientsFromMap(lastOnline) + if !websocket.HasClients() { + return + } + online := j.inboundService.GetOnlineClients() if online == nil { online = []string{} diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go index 7a471b4c..583c5995 100644 --- a/web/job/xray_traffic_job.go +++ b/web/job/xray_traffic_job.go @@ -65,18 +65,6 @@ func (j *XrayTrafficJob) Run() { j.xrayService.SetToNeedRestart() } - // If no frontend client is connected, skip all WebSocket broadcasting - // routines — including the active-client DB query and JSON marshaling. - if !websocket.HasClients() { - return - } - - // Online presence + traffic deltas — small payload, always fits in WS. - // Force non-nil slice/map so JSON marshalling produces [] / {} instead of - // `null` when everyone is offline. The frontend's traffic handler treats - // a missing/null onlineClients field as "no update", so without this the - // "everyone went offline" transition was silently dropped — stale online - // users lingered in the list and the online filter kept showing them. lastOnlineMap, err := j.inboundService.GetClientsLastOnline() if err != nil { logger.Warning("get clients last online failed:", err) @@ -84,13 +72,12 @@ func (j *XrayTrafficJob) Run() { if lastOnlineMap == nil { lastOnlineMap = make(map[string]int64) } - - // Determine online clients from lastOnline timestamps with a 5-second - // grace period instead of just the current 5-second traffic poll. This - // prevents idle-but-connected clients from randomly disappearing from - // the UI between polling windows. j.inboundService.RefreshOnlineClientsFromMap(lastOnlineMap) + if !websocket.HasClients() { + return + } + onlineClients := j.inboundService.GetOnlineClients() if onlineClients == nil { onlineClients = []string{} @@ -102,11 +89,6 @@ func (j *XrayTrafficJob) Run() { "lastOnlineMap": lastOnlineMap, }) - // 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 stats, err := j.inboundService.GetAllClientTraffics(); err != nil { logger.Warning("get all client traffics for websocket failed:", err) @@ -122,8 +104,6 @@ func (j *XrayTrafficJob) Run() { websocket.BroadcastClientStats(clientStatsPayload) } - // Outbounds list is small (one row per outbound, no per-client expansion) - // so the full snapshot still fits comfortably in WS. if updatedOutbounds, err := j.outboundService.GetOutboundsTraffic(); err == nil && updatedOutbounds != nil { websocket.BroadcastOutbounds(updatedOutbounds) } else if err != nil {