diff options
| author | lolka1333 <xtrafcyz@gmail.com> | 2026-05-05 18:27:49 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-05 18:27:49 +0300 |
| commit | 8177f6dc667f2edca66073e433baf5cff36cda41 (patch) | |
| tree | 7fddbec4848c3c0ab0fc39e3dc45703c368e9340 /web/websocket/notifier.go | |
| parent | 77d94b25d054bd6cf7ace029571db9c58ae87fa9 (diff) | |
* 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.go | 80 |
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) } } |
