fix(proxy): add system proxy connectivity test and align proxy UI copy
This commit is contained in:
@@ -55,6 +55,11 @@ type RuntimeSettings = {
|
||||
currentAdminIp?: string;
|
||||
};
|
||||
|
||||
type SystemProxyTestState =
|
||||
| { kind: 'success'; text: string }
|
||||
| { kind: 'error'; text: string }
|
||||
| null;
|
||||
|
||||
type DownstreamApiKeyItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -222,6 +227,8 @@ export default function Settings() {
|
||||
const [testingCheckin, setTestingCheckin] = useState(false);
|
||||
const [savingToken, setSavingToken] = useState(false);
|
||||
const [savingSystemProxy, setSavingSystemProxy] = useState(false);
|
||||
const [testingSystemProxy, setTestingSystemProxy] = useState(false);
|
||||
const [systemProxyTestState, setSystemProxyTestState] = useState<SystemProxyTestState>(null);
|
||||
const [savingProxyFailureRules, setSavingProxyFailureRules] = useState(false);
|
||||
const [savingRouting, setSavingRouting] = useState(false);
|
||||
const [showAdvancedRouting, setShowAdvancedRouting] = useState(false);
|
||||
@@ -576,6 +583,31 @@ export default function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
const testSystemProxy = async () => {
|
||||
const proxyUrl = runtime.systemProxyUrl.trim();
|
||||
if (!proxyUrl) {
|
||||
const message = '请先填写系统代理地址';
|
||||
setSystemProxyTestState({ kind: 'error', text: message });
|
||||
toast.info(message);
|
||||
return;
|
||||
}
|
||||
|
||||
setTestingSystemProxy(true);
|
||||
setSystemProxyTestState(null);
|
||||
try {
|
||||
const res = await api.testSystemProxy({ proxyUrl });
|
||||
const summary = `连通成功,延迟 ${res.latencyMs} ms`;
|
||||
setSystemProxyTestState({ kind: 'success', text: summary });
|
||||
toast.success(`系统代理测试成功(${res.latencyMs} ms)`);
|
||||
} catch (err: any) {
|
||||
const message = err?.message || '系统代理测试失败';
|
||||
setSystemProxyTestState({ kind: 'error', text: message });
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setTestingSystemProxy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveProxyFailureRules = async () => {
|
||||
setSavingProxyFailureRules(true);
|
||||
try {
|
||||
@@ -1114,13 +1146,39 @@ export default function Settings() {
|
||||
</div>
|
||||
<input
|
||||
value={runtime.systemProxyUrl}
|
||||
onChange={(e) => setRuntime((prev) => ({ ...prev, systemProxyUrl: e.target.value }))}
|
||||
onChange={(e) => {
|
||||
setRuntime((prev) => ({ ...prev, systemProxyUrl: e.target.value }));
|
||||
setSystemProxyTestState(null);
|
||||
}}
|
||||
placeholder="系统代理 URL(可选,如 http://127.0.0.1:7890 或 socks5://127.0.0.1:1080)"
|
||||
style={{ ...inputStyle, fontFamily: 'var(--font-mono)', marginBottom: 10 }}
|
||||
/>
|
||||
<button onClick={saveSystemProxy} disabled={savingSystemProxy} className="btn btn-primary">
|
||||
{savingSystemProxy ? <><span className="spinner spinner-sm" style={{ borderTopColor: 'white', borderColor: 'rgba(255,255,255,0.3)' }} /> 保存中...</> : '保存系统代理'}
|
||||
</button>
|
||||
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<button onClick={saveSystemProxy} disabled={savingSystemProxy} className="btn btn-primary">
|
||||
{savingSystemProxy ? <><span className="spinner spinner-sm" style={{ borderTopColor: 'white', borderColor: 'rgba(255,255,255,0.3)' }} /> 保存中...</> : '保存系统代理'}
|
||||
</button>
|
||||
<button
|
||||
onClick={testSystemProxy}
|
||||
disabled={testingSystemProxy}
|
||||
className="btn btn-ghost"
|
||||
style={{ border: '1px solid var(--color-border)' }}
|
||||
>
|
||||
{testingSystemProxy ? <><span className="spinner spinner-sm" /> 测试中...</> : '测试系统代理'}
|
||||
</button>
|
||||
</div>
|
||||
{systemProxyTestState && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginTop: 10,
|
||||
color: systemProxyTestState.kind === 'success'
|
||||
? 'var(--color-success)'
|
||||
: 'var(--color-danger)',
|
||||
}}
|
||||
>
|
||||
{systemProxyTestState.text}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="card animate-slide-up stagger-4" style={{ padding: 20 }}>
|
||||
|
||||
@@ -921,13 +921,13 @@ export default function Sites() {
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
placeholder="站点代理 URL(可选,如 http://127.0.0.1:7890 或 socks5://127.0.0.1:1080)"
|
||||
placeholder="站点代理(可选,如 http://127.0.0.1:7890 或 socks5://127.0.0.1:1080)"
|
||||
value={form.proxyUrl}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
||||
style={formInputStyle}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)' }}>
|
||||
留空则直连;填写后优先于系统代理。
|
||||
填写后优先使用站点代理;留空则使用系统代理或直连(取决于设置开关状态)。
|
||||
</div>
|
||||
<label style={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -12,6 +12,7 @@ const { apiMock } = vi.hoisted(() => ({
|
||||
getRoutesLite: vi.fn(),
|
||||
getRuntimeDatabaseConfig: vi.fn(),
|
||||
updateRuntimeSettings: vi.fn(),
|
||||
testSystemProxy: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -67,6 +68,16 @@ describe('Settings system proxy', () => {
|
||||
success: true,
|
||||
systemProxyUrl: 'http://127.0.0.1:7890',
|
||||
});
|
||||
apiMock.testSystemProxy.mockResolvedValue({
|
||||
success: true,
|
||||
proxyUrl: 'http://127.0.0.1:7890',
|
||||
probeUrl: 'https://www.gstatic.com/generate_204',
|
||||
finalUrl: 'https://www.gstatic.com/generate_204',
|
||||
reachable: true,
|
||||
ok: true,
|
||||
statusCode: 204,
|
||||
latencyMs: 321,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -112,4 +123,45 @@ describe('Settings system proxy', () => {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('tests system proxy from settings', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter>
|
||||
<ToastProvider>
|
||||
<Settings />
|
||||
</ToastProvider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const proxyInput = root.root.find((node) => (
|
||||
node.type === 'input'
|
||||
&& node.props.placeholder === '系统代理 URL(可选,如 http://127.0.0.1:7890 或 socks5://127.0.0.1:1080)'
|
||||
));
|
||||
await act(async () => {
|
||||
proxyInput.props.onChange({ target: { value: 'http://127.0.0.1:7890' } });
|
||||
});
|
||||
|
||||
const testButton = root.root.find((node) => (
|
||||
node.type === 'button'
|
||||
&& typeof node.props.onClick === 'function'
|
||||
&& collectText(node).trim() === '测试系统代理'
|
||||
));
|
||||
await act(async () => {
|
||||
testButton.props.onClick();
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(apiMock.testSystemProxy).toHaveBeenCalledWith({
|
||||
proxyUrl: 'http://127.0.0.1:7890',
|
||||
});
|
||||
expect(collectText(root.root)).toContain('连通成功,延迟 321 ms');
|
||||
} finally {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user