Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlolka1333 <xtrafcyz@gmail.com>2026-05-05 18:27:49 +0300
committerGitHub <noreply@github.com>2026-05-05 18:27:49 +0300
commit8177f6dc667f2edca66073e433baf5cff36cda41 (patch)
tree7fddbec4848c3c0ab0fc39e3dc45703c368e9340 /web/websocket/notifier.go
parent77d94b25d054bd6cf7ace029571db9c58ae87fa9 (diff)
ws/inbounds: realtime fixes + perf for 10k+ client inbounds (#4123)HEADmain
* ws/inbounds: realtime fixes + perf for 10k+ client inbounds - hub: dedup, throttle, panic-restart, deadlock fix, race tests - client: backoff cap + slow-retry instead of giving up - broadcast: delta-only payload, count-based invalidate fallback - filter: fix empty online list (Inbound has no .id, use dbInbound.toInbound) - perf: O(N²)→O(N) traffic merge, bulk delete, /setEnable endpoint - traffic: monotonic all_time + UI clamp + propagate in delta handler - session: persist on update/logout (fixes logout-after-password-change) - ui: protocol tags flex, traffic bar normalize * Remove hub_test.go file * fix: ws hub, inbound service, and frontend correctness - propagate DelInbound error on disable path in SetInboundEnable - skip empty emails in updateClientTraffics to avoid constraint violations - use consistent IN ? clause, drop redundant ErrRecordNotFound guards - Hub.Unregister: direct removeClient fallback when channel is full - applyClientStatsDelta: O(1) email lookup via per-inbound Map cache - WS payload size check: Blob.size instead of .length for real byte count * fix: chunk large IN ? queries and fix IPv6 same-origin check * fix: chunk large IN ? queries and fix IPv6 same-origin check * fix: unify clientStats cache, throttle clarity, hub constants * fix(ui): align traffic/expiry cell columns across all rows * style(ui): redesign outbounds table for visual consistency * style(ui): redesign routing table for visual consistency * fix: * fix: * fix: * fix: * fix: * fix: font * refactor: simplify outbound tone functions for consistency and maintainability --------- Co-authored-by: lolka1333 <test123@gmail.com>
Diffstat (limited to 'web/websocket/notifier.go')
-rw-r--r--web/websocket/notifier.go80
1 files changed, 41 insertions, 39 deletions
diff --git a/web/websocket/notifier.go b/web/websocket/notifier.go
index 2db78578..8115ae0f 100644
--- a/web/websocket/notifier.go
+++ b/web/websocket/notifier.go
@@ -6,7 +6,7 @@ import (
"github.com/mhsanaei/3x-ui/v2/web/global"
)
-// GetHub returns the global WebSocket hub instance
+// GetHub returns the global WebSocket hub instance.
func GetHub() *Hub {
webServer := global.GetWebServer()
if webServer == nil {
@@ -24,80 +24,82 @@ func GetHub() *Hub {
return wsHub
}
-// HasClients returns true if there are any WebSocket clients connected.
+// HasClients returns true if any WebSocket client is connected.
// Use this to skip expensive work (DB queries, serialization) when no browser is open.
func HasClients() bool {
hub := GetHub()
- if hub == nil {
- return false
- }
- return hub.GetClientCount() > 0
+ return hub != nil && hub.GetClientCount() > 0
}
-// BroadcastStatus broadcasts server status update to all connected clients
+// BroadcastStatus broadcasts server status update to all connected clients.
func BroadcastStatus(status any) {
- hub := GetHub()
- if hub != nil {
+ if hub := GetHub(); hub != nil {
hub.Broadcast(MessageTypeStatus, status)
}
}
-// BroadcastTraffic broadcasts traffic statistics update to all connected clients
+// BroadcastTraffic broadcasts traffic statistics update to all connected clients.
func BroadcastTraffic(traffic any) {
- hub := GetHub()
- if hub != nil {
+ if hub := GetHub(); hub != nil {
hub.Broadcast(MessageTypeTraffic, traffic)
}
}
-// BroadcastInbounds broadcasts inbounds list update to all connected clients
+// BroadcastClientStats broadcasts absolute per-client traffic counters for the
+// clients that had activity in the latest collection window. Use this instead
+// of re-broadcasting the full inbound list — it scales to 10k+ clients because
+// the payload only includes active rows (typically a fraction of total).
+func BroadcastClientStats(stats any) {
+ if hub := GetHub(); hub != nil {
+ hub.Broadcast(MessageTypeClientStats, stats)
+ }
+}
+
+// BroadcastInbounds broadcasts inbounds list update to all connected clients.
func BroadcastInbounds(inbounds any) {
- hub := GetHub()
- if hub != nil {
+ if hub := GetHub(); hub != nil {
hub.Broadcast(MessageTypeInbounds, inbounds)
}
}
-// BroadcastOutbounds broadcasts outbounds list update to all connected clients
+// BroadcastOutbounds broadcasts outbounds list update to all connected clients.
func BroadcastOutbounds(outbounds any) {
- hub := GetHub()
- if hub != nil {
+ if hub := GetHub(); hub != nil {
hub.Broadcast(MessageTypeOutbounds, outbounds)
}
}
-// BroadcastNotification broadcasts a system notification to all connected clients
+// BroadcastNotification broadcasts a system notification to all connected clients.
func BroadcastNotification(title, message, level string) {
hub := GetHub()
- if hub != nil {
- notification := map[string]string{
- "title": title,
- "message": message,
- "level": level, // info, warning, error, success
- }
- hub.Broadcast(MessageTypeNotification, notification)
+ if hub == nil {
+ return
}
+ hub.Broadcast(MessageTypeNotification, map[string]string{
+ "title": title,
+ "message": message,
+ "level": level,
+ })
}
-// BroadcastXrayState broadcasts Xray state change to all connected clients
+// BroadcastXrayState broadcasts Xray state change to all connected clients.
func BroadcastXrayState(state string, errorMsg string) {
hub := GetHub()
- if hub != nil {
- stateUpdate := map[string]string{
- "state": state,
- "errorMsg": errorMsg,
- }
- hub.Broadcast(MessageTypeXrayState, stateUpdate)
+ if hub == nil {
+ return
}
+ hub.Broadcast(MessageTypeXrayState, map[string]string{
+ "state": state,
+ "errorMsg": errorMsg,
+ })
}
-// BroadcastInvalidate sends a lightweight invalidate signal for the given data type,
-// telling connected frontends to re-fetch data via REST API.
-// Use this instead of BroadcastInbounds/BroadcastOutbounds when you know the payload
-// will be too large, to avoid wasting resources on serialization.
+// BroadcastInvalidate sends a lightweight signal telling clients to re-fetch
+// the named data type via REST. Use this when the caller already knows the
+// payload is too large to push directly (e.g., 10k+ clients) to skip the
+// JSON-marshal cost on the hot path.
func BroadcastInvalidate(dataType MessageType) {
- hub := GetHub()
- if hub != nil {
+ if hub := GetHub(); hub != nil {
hub.broadcastInvalidate(dataType)
}
}