mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-12 06:10:58 +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:
@@ -798,7 +798,18 @@ func (s *SettingService) SetRestartXrayOnClientDisable(value bool) error {
|
||||
return s.setBool("restartXrayOnClientDisable", value)
|
||||
}
|
||||
|
||||
// GetIpLimitEnable reports whether the IP-limit feature is available. Always
|
||||
// true since the panel enforces limits via the core's online-stats API; on an
|
||||
// older core the job falls back to access-log parsing and warns there when the
|
||||
// log is missing, so the UI no longer hides the field behind that condition.
|
||||
func (s *SettingService) GetIpLimitEnable() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetAccessLogEnable reports whether an Xray access log is configured. Used by
|
||||
// the UI for features that genuinely read the log file (the xray log viewer) —
|
||||
// distinct from IP limiting, which works without it.
|
||||
func (s *SettingService) GetAccessLogEnable() (bool, error) {
|
||||
accessLogPath, err := xray.GetAccessLogPath()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -1022,25 +1033,26 @@ func (s *SettingService) BuildSubURIBase(host string) string {
|
||||
func (s *SettingService) GetDefaultSettings(host string) (any, error) {
|
||||
type settingFunc func() (any, error)
|
||||
settings := map[string]settingFunc{
|
||||
"expireDiff": func() (any, error) { return s.GetExpireDiff() },
|
||||
"trafficDiff": func() (any, error) { return s.GetTrafficDiff() },
|
||||
"pageSize": func() (any, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (any, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (any, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (any, error) { return s.GetTgbotEnabled() },
|
||||
"subThemeDir": func() (any, error) { return s.GetSubThemeDir() },
|
||||
"subEnable": func() (any, error) { return s.GetSubEnable() },
|
||||
"subJsonEnable": func() (any, error) { return s.GetSubJsonEnable() },
|
||||
"subClashEnable": func() (any, error) { return s.GetSubClashEnable() },
|
||||
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
||||
"subURI": func() (any, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
||||
"subClashURI": func() (any, error) { return s.GetSubClashURI() },
|
||||
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
||||
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
||||
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
||||
"webDomain": func() (any, error) { return s.GetWebDomain() },
|
||||
"subDomain": func() (any, error) { return s.GetSubDomain() },
|
||||
"expireDiff": func() (any, error) { return s.GetExpireDiff() },
|
||||
"trafficDiff": func() (any, error) { return s.GetTrafficDiff() },
|
||||
"pageSize": func() (any, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (any, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (any, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (any, error) { return s.GetTgbotEnabled() },
|
||||
"subThemeDir": func() (any, error) { return s.GetSubThemeDir() },
|
||||
"subEnable": func() (any, error) { return s.GetSubEnable() },
|
||||
"subJsonEnable": func() (any, error) { return s.GetSubJsonEnable() },
|
||||
"subClashEnable": func() (any, error) { return s.GetSubClashEnable() },
|
||||
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
||||
"subURI": func() (any, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
||||
"subClashURI": func() (any, error) { return s.GetSubClashURI() },
|
||||
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
||||
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
||||
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
||||
"accessLogEnable": func() (any, error) { return s.GetAccessLogEnable() },
|
||||
"webDomain": func() (any, error) { return s.GetWebDomain() },
|
||||
"subDomain": func() (any, error) { return s.GetSubDomain() },
|
||||
}
|
||||
|
||||
result := make(map[string]any)
|
||||
|
||||
Reference in New Issue
Block a user