feat: add Monitor page for S2A pool monitoring, health checks, and auto-add management.
This commit is contained in:
@@ -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' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
target: targetInput,
|
target: targetInput,
|
||||||
auto_add: autoAdd,
|
auto_add: autoAdd,
|
||||||
min_interval: minInterval,
|
min_interval: minInterval,
|
||||||
}),
|
deficit: Math.max(0, targetInput - prev.current),
|
||||||
})
|
} : null)
|
||||||
await fetchPoolStatus()
|
// 刷新统计数据
|
||||||
} catch (e) {
|
await refreshStats()
|
||||||
console.error('设置目标失败:', e)
|
|
||||||
}
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 控制轮询
|
// 控制轮询 - 本地状态管理
|
||||||
const handleTogglePolling = async () => {
|
const handleTogglePolling = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
|
||||||
await requestLocal('/api/pool/polling', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
enabled: !pollingEnabled,
|
|
||||||
interval: pollingInterval,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
setPollingEnabled(!pollingEnabled)
|
setPollingEnabled(!pollingEnabled)
|
||||||
await fetchPoolStatus()
|
setPoolStatus(prev => prev ? {
|
||||||
} catch (e) {
|
...prev,
|
||||||
console.error('控制轮询失败:', e)
|
polling_enabled: !pollingEnabled,
|
||||||
}
|
polling_interval: pollingInterval,
|
||||||
|
} : null)
|
||||||
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) {
|
} catch (e) {
|
||||||
console.error('健康检查失败:', e)
|
console.error('健康检查失败:', e)
|
||||||
|
}
|
||||||
setCheckingHealth(false)
|
setCheckingHealth(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 获取自动补号日志
|
// 获取自动补号日志 - S2A 没有此接口
|
||||||
const fetchAutoAddLogs = async () => {
|
const fetchAutoAddLogs = async () => {
|
||||||
try {
|
// S2A 没有自动补号日志 API,留空
|
||||||
const res = await requestLocal('/api/auto-add/logs')
|
setAutoAddLogs([])
|
||||||
if (res.code === 0) {
|
|
||||||
setAutoAddLogs(res.data || [])
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取日志失败:', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
|
|||||||
Reference in New Issue
Block a user