import { useState, useEffect, useCallback } from 'react' import { Target, Activity, RefreshCw, Play, Pause, Shield, TrendingUp, TrendingDown, Zap, AlertTriangle, CheckCircle, 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 { target: number current: number deficit: number last_check: string auto_add: boolean min_interval: number last_auto_add: string polling_enabled: boolean polling_interval: number } interface HealthCheckResult { account_id: number email: string status: string checked_at: string error?: string auto_paused?: boolean } interface AutoAddLog { timestamp: string target: number current: number deficit: number action: string success: number failed: number message: string } export default function Monitor() { const { config } = useConfig() const [stats, setStats] = useState(null) const [poolStatus, setPoolStatus] = useState(null) const [healthResults, setHealthResults] = useState([]) const [autoAddLogs, setAutoAddLogs] = useState([]) const [loading, setLoading] = useState(false) const [refreshing, setRefreshing] = useState(false) const [checkingHealth, setCheckingHealth] = useState(false) const [autoPauseEnabled, setAutoPauseEnabled] = useState(false) // 配置表单状态 const [targetInput, setTargetInput] = useState(50) const [autoAdd, setAutoAdd] = useState(false) const [minInterval, setMinInterval] = useState(300) const [pollingEnabled, setPollingEnabled] = useState(false) const [pollingInterval, setPollingInterval] = useState(60) const backendUrl = config.s2a.apiBase.replace(/\/api.*$/, '').replace(/:8080/, ':8088') // 获取号池状态 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) } } } 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) } } await fetchPoolStatus() } catch (e) { console.error('刷新统计失败:', e) } setRefreshing(false) }, [backendUrl, fetchPoolStatus]) // 设置目标 const handleSetTarget = async () => { setLoading(true) try { await fetch(`${backendUrl}/api/pool/target`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ target: targetInput, auto_add: autoAdd, min_interval: minInterval, }), }) await fetchPoolStatus() } catch (e) { console.error('设置目标失败:', e) } setLoading(false) } // 控制轮询 const handleTogglePolling = async () => { setLoading(true) try { await fetch(`${backendUrl}/api/pool/polling`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !pollingEnabled, interval: pollingInterval, }), }) setPollingEnabled(!pollingEnabled) await fetchPoolStatus() } catch (e) { console.error('控制轮询失败:', e) } setLoading(false) } // 健康检查 const handleHealthCheck = async (autoPause: boolean = false) => { setCheckingHealth(true) try { await fetch(`${backendUrl}/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 || []) } } setCheckingHealth(false) }, 5000) } catch (e) { console.error('健康检查失败:', e) setCheckingHealth(false) } } // 获取自动补号日志 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 || []) } } } catch (e) { console.error('获取日志失败:', e) } } // 初始化 useEffect(() => { fetchPoolStatus() refreshStats() fetchAutoAddLogs() }, [fetchPoolStatus, refreshStats]) // 计算健康状态 const healthySummary = healthResults.reduce( (acc, r) => { if (r.status === 'active' && !r.error) acc.healthy++ else acc.unhealthy++ return acc }, { healthy: 0, unhealthy: 0 } ) const deficit = poolStatus ? Math.max(0, poolStatus.target - poolStatus.current) : 0 const healthPercent = poolStatus && poolStatus.target > 0 ? Math.min(100, (poolStatus.current / poolStatus.target) * 100) : 0 return (
{/* Header */}

号池监控

实时监控号池状态,自动补号管理

{/* 状态概览卡片 */}
{/* 当前/目标 */}

当前 / 目标

{poolStatus?.current ?? '-'} / {poolStatus?.target ?? '-'}

{/* 需补充 */}

需补充

0 ? 'text-orange-500' : 'text-green-500' }`}> {deficit}

0 ? 'bg-orange-100 dark:bg-orange-900/30' : 'bg-green-100 dark:bg-green-900/30' }`}> {deficit > 0 ? ( ) : ( )}
{deficit > 0 && (

低于目标

)}
{/* 轮询状态 */}

实时监控

{pollingEnabled ? '运行中' : '已停止'}

{pollingEnabled ? ( ) : ( )}
{pollingEnabled && (

每 {pollingInterval} 秒刷新

)}
{/* 自动补号 */}

自动补号

{autoAdd ? '已启用' : '已禁用'}

{/* 配置面板 */}
{/* 目标设置 */} 号池目标设置 setTargetInput(Number(e.target.value))} hint="期望保持的活跃账号数量" />
setAutoAdd(e.target.checked)} className="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" />
setMinInterval(Number(e.target.value))} hint="两次自动补号的最小间隔" disabled={!autoAdd} />
{/* 轮询控制 */} 实时监控设置 setPollingInterval(Number(e.target.value))} hint="自动刷新号池状态的间隔时间" />

监控状态

{pollingEnabled ? '正在实时监控号池状态' : '监控已暂停'}

{/* 健康检查 */} 账号健康检查
{healthResults.length > 0 ? ( <> {/* 统计 */}
健康: {healthySummary.healthy}
异常: {healthySummary.unhealthy}
{/* 结果列表 */}
{healthResults.map((result) => (

{result.email}

ID: {result.account_id}

{result.status}

{result.error && (

{result.error}

)}
))}
) : (

点击"开始检查"验证所有账号状态

)}
{/* S2A 实时统计 */} {stats && ( S2A 实时统计

{stats.total_accounts}

总账号

{stats.normal_accounts}

正常

{stats.error_accounts}

错误

{stats.ratelimit_accounts}

限流

)} {/* 自动补号日志 */} {autoAddLogs.length > 0 && ( 操作日志
{[...autoAddLogs].reverse().slice(0, 20).map((log, idx) => (
{new Date(log.timestamp).toLocaleTimeString()} {log.message}
{log.current} / {log.target}
))}
)}
) }