From cefbb2f38f2e4f1739005ad53cda5c20d19b3423 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Fri, 30 Jan 2026 10:08:18 +0800 Subject: [PATCH] feat: Implement a pool monitoring dashboard and file upload functionality. --- backend/cmd/main.go | 14 +++++- frontend/src/pages/Monitor.tsx | 83 ++++++++++++++++------------------ 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/backend/cmd/main.go b/backend/cmd/main.go index cd699e3..cfe0deb 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -264,9 +264,19 @@ func handleS2AProxy(w http.ResponseWriter, r *http.Request) { return } - // 提取路径: /api/s2a/proxy/xxx -> /api/v1/admin/xxx + // 提取路径: /api/s2a/proxy/xxx -> 目标路径 path := strings.TrimPrefix(r.URL.Path, "/api/s2a/proxy") - targetURL := config.Global.S2AApiBase + "/api/v1/admin" + path + + // 如果路径不是以 /api/ 开通的,默认补上 /api/v1/admin 开头(兼容 dashboard 统计等) + // 如果已经是 /api/ 开头(如 /api/pool/polling),则保持原样 + var targetPath string + if strings.HasPrefix(path, "/api/") { + targetPath = path + } else { + targetPath = "/api/v1/admin" + path + } + + targetURL := config.Global.S2AApiBase + targetPath if r.URL.RawQuery != "" { targetURL += "?" + r.URL.RawQuery } diff --git a/frontend/src/pages/Monitor.tsx b/frontend/src/pages/Monitor.tsx index bae87bc..69dc491 100644 --- a/frontend/src/pages/Monitor.tsx +++ b/frontend/src/pages/Monitor.tsx @@ -14,7 +14,6 @@ import { Clock, } from 'lucide-react' import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common' -import { useConfig } from '../hooks/useConfig' import type { DashboardStats } from '../types' interface PoolStatus { @@ -50,7 +49,6 @@ interface AutoAddLog { } export default function Monitor() { - const { config } = useConfig() const [stats, setStats] = useState(null) const [poolStatus, setPoolStatus] = useState(null) const [healthResults, setHealthResults] = useState([]) @@ -67,51 +65,54 @@ export default function Monitor() { const [pollingEnabled, setPollingEnabled] = useState(false) const [pollingInterval, setPollingInterval] = useState(60) - const backendUrl = config.s2a.apiBase.replace(/\/api.*$/, '').replace(/:8080/, ':8088') + // 使用后端代理路径 + const proxyBase = '/api/s2a/proxy' + + // 辅助函数:解包 S2A 响应 + const requestS2A = async (url: string, options: RequestInit = {}) => { + const res = await fetch(url, options) + if (!res.ok) throw new Error(`HTTP ${res.status}`) + const data = await res.json() + if (data && typeof data === 'object' && 'code' in data && 'data' in data) { + if (data.code !== 0) throw new Error(data.message || 'API error') + return data.data + } + return data + } // 获取号池状态 const fetchPoolStatus = useCallback(async () => { try { - const res = await fetch(`${backendUrl}/api/pool/status`) - if (res.ok) { - const data = await res.json() - if (data.code === 0) { - setPoolStatus(data.data) - setTargetInput(data.data.target) - setAutoAdd(data.data.auto_add) - setMinInterval(data.data.min_interval) - setPollingEnabled(data.data.polling_enabled) - setPollingInterval(data.data.polling_interval) - } - } + const data = await requestS2A(`${proxyBase}/api/pool/status`) + setPoolStatus(data) + setTargetInput(data.target) + setAutoAdd(data.auto_add) + setMinInterval(data.min_interval) + setPollingEnabled(data.polling_enabled) + setPollingInterval(data.polling_interval) } catch (e) { console.error('获取号池状态失败:', e) } - }, [backendUrl]) + }, []) // 刷新 S2A 统计 const refreshStats = useCallback(async () => { setRefreshing(true) try { - const res = await fetch(`${backendUrl}/api/pool/refresh`, { method: 'POST' }) - if (res.ok) { - const data = await res.json() - if (data.code === 0) { - setStats(data.data) - } - } + const data = await requestS2A(`${proxyBase}/api/pool/refresh`, { method: 'POST' }) + setStats(data) await fetchPoolStatus() } catch (e) { console.error('刷新统计失败:', e) } setRefreshing(false) - }, [backendUrl, fetchPoolStatus]) + }, [fetchPoolStatus]) // 设置目标 const handleSetTarget = async () => { setLoading(true) try { - await fetch(`${backendUrl}/api/pool/target`, { + await requestS2A(`${proxyBase}/api/pool/target`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -131,7 +132,7 @@ export default function Monitor() { const handleTogglePolling = async () => { setLoading(true) try { - await fetch(`${backendUrl}/api/pool/polling`, { + await requestS2A(`${proxyBase}/api/pool/polling`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -151,19 +152,18 @@ export default function Monitor() { const handleHealthCheck = async (autoPause: boolean = false) => { setCheckingHealth(true) try { - await fetch(`${backendUrl}/api/health-check/start`, { + await requestS2A(`${proxyBase}/api/health-check/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ auto_pause: autoPause }), }) // 等待一会儿再获取结果 setTimeout(async () => { - const res = await fetch(`${backendUrl}/api/health-check/results`) - if (res.ok) { - const data = await res.json() - if (data.code === 0) { - setHealthResults(data.data || []) - } + try { + const data = await requestS2A(`${proxyBase}/api/health-check/results`) + setHealthResults(data || []) + } catch (e) { + console.error('获取健康检查结果失败:', e) } setCheckingHealth(false) }, 5000) @@ -176,13 +176,8 @@ export default function Monitor() { // 获取自动补号日志 const fetchAutoAddLogs = async () => { try { - const res = await fetch(`${backendUrl}/api/auto-add/logs`) - if (res.ok) { - const data = await res.json() - if (data.code === 0) { - setAutoAddLogs(data.data || []) - } - } + const data = await requestS2A(`${proxyBase}/api/auto-add/logs`) + setAutoAddLogs(data || []) } catch (e) { console.error('获取日志失败:', e) } @@ -560,10 +555,10 @@ export default function Monitor() {