精简版P9(4b): 删除本地核心整条路径(进程管理/buildXrayConfig本地分支/local-only注入/本地核心接口与任务)
- service/xray.go 1741->573行: 只剩 agent 配置构建(GetXrayConfigForAgent/buildAgentXrayConfig=模板+节点入站+egress+转发编译); 删进程生命周期/状态机/RestartXray/StopXray/SetToNeedRestart/GetXrayTraffic/GetOnlineUsers/balancer/TestRoute 与 injectPanelEgress/injectNodeEgresses/injectMtproto/injectAmneziawg/mergeSubscriptionOutbounds
- controller: 43处 SetToNeedRestart 与各 RestartXray 调用清除; 删 balancerStatus/balancerOverride/routeTest/getXrayResult 路由与handler(纯本地核心操作); 前端删 RouteTester 页签与组件
- server service: GetConfigJson 改模板直读; 状态Xray块恒 stopped/空版本; Stop/RestartXrayService 改 no-op(DB导入/恢复流程保留形状)
- setting service: PanelEgressProxyURL/NodeEgressProxyURL 恒直连(桥在本地核心内,已不存在); NodeService.withOutboundBridge 恒直连回退
- runtime: Local 改为全 no-op 桩(DB即真源); NewManager() 去 LocalDeps(APIPort/SetNeedRestart)
- job: 删 XrayTrafficJob/CheckXrayRunningJob/CheckClientIpJob 三文件(共享声明 EventBus/IPWithTimestamp/clientStatsSnapshotMaxClients 迁 shared.go); web.go 不再调度 MtprotoJob/AmneziaWGJob, 删 OnCrash 接线与 stop() 的 sidecar StopAll
- MTProto/AmneziaWG 下线: AddInbound/UpdateInbound 对两协议直接拒绝(存量行保留可删); TG bot 的 restart 命令与在线查询按无本地核心收口
- 测试: GetXrayConfig 系列移植到 agent 构建(finalmask/clients/wireguard/scale); 删退役行为测试(lifecycle/restart/mtproto本地应用/client-ip job/inject系列); 共享测试helper迁 mtproto_test_helpers_test.go 与 job/integration_helpers_test.go
- 既有红(非本轮引入,已在 f6d6520 复现): TestGetXrayConfig_EnabledClientsStillEmitted、TestRouteRegistryContract(P6删 endpoints.ts 遗留)
This commit is contained in:
@@ -1,165 +0,0 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Button, Col, Input, InputNumber, Row, Select, Space, Tag } from 'antd';
|
||||
import { AimOutlined } from '@ant-design/icons';
|
||||
|
||||
import { HttpUtil } from '@/utils';
|
||||
import { useInboundOptions } from '@/api/queries/useInboundOptions';
|
||||
import { buildRemarkByTag, formatInboundTag } from './helpers';
|
||||
|
||||
interface RouteTesterProps {
|
||||
inboundTags: string[];
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
// Mirror of the /xray/routeTest response (RoutingService.TestRoute).
|
||||
interface RouteTestResult {
|
||||
matched: boolean;
|
||||
outboundTag: string;
|
||||
groupTags?: string[];
|
||||
}
|
||||
|
||||
const PROTOCOL_OPTIONS = ['http', 'tls', 'quic', 'bittorrent'].map((p) => ({ label: p, value: p }));
|
||||
|
||||
export default function RouteTester({ inboundTags, isMobile }: RouteTesterProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: inboundOptions } = useInboundOptions();
|
||||
const remarkByTag = useMemo(() => buildRemarkByTag(inboundOptions || []), [inboundOptions]);
|
||||
const [dest, setDest] = useState('');
|
||||
const [port, setPort] = useState<number | null>(443);
|
||||
const [network, setNetwork] = useState('tcp');
|
||||
const [inboundTag, setInboundTag] = useState<string | undefined>(undefined);
|
||||
const [protocol, setProtocol] = useState<string | undefined>(undefined);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [result, setResult] = useState<RouteTestResult | null>(null);
|
||||
|
||||
async function run() {
|
||||
const value = dest.trim();
|
||||
if (!value) return;
|
||||
// Domains never contain ':' and a pure dotted-quad is an IPv4 address;
|
||||
// everything else is treated as a domain.
|
||||
const isIp = /^(\d{1,3}\.){3}\d{1,3}$/.test(value) || value.includes(':');
|
||||
setTesting(true);
|
||||
setResult(null);
|
||||
try {
|
||||
const msg = await HttpUtil.post('/panel/api/xray/routeTest', {
|
||||
domain: isIp ? '' : value,
|
||||
ip: isIp ? value : '',
|
||||
port: port ?? 0,
|
||||
network,
|
||||
inboundTag: inboundTag || '',
|
||||
protocol: protocol || '',
|
||||
});
|
||||
if (msg?.success && msg.obj && typeof msg.obj === 'object') {
|
||||
setResult(msg.obj as RouteTestResult);
|
||||
}
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const fieldSpan = isMobile ? 24 : undefined;
|
||||
|
||||
return (
|
||||
<Space orientation="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<Alert type="info" showIcon title={t('pages.xray.routeTesterDesc')} />
|
||||
<Row gutter={[8, 8]} align="bottom">
|
||||
<Col xs={fieldSpan} sm={7}>
|
||||
<Input
|
||||
aria-label={t('pages.xray.routeTesterDest')}
|
||||
placeholder={t('pages.xray.routeTesterDest')}
|
||||
value={dest}
|
||||
onChange={(e) => setDest(e.target.value)}
|
||||
onPressEnter={run}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={3}>
|
||||
<InputNumber
|
||||
aria-label={t('pages.xray.routeTesterPort')}
|
||||
style={{ width: '100%' }}
|
||||
min={0}
|
||||
max={65535}
|
||||
placeholder={t('pages.xray.routeTesterPort')}
|
||||
value={port}
|
||||
onChange={(v) => setPort(v)}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={3}>
|
||||
<Select
|
||||
aria-label={t('pages.inbounds.network')}
|
||||
style={{ width: '100%' }}
|
||||
value={network}
|
||||
onChange={setNetwork}
|
||||
options={[
|
||||
{ label: 'TCP', value: 'tcp' },
|
||||
{ label: 'UDP', value: 'udp' },
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={4}>
|
||||
<Select
|
||||
aria-label={t('pages.xray.routeTesterInbound')}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('pages.xray.routeTesterInbound')}
|
||||
allowClear
|
||||
value={inboundTag}
|
||||
onChange={setInboundTag}
|
||||
options={inboundTags
|
||||
.filter(Boolean)
|
||||
.map((tag) => ({ label: formatInboundTag(tag, remarkByTag), value: tag }))}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={4}>
|
||||
<Select
|
||||
aria-label={t('pages.xray.routeTesterProtocol')}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('pages.xray.routeTesterProtocol')}
|
||||
allowClear
|
||||
value={protocol}
|
||||
onChange={setProtocol}
|
||||
options={PROTOCOL_OPTIONS}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={fieldSpan} sm={3}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<AimOutlined />}
|
||||
loading={testing}
|
||||
disabled={!dest.trim()}
|
||||
onClick={run}
|
||||
block
|
||||
>
|
||||
{t('pages.xray.routeTesterTest')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{result &&
|
||||
(result.matched ? (
|
||||
<Alert
|
||||
type="success"
|
||||
showIcon
|
||||
title={
|
||||
<Space wrap>
|
||||
<span>{t('pages.xray.routeTesterMatchedOutbound')}:</span>
|
||||
<Tag color="blue">{result.outboundTag || '—'}</Tag>
|
||||
{(result.groupTags || []).length > 0 && (
|
||||
<>
|
||||
<span>{t('pages.xray.routeTesterViaBalancer')}:</span>
|
||||
{(result.groupTags || []).map((tag) => (
|
||||
<Tag key={tag} color="orange">
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Alert type="warning" showIcon title={t('pages.xray.routeTesterDefaultOutbound')} />
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Dropdown, Modal, Space, Table, Tabs, message } from 'antd';
|
||||
import {
|
||||
AimOutlined,
|
||||
ControlOutlined,
|
||||
ExportOutlined,
|
||||
ImportOutlined,
|
||||
@@ -16,7 +15,6 @@ import PromptModal from '@/components/feedback/PromptModal';
|
||||
import TextModal from '@/components/feedback/TextModal';
|
||||
import { isBalancerLoopbackTag } from '../balancers/balancer-loopback';
|
||||
import RoutingBasic from './RoutingBasic';
|
||||
import RouteTester from './RouteTester';
|
||||
import RuleFormModal from './RuleFormModal';
|
||||
import type { RoutingRule } from './RuleFormModal';
|
||||
import RuleCardList from './RuleCardList';
|
||||
@@ -430,11 +428,6 @@ export default function RoutingTab({
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'tester',
|
||||
label: catTabLabel(<AimOutlined />, t('pages.xray.routeTester'), isMobile),
|
||||
children: <RouteTester inboundTags={inboundTagOptions} isMobile={isMobile} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<RuleFormModal
|
||||
|
||||
Reference in New Issue
Block a user