- 删除 requirements.txt,pyproject.toml 为唯一依赖源(含 requests[socks] extra) - core/geetest 下 5 处 print() 替换为 loguru logger.debug() - api/modules.ts 新增 13 个响应接口,消除所有 api.xxx<any, any> - 新建 utils/error.ts 提供 getErrorMessage(e: unknown) 安全取消息 - 11 个页面组件 catch (e: any) 改为 catch (e: unknown) - useState<any[]>、columns: any[]、render 参数全部类型化 - TypeScript 类型检查零错误通过
188 lines
6.3 KiB
TypeScript
188 lines
6.3 KiB
TypeScript
import { useEffect, useState, useRef } from 'react';
|
|
import { Form, Input, Switch, Button, Card, message, Row, Col, theme } from 'antd';
|
|
import { proxyApi, type ProxyConfig } from '../api/modules';
|
|
import { getErrorMessage } from '../utils/error';
|
|
|
|
const WS_BASE = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}`;
|
|
|
|
export default function ProxyPage() {
|
|
const { token } = theme.useToken();
|
|
const [form] = Form.useForm();
|
|
const [loading, setLoading] = useState(false);
|
|
const [testing, setTesting] = useState(false);
|
|
const [testingWl, setTestingWl] = useState(false);
|
|
const [configLoaded, setConfigLoaded] = useState(false);
|
|
const [logs, setLogs] = useState<{ level: string; message: string }[]>([]);
|
|
const wsRef = useRef<WebSocket | null>(null);
|
|
|
|
const loadConfig = async () => {
|
|
try {
|
|
const data = await proxyApi.get();
|
|
form.setFieldsValue({
|
|
enabled: data.enabled ?? false,
|
|
api_url: data.api_url ?? '',
|
|
http: data.http ?? '',
|
|
https: data.https ?? '',
|
|
whitelist_enabled: data.whitelist_enabled ?? false,
|
|
whitelist_uid: data.whitelist_uid ?? '',
|
|
whitelist_ukey: data.whitelist_ukey ?? '',
|
|
});
|
|
} catch (e: unknown) {
|
|
message.error(getErrorMessage(e));
|
|
} finally {
|
|
setConfigLoaded(true);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadConfig();
|
|
return () => {
|
|
wsRef.current?.close();
|
|
};
|
|
}, []);
|
|
|
|
const appendLog = (level: string, msg: string) => {
|
|
setLogs((prev) => [...prev, { level, message: msg }]);
|
|
};
|
|
|
|
const connectWs = (testId: string) => {
|
|
wsRef.current?.close();
|
|
setLogs([]);
|
|
const ws = new WebSocket(`${WS_BASE}/api/proxy/ws/test/${testId}`);
|
|
wsRef.current = ws;
|
|
ws.onmessage = (event) => {
|
|
const msg = JSON.parse(event.data);
|
|
if (msg.level === 'heartbeat') return;
|
|
if (msg.level === 'result') return;
|
|
appendLog(msg.level, msg.message);
|
|
};
|
|
ws.onclose = () => {
|
|
wsRef.current = null;
|
|
setTesting(false);
|
|
setTestingWl(false);
|
|
};
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const values = await form.validateFields();
|
|
await proxyApi.update(values as ProxyConfig);
|
|
message.success('已保存');
|
|
} catch (e: unknown) {
|
|
message.error(getErrorMessage(e));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleTestProxy = async () => {
|
|
setTesting(true);
|
|
setLogs([]);
|
|
try {
|
|
const result = await proxyApi.test();
|
|
if (result.test_id) connectWs(result.test_id);
|
|
} catch (e: unknown) {
|
|
message.error(getErrorMessage(e));
|
|
setTesting(false);
|
|
}
|
|
};
|
|
|
|
const handleTestWhitelist = async () => {
|
|
setTestingWl(true);
|
|
setLogs([]);
|
|
try {
|
|
const result = await proxyApi.testWhitelist();
|
|
if (result.test_id) connectWs(result.test_id);
|
|
} catch (e: unknown) {
|
|
message.error(getErrorMessage(e));
|
|
setTestingWl(false);
|
|
}
|
|
};
|
|
|
|
|
|
const logColors: Record<string, string> = {
|
|
error: token.colorError,
|
|
success: token.colorSuccess,
|
|
warning: token.colorWarning,
|
|
info: token.colorText,
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', gap: 8 }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
|
|
<h2 style={{ margin: 0 }}>代理配置</h2>
|
|
<Button type="primary" onClick={handleSave} loading={loading}>保存配置</Button>
|
|
</div>
|
|
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
disabled={!configLoaded}
|
|
size="small"
|
|
initialValues={{
|
|
enabled: false,
|
|
whitelist_enabled: false,
|
|
api_url: '',
|
|
http: '',
|
|
https: '',
|
|
whitelist_uid: '',
|
|
whitelist_ukey: '',
|
|
}}
|
|
style={{ flexShrink: 0 }}
|
|
>
|
|
<Row gutter={12}>
|
|
<Col span={12}>
|
|
<Card title="代理设置" size="small" styles={{ body: { paddingBottom: 8 } }}>
|
|
<Form.Item name="enabled" label="启用代理" valuePropName="checked" style={{ marginBottom: 8 }}>
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item name="api_url" label="代理API地址" style={{ marginBottom: 8 }}>
|
|
<Input placeholder="http://op.xiequ.cn/...?act=get" />
|
|
</Form.Item>
|
|
<Form.Item name="http" label="静态HTTP代理" style={{ marginBottom: 8 }}>
|
|
<Input placeholder="http://ip:port" />
|
|
</Form.Item>
|
|
<Form.Item name="https" label="静态HTTPS代理" style={{ marginBottom: 8 }}>
|
|
<Input placeholder="http://ip:port" />
|
|
</Form.Item>
|
|
<Button size="small" onClick={handleTestProxy} loading={testing}>测试代理</Button>
|
|
</Card>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Card title="白名单管理" size="small" styles={{ body: { paddingBottom: 8 } }}>
|
|
<Form.Item name="whitelist_enabled" label="启用白名单自动管理" valuePropName="checked" style={{ marginBottom: 8 }}>
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item name="whitelist_uid" label="协固UID" style={{ marginBottom: 8 }}>
|
|
<Input placeholder="如: 99769" />
|
|
</Form.Item>
|
|
<Form.Item name="whitelist_ukey" label="协固UKEY" style={{ marginBottom: 8 }}>
|
|
<Input placeholder="如: C99371082B965B70F46DCAA87A04618B" />
|
|
</Form.Item>
|
|
<Button size="small" onClick={handleTestWhitelist} loading={testingWl}>测试白名单</Button>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</Form>
|
|
|
|
<Card
|
|
title="实时日志"
|
|
size="small"
|
|
style={{ flex: 1, overflow: 'hidden' }}
|
|
styles={{ body: { height: '100%', overflow: 'auto', fontFamily: 'monospace', fontSize: 12, padding: '8px 16px' } }}
|
|
>
|
|
{logs.length === 0 ? (
|
|
<span style={{ color: token.colorTextTertiary }}>点击"测试代理"或"测试白名单"查看日志</span>
|
|
) : (
|
|
logs.map((log, i) => (
|
|
<div key={i} style={{ color: logColors[log.level] || token.colorText, lineHeight: '20px' }}>
|
|
{log.message}
|
|
</div>
|
|
))
|
|
)}
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|