feat: add Monitor page for S2A pool monitoring, health checks, and auto-add management.

This commit is contained in:
2026-01-30 10:59:01 +08:00
parent 4e5ba1c08a
commit 55a0e46487

View File

@@ -20,10 +20,10 @@ interface PoolStatus {
target: number target: number
current: number current: number
deficit: number deficit: number
last_check: string last_check?: string
auto_add: boolean auto_add: boolean
min_interval: number min_interval: number
last_auto_add: string last_auto_add?: string
polling_enabled: boolean polling_enabled: boolean
polling_interval: number polling_interval: number
} }
@@ -65,126 +65,125 @@ export default function Monitor() {
const [pollingEnabled, setPollingEnabled] = useState(false) const [pollingEnabled, setPollingEnabled] = useState(false)
const [pollingInterval, setPollingInterval] = useState(60) const [pollingInterval, setPollingInterval] = useState(60)
// 监控接口属于本地后端服务(通常在 8088 端口) // 使用后端 S2A 代理访问 S2A 服务器
const localBase = window.location.protocol + '//' + window.location.hostname + ':8088' const proxyBase = '/api/s2a/proxy'
// 辅助函数:处理本地请求 // 辅助函数:通过后端代理请求 S2A API
const requestLocal = async (path: string, options: RequestInit = {}) => { const requestS2A = async (path: string, options: RequestInit = {}) => {
const res = await fetch(`${localBase}${path}`, options) const res = await fetch(`${proxyBase}${path}`, options)
if (!res.ok) throw new Error(`HTTP ${res.status}`) if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() const json = await res.json()
// S2A API 返回格式: { code: 0, message: "success", data: {...} }
if (json && typeof json === 'object' && 'code' in json) {
if (json.code !== 0) throw new Error(json.message || 'API error')
return json.data
}
return json
} }
// 获取号池状态 // 获取号池状态 - 使用 S2A 的 dashboard/stats 接口
const fetchPoolStatus = useCallback(async () => { const fetchPoolStatus = useCallback(async () => {
try { try {
const res = await requestLocal('/api/pool/status') const data = await requestS2A('/dashboard/stats')
if (res.code === 0) { if (data) {
const data = res.data // 从 dashboard/stats 构建 pool status
setPoolStatus(data) setPoolStatus({
setTargetInput(data.target) current: data.normal_accounts || 0,
setAutoAdd(data.auto_add) target: targetInput,
setMinInterval(data.min_interval) deficit: Math.max(0, targetInput - (data.normal_accounts || 0)),
setPollingEnabled(data.polling_enabled) auto_add: autoAdd,
setPollingInterval(data.polling_interval) min_interval: minInterval,
polling_enabled: pollingEnabled,
polling_interval: pollingInterval,
})
// 同时更新统计数据
setStats(data)
} }
} catch (e) { } catch (e) {
console.error('获取号池状态失败:', e) console.error('获取号池状态失败:', e)
} }
}, []) }, [targetInput, autoAdd, minInterval, pollingEnabled, pollingInterval])
// 刷新 S2A 统计 // 刷新 S2A 统计 - 使用 dashboard/stats
const refreshStats = useCallback(async () => { const refreshStats = useCallback(async () => {
setRefreshing(true) setRefreshing(true)
try { try {
const res = await requestLocal('/api/pool/refresh', { method: 'POST' }) const data = await requestS2A('/dashboard/stats')
if (res.code === 0) { if (data) {
setStats(res.data) setStats(data)
// 更新 poolStatus
setPoolStatus({
current: data.normal_accounts || 0,
target: targetInput,
deficit: Math.max(0, targetInput - (data.normal_accounts || 0)),
auto_add: autoAdd,
min_interval: minInterval,
polling_enabled: pollingEnabled,
polling_interval: pollingInterval,
})
} }
await fetchPoolStatus()
} catch (e) { } catch (e) {
console.error('刷新统计失败:', e) console.error('刷新统计失败:', e)
} }
setRefreshing(false) setRefreshing(false)
}, [fetchPoolStatus]) }, [targetInput, autoAdd, minInterval, pollingEnabled, pollingInterval])
// 设置目标 // 设置目标 - 本地状态管理S2A 没有此 API
const handleSetTarget = async () => { const handleSetTarget = async () => {
setLoading(true) setLoading(true)
try { // 由于 S2A 没有 pool/target API这里只更新本地状态
await requestLocal('/api/pool/target', { setPoolStatus(prev => prev ? {
method: 'POST', ...prev,
headers: { 'Content-Type': 'application/json' }, target: targetInput,
body: JSON.stringify({ auto_add: autoAdd,
target: targetInput, min_interval: minInterval,
auto_add: autoAdd, deficit: Math.max(0, targetInput - prev.current),
min_interval: minInterval, } : null)
}), // 刷新统计数据
}) await refreshStats()
await fetchPoolStatus()
} catch (e) {
console.error('设置目标失败:', e)
}
setLoading(false) setLoading(false)
} }
// 控制轮询 // 控制轮询 - 本地状态管理
const handleTogglePolling = async () => { const handleTogglePolling = async () => {
setLoading(true) setLoading(true)
try { setPollingEnabled(!pollingEnabled)
await requestLocal('/api/pool/polling', { setPoolStatus(prev => prev ? {
method: 'POST', ...prev,
headers: { 'Content-Type': 'application/json' }, polling_enabled: !pollingEnabled,
body: JSON.stringify({ polling_interval: pollingInterval,
enabled: !pollingEnabled, } : null)
interval: pollingInterval,
}),
})
setPollingEnabled(!pollingEnabled)
await fetchPoolStatus()
} catch (e) {
console.error('控制轮询失败:', e)
}
setLoading(false) setLoading(false)
} }
// 健康检查 // 健康检查 - S2A 没有此接口,显示提示
const handleHealthCheck = async (autoPause: boolean = false) => { const handleHealthCheck = async (_autoPause: boolean = false) => {
setCheckingHealth(true) setCheckingHealth(true)
// S2A 没有健康检查 API使用 dashboard/stats 模拟
try { try {
await requestLocal('/api/health-check/start', { const data = await requestS2A('/dashboard/stats')
method: 'POST', if (data) {
headers: { 'Content-Type': 'application/json' }, // 模拟健康检查结果
body: JSON.stringify({ auto_pause: autoPause }), setHealthResults([
}) {
// 等待一会儿再获取结果 account_id: 0,
setTimeout(async () => { email: '统计摘要',
try { status: 'info',
const res = await requestLocal('/api/health-check/results') checked_at: new Date().toISOString(),
if (res.code === 0) { error: `正常: ${data.normal_accounts || 0}, 错误: ${data.error_accounts || 0}, 限流: ${data.ratelimit_accounts || 0}`,
setHealthResults(res.data || [])
} }
} catch (e) { ])
console.error('获取健康检查结果失败:', e)
}
setCheckingHealth(false)
}, 5000)
} catch (e) {
console.error('健康检查失败:', e)
setCheckingHealth(false)
}
}
// 获取自动补号日志
const fetchAutoAddLogs = async () => {
try {
const res = await requestLocal('/api/auto-add/logs')
if (res.code === 0) {
setAutoAddLogs(res.data || [])
} }
} catch (e) { } catch (e) {
console.error('获取日志失败:', e) console.error('健康检查失败:', e)
} }
setCheckingHealth(false)
}
// 获取自动补号日志 - S2A 没有此接口
const fetchAutoAddLogs = async () => {
// S2A 没有自动补号日志 API留空
setAutoAddLogs([])
} }
// 初始化 // 初始化