feat: Add a new monitoring dashboard for S2A account pool management with configurable settings and status display.

This commit is contained in:
2026-01-30 12:14:33 +08:00
parent b9c5b42f4e
commit 5987a425ec
2 changed files with 71 additions and 23 deletions

View File

@@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"codex-pool/internal/database" "codex-pool/internal/database"
"codex-pool/internal/logger"
) )
// MonitorSettings 监控设置 // MonitorSettings 监控设置
@@ -87,6 +88,11 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
database.Instance.SetConfig("monitor_polling_enabled", strconv.FormatBool(settings.PollingEnabled)) database.Instance.SetConfig("monitor_polling_enabled", strconv.FormatBool(settings.PollingEnabled))
database.Instance.SetConfig("monitor_polling_interval", strconv.Itoa(settings.PollingInterval)) database.Instance.SetConfig("monitor_polling_interval", strconv.Itoa(settings.PollingInterval))
// 输出日志
logger.Info("监控设置已保存: target="+strconv.Itoa(settings.Target)+
", polling="+strconv.FormatBool(settings.PollingEnabled)+
", interval="+strconv.Itoa(settings.PollingInterval)+"s", "", "monitor")
Success(w, map[string]interface{}{ Success(w, map[string]interface{}{
"message": "设置已保存", "message": "设置已保存",
"settings": settings, "settings": settings,

View File

@@ -12,6 +12,7 @@ import {
AlertTriangle, AlertTriangle,
CheckCircle, CheckCircle,
Clock, Clock,
Save,
} from 'lucide-react' } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common' import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
import type { DashboardStats } from '../types' import type { DashboardStats } from '../types'
@@ -194,6 +195,29 @@ export default function Monitor() {
setLoading(false) setLoading(false)
} }
// 保存轮询设置(不切换状态)
const handleSavePollingSettings = async () => {
setLoading(true)
try {
await fetch('/api/monitor/settings/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
target: targetInput,
auto_add: autoAdd,
min_interval: minInterval,
polling_enabled: pollingEnabled,
polling_interval: pollingInterval,
}),
})
// 重置倒计时
setCountdown(pollingInterval)
} catch (e) {
console.error('保存轮询设置失败:', e)
}
setLoading(false)
}
// 健康检查 - S2A 没有此接口,显示提示 // 健康检查 - S2A 没有此接口,显示提示
const handleHealthCheck = async (_autoPause: boolean = false) => { const handleHealthCheck = async (_autoPause: boolean = false) => {
setCheckingHealth(true) setCheckingHealth(true)
@@ -471,20 +495,27 @@ export default function Monitor() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<Input <div className="w-full">
label="轮询间隔 (秒)" <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
()
</label>
<input
type="number" type="number"
min={10} min={10}
max={300} max={600}
value={pollingInterval} value={pollingInterval}
onChange={(e) => { onChange={(e) => setPollingInterval(Number(e.target.value) || 60)}
const val = parseInt(e.target.value, 10) className="w-full px-3 py-2 text-sm rounded-lg border transition-colors
if (!isNaN(val)) { bg-white dark:bg-slate-800
setPollingInterval(val) text-slate-900 dark:text-slate-100
} border-slate-300 dark:border-slate-600
}} focus:border-blue-500 focus:ring-blue-500
hint="自动刷新号池状态的间隔时间 (10-300秒)" focus:outline-none focus:ring-2"
/> />
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
(10-600)
</p>
</div>
<div className="p-4 rounded-lg bg-slate-50 dark:bg-slate-800/50"> <div className="p-4 rounded-lg bg-slate-50 dark:bg-slate-800/50">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
@@ -496,15 +527,26 @@ export default function Monitor() {
<div className={`status-dot ${pollingEnabled ? 'online' : 'offline'}`} /> <div className={`status-dot ${pollingEnabled ? 'online' : 'offline'}`} />
</div> </div>
</div> </div>
<div className="flex gap-3">
<Button
onClick={handleSavePollingSettings}
loading={loading}
variant="outline"
className="flex-1"
icon={<Save className="h-4 w-4" />}
>
</Button>
<Button <Button
onClick={handleTogglePolling} onClick={handleTogglePolling}
loading={loading} loading={loading}
variant={pollingEnabled ? 'outline' : 'primary'} variant={pollingEnabled ? 'outline' : 'primary'}
className="w-full" className="flex-1"
icon={pollingEnabled ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />} icon={pollingEnabled ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
> >
{pollingEnabled ? '停止监控' : '启动监控'} {pollingEnabled ? '停止监控' : '启动监控'}
</Button> </Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>