优化代理配置
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -115,7 +115,7 @@ async def ws_login_logs(websocket: WebSocket, batch_id: str):
|
||||
await websocket.accept()
|
||||
|
||||
log_queue = asyncio.Queue()
|
||||
loop = asyncio.get_event_loop()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# 查找已运行的批次,或等待新批次
|
||||
# 简化:直接把 log_queue 注册到全局,前端创建批次后连 ws
|
||||
|
||||
+221
-76
@@ -1,17 +1,22 @@
|
||||
"""代理 & 白名单配置路由"""
|
||||
"""代理 & 白名单配置路由 + WebSocket 实时日志"""
|
||||
|
||||
import re
|
||||
import time
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
import asyncio
|
||||
import threading
|
||||
import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import User, ProxyConfig as ProxyConfigModel, AuditLog
|
||||
from ..schemas import ProxyConfigOut, ProxyConfigUpdate, MessageResponse
|
||||
from ..schemas import ProxyConfigOut, ProxyConfigUpdate
|
||||
from ..deps import require_permission
|
||||
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
|
||||
|
||||
router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"])
|
||||
|
||||
# 运行中的测试: test_id -> {log_queue, loop, result}
|
||||
_active_tests: dict[str, dict] = {}
|
||||
|
||||
|
||||
def _get_or_create(db: Session) -> ProxyConfigModel:
|
||||
cfg = db.query(ProxyConfigModel).first()
|
||||
@@ -54,93 +59,233 @@ def update_proxy_config(
|
||||
return cfg
|
||||
|
||||
|
||||
# ---- WebSocket 日志推送 ----
|
||||
|
||||
@router.websocket("/ws/test/{test_id}")
|
||||
async def ws_test_logs(websocket: WebSocket, test_id: str):
|
||||
"""WebSocket 推送代理/白名单测试实时日志。"""
|
||||
await websocket.accept()
|
||||
|
||||
test = _active_tests.get(test_id)
|
||||
if not test:
|
||||
await websocket.send_json({"level": "error", "message": "测试任务不存在"})
|
||||
await websocket.close()
|
||||
return
|
||||
|
||||
log_queue: asyncio.Queue = test["log_queue"]
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
msg = await asyncio.wait_for(log_queue.get(), timeout=30)
|
||||
await websocket.send_json(msg)
|
||||
# 收到 result 消息表示测试结束
|
||||
if msg.get("level") == "result":
|
||||
await asyncio.sleep(0.1)
|
||||
break
|
||||
except asyncio.TimeoutError:
|
||||
await websocket.send_json({"level": "heartbeat", "message": ""})
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
finally:
|
||||
_active_tests.pop(test_id, None)
|
||||
|
||||
|
||||
# ---- 异步测试执行 ----
|
||||
|
||||
def _run_proxy_test(
|
||||
cfg: ProxyConfigModel,
|
||||
log_queue: asyncio.Queue,
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
):
|
||||
"""在线程中执行代理测试。"""
|
||||
|
||||
def push(level, message):
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
log_queue.put({"level": level, "message": message}),
|
||||
loop,
|
||||
)
|
||||
|
||||
try:
|
||||
if not cfg.enabled:
|
||||
push("error", "代理未启用")
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
# 静态代理
|
||||
if cfg.http or cfg.https:
|
||||
proxy_url = cfg.http or cfg.https
|
||||
push("info", f"验证静态代理: {proxy_url}")
|
||||
ok, msg = verify_proxy_url(proxy_url)
|
||||
push("success" if ok else "error", msg)
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
# API代理
|
||||
if cfg.api_url:
|
||||
whitelist_uid = cfg.whitelist_uid if cfg.whitelist_enabled else ""
|
||||
whitelist_ukey = cfg.whitelist_ukey if cfg.whitelist_enabled else ""
|
||||
|
||||
proxy_url, msg = resolve_working_proxy(
|
||||
api_url=cfg.api_url,
|
||||
whitelist_uid=whitelist_uid,
|
||||
whitelist_ukey=whitelist_ukey,
|
||||
max_attempts=3,
|
||||
log_func=push,
|
||||
)
|
||||
if proxy_url:
|
||||
push("success", f"代理可用: {proxy_url}")
|
||||
else:
|
||||
push("error", msg)
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
push("error", "未配置代理地址或API")
|
||||
push("result", "")
|
||||
except Exception as e:
|
||||
push("error", f"测试异常: {e}")
|
||||
push("result", "")
|
||||
|
||||
|
||||
def _run_whitelist_test(
|
||||
cfg: ProxyConfigModel,
|
||||
log_queue: asyncio.Queue,
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
):
|
||||
"""在线程中执行白名单测试。"""
|
||||
import requests as req_lib
|
||||
import re
|
||||
from core.douyu.whitelist import WhitelistManager, get_exit_ip_via_proxy
|
||||
|
||||
def push(level, message):
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
log_queue.put({"level": level, "message": message}),
|
||||
loop,
|
||||
)
|
||||
|
||||
try:
|
||||
if not cfg.whitelist_enabled:
|
||||
push("error", "白名单未启用")
|
||||
push("result", "")
|
||||
return
|
||||
if not cfg.whitelist_uid or not cfg.whitelist_ukey:
|
||||
push("error", "未配置白名单UID/UKEY")
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
manager = WhitelistManager(cfg.whitelist_uid, cfg.whitelist_ukey)
|
||||
|
||||
# 1. 测试API连接
|
||||
push("info", "测试白名单API连接...")
|
||||
ok, msg = manager.test_connection()
|
||||
push("info" if ok else "error", f"白名单API: {msg}")
|
||||
if not ok:
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
# 2. 获取本机公网IP(白名单需要的是本机IP,不是代理出口IP)
|
||||
push("info", "正在获取本机公网IP...")
|
||||
|
||||
# 优先:从代理API响应中提取(代理API返回"请先添加白名单:xxx"时,xxx就是本机IP)
|
||||
local_ip = None
|
||||
if cfg.api_url:
|
||||
try:
|
||||
resp = req_lib.get(cfg.api_url, timeout=10)
|
||||
text = resp.text.strip()
|
||||
push("info", f"代理API响应: {text[:80]}")
|
||||
_, whitelist_ip = parse_proxy_response(text)
|
||||
if whitelist_ip:
|
||||
local_ip = whitelist_ip
|
||||
push("info", f"从代理API获取到本机IP: {local_ip}")
|
||||
except Exception as e:
|
||||
push("warning", f"代理API请求失败: {e}")
|
||||
|
||||
# 备用:直接访问IP检测服务获取本机公网IP
|
||||
if not local_ip:
|
||||
push("info", "通过IP检测服务获取本机公网IP...")
|
||||
for url in [
|
||||
'https://qifu-api.baidubce.com/ip/local/geo/v1/district',
|
||||
'https://myip.ipip.net',
|
||||
'https://4.ipw.cn',
|
||||
]:
|
||||
try:
|
||||
resp = req_lib.get(url, timeout=6, headers={"User-Agent": "Mozilla/5.0"})
|
||||
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', resp.text)
|
||||
if match:
|
||||
local_ip = match.group(1)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not local_ip:
|
||||
push("error", "无法获取本机公网IP")
|
||||
push("result", "")
|
||||
return
|
||||
|
||||
push("info", f"本机公网IP: {local_ip}")
|
||||
|
||||
# 3. 检查并同步白名单
|
||||
records = manager.get_whitelist_json()
|
||||
in_list = any(r.get('IP') == local_ip for r in records)
|
||||
push("info", f"白名单共 {len(records)} 条记录")
|
||||
|
||||
if in_list:
|
||||
record = next((r for r in records if r.get('IP') == local_ip), {})
|
||||
memo = record.get('MEMO', '')
|
||||
if memo == manager.memo:
|
||||
push("success", f"本机IP {local_ip} 已在白名单中 (备注正确)")
|
||||
else:
|
||||
push("warning", f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...')
|
||||
sync_ok, sync_msg = manager.sync_ip(local_ip)
|
||||
push("success" if sync_ok else "error", f"白名单更新: {sync_msg}")
|
||||
else:
|
||||
push("info", f"正在将 {local_ip} 添加到白名单...")
|
||||
sync_ok, sync_msg = manager.sync_ip(local_ip)
|
||||
push("success" if sync_ok else "error", f"白名单同步: {sync_msg}")
|
||||
|
||||
push("result", "")
|
||||
except Exception as e:
|
||||
push("error", f"测试异常: {e}")
|
||||
push("result", "")
|
||||
|
||||
|
||||
# ---- API 端点 ----
|
||||
|
||||
@router.post("/test")
|
||||
def test_proxy(
|
||||
async def test_proxy(
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("proxy:manage")),
|
||||
):
|
||||
"""测试代理连通性。"""
|
||||
import requests as req_lib
|
||||
"""启动代理测试(异步执行,通过 WebSocket 推送日志)。"""
|
||||
cfg = _get_or_create(db)
|
||||
if not cfg.enabled:
|
||||
return {"success": False, "message": "代理未启用"}
|
||||
|
||||
proxy_url = cfg.http or cfg.https
|
||||
if cfg.api_url and not proxy_url:
|
||||
try:
|
||||
resp = req_lib.get(cfg.api_url, timeout=10)
|
||||
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text)
|
||||
if match:
|
||||
proxy_url = f"http://{match.group(1)}:{match.group(2)}"
|
||||
except Exception as e:
|
||||
return {"success": False, "message": f"代理API请求失败: {e}"}
|
||||
test_id = uuid.uuid4().hex[:12]
|
||||
log_queue = asyncio.Queue()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
if not proxy_url:
|
||||
return {"success": False, "message": "无可用代理地址"}
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
|
||||
try:
|
||||
resp = req_lib.get(
|
||||
"https://qifu-api.baidubce.com/ip/local/geo/v1/district",
|
||||
proxies={"http": proxy_url, "https": proxy_url},
|
||||
timeout=(4, 6),
|
||||
headers={"User-Agent": "Mozilla/5.0"},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return {"success": True, "message": f"代理可用,响应: {resp.text[:100]}"}
|
||||
except Exception as e:
|
||||
return {"success": False, "message": f"代理验证失败: {e}"}
|
||||
thread = threading.Thread(target=_run_proxy_test, args=(cfg, log_queue, loop), daemon=True)
|
||||
thread.start()
|
||||
|
||||
return {"test_id": test_id, "success": True}
|
||||
|
||||
|
||||
@router.post("/whitelist/test")
|
||||
def test_whitelist(
|
||||
async def test_whitelist(
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("whitelist:test")),
|
||||
):
|
||||
"""测试白名单连接并自动同步出口IP。"""
|
||||
from core.douyu.whitelist import WhitelistManager, get_exit_ip_via_proxy
|
||||
import requests as req_lib
|
||||
|
||||
"""启动白名单测试(异步执行,通过 WebSocket 推送日志)。"""
|
||||
cfg = _get_or_create(db)
|
||||
if not cfg.whitelist_enabled:
|
||||
return {"success": False, "message": "白名单未启用"}
|
||||
if not cfg.whitelist_uid or not cfg.whitelist_ukey:
|
||||
return {"success": False, "message": "未配置白名单UID/UKEY"}
|
||||
|
||||
manager = WhitelistManager(cfg.whitelist_uid, cfg.whitelist_ukey)
|
||||
test_id = uuid.uuid4().hex[:12]
|
||||
log_queue = asyncio.Queue()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# 测试API连接
|
||||
ok, msg = manager.test_connection()
|
||||
if not ok:
|
||||
return {"success": False, "message": msg}
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
|
||||
# 获取出口IP
|
||||
proxy_url = cfg.http or cfg.https
|
||||
if cfg.api_url and not proxy_url:
|
||||
try:
|
||||
resp = req_lib.get(cfg.api_url, timeout=10)
|
||||
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text)
|
||||
if match:
|
||||
proxy_url = f"http://{match.group(1)}:{match.group(2)}"
|
||||
except Exception:
|
||||
pass
|
||||
thread = threading.Thread(target=_run_whitelist_test, args=(cfg, log_queue, loop), daemon=True)
|
||||
thread.start()
|
||||
|
||||
exit_ip = None
|
||||
if proxy_url:
|
||||
exit_ip = get_exit_ip_via_proxy(proxy_url)
|
||||
if not exit_ip:
|
||||
try:
|
||||
resp = req_lib.get("https://4.ipw.cn", timeout=6, headers={"User-Agent": "Mozilla/5.0"})
|
||||
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', resp.text)
|
||||
if match:
|
||||
exit_ip = match.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not exit_ip:
|
||||
return {"success": False, "message": "API连接正常但无法获取出口IP"}
|
||||
|
||||
# 同步白名单
|
||||
sync_ok, sync_msg = manager.sync_ip(exit_ip)
|
||||
if sync_ok:
|
||||
return {"success": True, "message": f"出口IP {exit_ip} 已同步: {sync_msg}"}
|
||||
return {"success": False, "message": f"同步失败: {sync_msg}"}
|
||||
return {"test_id": test_id, "success": True}
|
||||
|
||||
Binary file not shown.
@@ -1,8 +1,7 @@
|
||||
"""登录服务:复用 douyu/ 核心模块,在线程池中执行登录并推送日志。"""
|
||||
"""登录服务:复用 core/ 核心模块,在线程池中执行登录并推送日志。"""
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
@@ -11,6 +10,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from core.douyu import DouyuLogin
|
||||
from core.models import Account, ProxyConfig as DouyuProxyConfig
|
||||
from core.douyu.proxy import resolve_working_proxy
|
||||
from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel
|
||||
from ..permissions import has_permission
|
||||
|
||||
@@ -50,6 +50,42 @@ class LoginBatchRunner:
|
||||
self.loop,
|
||||
)
|
||||
|
||||
def _resolve_proxy(self) -> tuple[Optional[dict], str]:
|
||||
"""
|
||||
解析代理配置,返回 (proxy_dict, message)。
|
||||
|
||||
- 静态代理:直接返回 dict
|
||||
- API代理:调用 resolve_working_proxy 预检,自动同步白名单
|
||||
- 无代理:返回 (None, '')
|
||||
"""
|
||||
if not self.proxy_config or not self.proxy_config.enabled:
|
||||
return None, ''
|
||||
|
||||
# 静态代理
|
||||
if self.proxy_config.http or self.proxy_config.https:
|
||||
proxy_url = self.proxy_config.http or self.proxy_config.https
|
||||
return {'http': proxy_url, 'https': proxy_url}, f'使用静态代理: {proxy_url}'
|
||||
|
||||
# API代理:预检获取可用代理
|
||||
if self.proxy_config.api_url:
|
||||
whitelist_uid = ''
|
||||
whitelist_ukey = ''
|
||||
if self.proxy_config.whitelist_enabled:
|
||||
whitelist_uid = self.proxy_config.whitelist_uid or ''
|
||||
whitelist_ukey = self.proxy_config.whitelist_ukey or ''
|
||||
|
||||
proxy_url, msg = resolve_working_proxy(
|
||||
api_url=self.proxy_config.api_url,
|
||||
whitelist_uid=whitelist_uid,
|
||||
whitelist_ukey=whitelist_ukey,
|
||||
log_func=self._push_log,
|
||||
)
|
||||
if proxy_url:
|
||||
return {'http': proxy_url, 'https': proxy_url}, msg
|
||||
return None, msg
|
||||
|
||||
return None, ''
|
||||
|
||||
def run(self):
|
||||
"""在线程中执行批量登录。"""
|
||||
batch_id = self.batch_id
|
||||
@@ -78,17 +114,20 @@ class LoginBatchRunner:
|
||||
|
||||
self.db.commit()
|
||||
|
||||
# 构建代理配置
|
||||
proxy_url = None
|
||||
proxy_api_url = None
|
||||
if self.proxy_config and self.proxy_config.enabled:
|
||||
if self.proxy_config.http or self.proxy_config.https:
|
||||
proxy_url = {
|
||||
'http': self.proxy_config.http or self.proxy_config.https,
|
||||
'https': self.proxy_config.https or self.proxy_config.http,
|
||||
}
|
||||
elif self.proxy_config.api_url:
|
||||
proxy_api_url = self.proxy_config.api_url
|
||||
# 代理预检
|
||||
proxy_dict, proxy_msg = self._resolve_proxy()
|
||||
if proxy_msg:
|
||||
self._push_log("info", proxy_msg)
|
||||
|
||||
# 如果启用了代理但预检失败,终止任务
|
||||
if self.proxy_config and self.proxy_config.enabled and not proxy_dict:
|
||||
self._push_log("error", f"代理不可用,任务终止: {proxy_msg}")
|
||||
for task, _ in tasks:
|
||||
task.status = "error"
|
||||
task.message = f"代理不可用: {proxy_msg}"
|
||||
task.finished_at = datetime.utcnow()
|
||||
self.db.commit()
|
||||
return
|
||||
|
||||
for i, (task, acc) in enumerate(tasks):
|
||||
if self._stop.is_set():
|
||||
@@ -112,8 +151,7 @@ class LoginBatchRunner:
|
||||
|
||||
loginer = DouyuLogin(
|
||||
account,
|
||||
proxy=proxy_url,
|
||||
proxy_api_url=proxy_api_url,
|
||||
proxy=proxy_dict,
|
||||
max_geetest_retries=self.max_geetest_retries,
|
||||
)
|
||||
result = loginer.login()
|
||||
|
||||
@@ -1,26 +1,64 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Form, Input, Switch, Button, Card, message, Divider, Space } from 'antd';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { Form, Input, Switch, Button, Card, message, Row, Col } from 'antd';
|
||||
import { proxyApi } from '../api/modules';
|
||||
|
||||
const WS_BASE = `ws://${window.location.hostname}:8000`;
|
||||
|
||||
export default function ProxyPage() {
|
||||
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(data);
|
||||
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: any) {
|
||||
message.error(e.message);
|
||||
} 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;
|
||||
};
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -36,74 +74,115 @@ export default function ProxyPage() {
|
||||
|
||||
const handleTestProxy = async () => {
|
||||
setTesting(true);
|
||||
setLogs([]);
|
||||
try {
|
||||
const result = await proxyApi.test();
|
||||
if (result.success) {
|
||||
message.success(result.message);
|
||||
} else {
|
||||
message.warning(result.message);
|
||||
}
|
||||
if (result.test_id) connectWs(result.test_id);
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestWhitelist = async () => {
|
||||
setTestingWl(true);
|
||||
setLogs([]);
|
||||
try {
|
||||
const result = await proxyApi.testWhitelist();
|
||||
if (result.success) {
|
||||
message.success(result.message);
|
||||
} else {
|
||||
message.warning(result.message);
|
||||
}
|
||||
if (result.test_id) connectWs(result.test_id);
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setTestingWl(false);
|
||||
}
|
||||
};
|
||||
|
||||
// WebSocket 关闭后恢复按钮
|
||||
useEffect(() => {
|
||||
if (!wsRef.current) {
|
||||
setTesting(false);
|
||||
setTestingWl(false);
|
||||
}
|
||||
}, [logs.length === 0]);
|
||||
|
||||
const logColors: Record<string, string> = {
|
||||
error: '#ff4d4f',
|
||||
success: '#52c41a',
|
||||
warning: '#faad14',
|
||||
info: '#333',
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>代理配置</h2>
|
||||
<Form form={form} layout="vertical" style={{ maxWidth: 600 }}>
|
||||
<Card title="代理设置" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item name="enabled" label="启用代理" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="api_url" label="代理API地址">
|
||||
<Input placeholder="http://op.xiequ.cn/...?act=get" />
|
||||
</Form.Item>
|
||||
<Form.Item name="http" label="静态HTTP代理">
|
||||
<Input placeholder="http://ip:port" />
|
||||
</Form.Item>
|
||||
<Form.Item name="https" label="静态HTTPS代理">
|
||||
<Input placeholder="http://ip:port" />
|
||||
</Form.Item>
|
||||
<Button onClick={handleTestProxy} loading={testing}>测试代理</Button>
|
||||
</Card>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h2 style={{ margin: 0 }}>代理配置</h2>
|
||||
<Button type="primary" onClick={handleSave} loading={loading}>保存配置</Button>
|
||||
</div>
|
||||
|
||||
<Card title="白名单管理" size="small">
|
||||
<Form.Item name="whitelist_enabled" label="启用白名单自动管理" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="whitelist_uid" label="协固UID">
|
||||
<Input placeholder="如: 99769" />
|
||||
</Form.Item>
|
||||
<Form.Item name="whitelist_ukey" label="协固UKEY">
|
||||
<Input placeholder="如: C99371082B965B70F46DCAA87A04618B" />
|
||||
</Form.Item>
|
||||
<Button onClick={handleTestWhitelist} loading={testingWl}>测试白名单</Button>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
<Space>
|
||||
<Button type="primary" onClick={handleSave} loading={loading}>保存配置</Button>
|
||||
</Space>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
disabled={!configLoaded}
|
||||
initialValues={{
|
||||
enabled: false,
|
||||
whitelist_enabled: false,
|
||||
api_url: '',
|
||||
http: '',
|
||||
https: '',
|
||||
whitelist_uid: '',
|
||||
whitelist_ukey: '',
|
||||
}}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card title="代理设置" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item name="enabled" label="启用代理" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="api_url" label="代理API地址">
|
||||
<Input placeholder="http://op.xiequ.cn/...?act=get" />
|
||||
</Form.Item>
|
||||
<Form.Item name="http" label="静态HTTP代理">
|
||||
<Input placeholder="http://ip:port" />
|
||||
</Form.Item>
|
||||
<Form.Item name="https" label="静态HTTPS代理">
|
||||
<Input placeholder="http://ip:port" />
|
||||
</Form.Item>
|
||||
<Button onClick={handleTestProxy} loading={testing}>测试代理</Button>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title="白名单管理" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item name="whitelist_enabled" label="启用白名单自动管理" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="whitelist_uid" label="协固UID">
|
||||
<Input placeholder="如: 99769" />
|
||||
</Form.Item>
|
||||
<Form.Item name="whitelist_ukey" label="协固UKEY">
|
||||
<Input placeholder="如: C99371082B965B70F46DCAA87A04618B" />
|
||||
</Form.Item>
|
||||
<Button onClick={handleTestWhitelist} loading={testingWl}>测试白名单</Button>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
|
||||
<Card
|
||||
title="实时日志"
|
||||
size="small"
|
||||
style={{ flex: 1, minHeight: 200, overflow: 'auto' }}
|
||||
styles={{ body: { maxHeight: 350, overflow: 'auto', fontFamily: 'monospace', fontSize: 12, padding: '8px 16px' } }}
|
||||
>
|
||||
{logs.length === 0 ? (
|
||||
<span style={{ color: '#999' }}>点击"测试代理"或"测试白名单"查看日志</span>
|
||||
) : (
|
||||
logs.map((log, i) => (
|
||||
<div key={i} style={{ color: logColors[log.level] || '#333', lineHeight: '20px' }}>
|
||||
{log.message}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user