mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-02 00:17:13 +00:00
feat(online): use xray online-stats API for onlines and access-log-free IP limit
Adopt xray-core's statsUserOnline policy and GetUsersStats RPC so online detection is connection-based and IP limiting no longer requires an access log. Falls back to the legacy traffic-delta onlines and access-log parsing when the running core lacks the RPCs (Unimplemented), probed lazily per process so a panel-driven version switch re-evaluates automatically. Backend: - xray/api.go: GetOnlineUsers (one GetUsersStats call returns all online users and their source IPs) and IsUnimplementedErr. - xray/process.go: per-process OnlineAPISupport tri-state capability cache. - service/xray.go: ensureStatsPolicy injects statsUserOnline into every policy level of the generated config; XrayService.GetOnlineUsers probes and falls back. - job/xray_traffic_job.go: union API onlines into the delta-derived active set; bump last_online for idle-but-connected clients. - job/check_client_ip_job.go: API-first IP source with shared enforcement; live observations bypass the 30-min stale cutoff; access-log path unchanged for older cores. - service/setting.go: GetIpLimitEnable always true; new accessLogEnable default for features that genuinely read the access log. Frontend: - Client form split into Basic and Config tabs; IP Limit and IP Log no longer gated on access log; compact Auto Renew next to Start After First Use; tabBasic/tabConfig added to all 13 locales. - Xray logs button on the dashboard now gated on accessLogEnable.
This commit is contained in:
@@ -116,6 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
xrayConfig.LogConfig = resolveXrayLogPaths(xrayConfig.LogConfig)
|
||||
xrayConfig.API = ensureAPIServices(xrayConfig.API)
|
||||
xrayConfig.Policy = ensureStatsPolicy(xrayConfig.Policy)
|
||||
|
||||
_, _, _ = s.inboundService.AddTraffic(nil, nil)
|
||||
|
||||
@@ -421,6 +422,51 @@ func ensureAPIServices(api json_util.RawMessage) json_util.RawMessage {
|
||||
return out
|
||||
}
|
||||
|
||||
// ensureStatsPolicy guarantees every policy level in the generated config has
|
||||
// statsUserOnline enabled, so the core tracks per-email online IPs for the
|
||||
// panel's online view and access-log-free IP limiting. Generated clients carry
|
||||
// no explicit level, so level "0" is created when absent. The flag is panel
|
||||
// infrastructure and is forced on even over an explicit false in the template,
|
||||
// same as the api services above. An entirely missing or unparsable policy
|
||||
// block is left alone; the stored template itself is never modified — only the
|
||||
// generated runtime config.
|
||||
func ensureStatsPolicy(policy json_util.RawMessage) json_util.RawMessage {
|
||||
if len(policy) == 0 {
|
||||
return policy
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal(policy, &parsed); err != nil {
|
||||
return policy
|
||||
}
|
||||
levels, _ := parsed["levels"].(map[string]any)
|
||||
if levels == nil {
|
||||
levels = make(map[string]any)
|
||||
}
|
||||
if _, ok := levels["0"]; !ok {
|
||||
levels["0"] = map[string]any{}
|
||||
}
|
||||
changed := false
|
||||
for _, raw := range levels {
|
||||
level, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if enabled, ok := level["statsUserOnline"].(bool); !ok || !enabled {
|
||||
level["statsUserOnline"] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return policy
|
||||
}
|
||||
parsed["levels"] = levels
|
||||
out, err := json.Marshal(parsed)
|
||||
if err != nil {
|
||||
return policy
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// resolveXrayLogPaths rewrites relative `log.access` / `log.error` values to
|
||||
// absolute paths under config.GetLogFolder(), so Xray writes those files
|
||||
// alongside the panel's other logs regardless of the working directory the
|
||||
@@ -493,6 +539,43 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
|
||||
return traffic, clientTraffic, nil
|
||||
}
|
||||
|
||||
// GetOnlineUsers returns connection-based online users (email + source IPs)
|
||||
// from the running core's online-stats API. ok=false means the API is not
|
||||
// available — xray isn't running or the core predates the online-stats RPCs —
|
||||
// and callers must use the legacy traffic-delta / access-log paths. The
|
||||
// capability is probed lazily per process: an Unimplemented answer pins this
|
||||
// core as unsupported until the next restart, while transient errors leave the
|
||||
// capability undecided so a flaky poll can't lock in legacy mode.
|
||||
func (s *XrayService) GetOnlineUsers() ([]xray.OnlineUser, bool, error) {
|
||||
if !s.IsXrayRunning() {
|
||||
return nil, false, nil
|
||||
}
|
||||
if p.OnlineAPISupport() == xray.OnlineAPIUnsupported {
|
||||
return nil, false, nil
|
||||
}
|
||||
if err := s.xrayAPI.Init(p.GetAPIPort()); err != nil {
|
||||
logger.Debug("Failed to initialize Xray API:", err)
|
||||
return nil, false, err
|
||||
}
|
||||
defer s.xrayAPI.Close()
|
||||
|
||||
users, err := s.xrayAPI.GetOnlineUsers()
|
||||
if err != nil {
|
||||
if xray.IsUnimplementedErr(err) {
|
||||
p.SetOnlineAPISupport(xray.OnlineAPIUnsupported)
|
||||
logger.Info("xray core does not support the online-stats API; falling back to traffic-delta onlines and access-log IP limit")
|
||||
return nil, false, nil
|
||||
}
|
||||
logger.Debug("Failed to fetch Xray online users:", err)
|
||||
return nil, false, err
|
||||
}
|
||||
if p.OnlineAPISupport() == xray.OnlineAPIUnknown {
|
||||
p.SetOnlineAPISupport(xray.OnlineAPISupported)
|
||||
logger.Info("xray core supports the online-stats API; using connection-based onlines and access-log-free IP limit")
|
||||
}
|
||||
return users, true, nil
|
||||
}
|
||||
|
||||
// BalancerStatus is the live view of one balancer for the panel UI. Running
|
||||
// is false when the balancer isn't present in the running core (e.g. xray is
|
||||
// stopped or the balancer hasn't been saved/applied yet).
|
||||
|
||||
Reference in New Issue
Block a user