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:
MHSanaei
2026-06-11 19:42:03 +02:00
parent 58905d81a4
commit 7bcc5830c6
33 changed files with 790 additions and 285 deletions
+4 -4
View File
@@ -64,7 +64,7 @@ export default function IndexPage() {
const [messageApi, messageContextHolder] = message.useMessage();
useEffect(() => { setMessageInstance(messageApi); }, [messageApi]);
const [ipLimitEnable, setIpLimitEnable] = useState(false);
const [accessLogEnable, setAccessLogEnable] = useState(false);
const [panelUpdateInfo, setPanelUpdateInfo] = useState<PanelUpdateInfo>({
currentVersion: '',
latestVersion: '',
@@ -87,8 +87,8 @@ export default function IndexPage() {
const [loadingTip, setLoadingTip] = useState(t('loading'));
useEffect(() => {
HttpUtil.post<{ ipLimitEnable?: boolean }>('/panel/api/setting/defaultSettings').then((msg) => {
if (msg?.success && msg.obj) setIpLimitEnable(!!msg.obj.ipLimitEnable);
HttpUtil.post<{ accessLogEnable?: boolean }>('/panel/api/setting/defaultSettings').then((msg) => {
if (msg?.success && msg.obj) setAccessLogEnable(!!msg.obj.accessLogEnable);
});
HttpUtil.get<PanelUpdateInfo>('/panel/api/server/getPanelUpdateInfo').then((msg) => {
if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj);
@@ -186,7 +186,7 @@ export default function IndexPage() {
<XrayStatusCard
status={status}
isMobile={isMobile}
ipLimitEnable={ipLimitEnable}
accessLogEnable={accessLogEnable}
onStopXray={stopXray}
onRestartXray={restartXray}
onOpenXrayLogs={() => setXrayLogsOpen(true)}
+5 -3
View File
@@ -14,7 +14,7 @@ import './XrayStatusCard.css';
interface XrayStatusCardProps {
status: Status;
isMobile: boolean;
ipLimitEnable: boolean;
accessLogEnable: boolean;
onStopXray: () => void;
onRestartXray: () => void;
onOpenLogs: () => void;
@@ -31,7 +31,7 @@ const XRAY_STATE_KEYS: Record<string, string> = {
export default function XrayStatusCard({
status,
isMobile,
ipLimitEnable,
accessLogEnable,
onStopXray,
onRestartXray,
onOpenLogs,
@@ -86,7 +86,9 @@ export default function XrayStatusCard({
);
const actions = [
...(ipLimitEnable
// the xray log viewer reads the access log file, so the button only makes
// sense when one is configured (unlike IP limit, which no longer needs it)
...(accessLogEnable
? [
<Space className="action" key="xraylogs" onClick={onOpenXrayLogs}>
<BarsOutlined />