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">
type="number" ()
min={10} </label>
max={300} <input
value={pollingInterval} type="number"
onChange={(e) => { min={10}
const val = parseInt(e.target.value, 10) max={600}
if (!isNaN(val)) { value={pollingInterval}
setPollingInterval(val) onChange={(e) => setPollingInterval(Number(e.target.value) || 60)}
} className="w-full px-3 py-2 text-sm rounded-lg border transition-colors
}} bg-white dark:bg-slate-800
hint="自动刷新号池状态的间隔时间 (10-300秒)" text-slate-900 dark:text-slate-100
/> border-slate-300 dark:border-slate-600
focus:border-blue-500 focus:ring-blue-500
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>
<Button <div className="flex gap-3">
onClick={handleTogglePolling} <Button
loading={loading} onClick={handleSavePollingSettings}
variant={pollingEnabled ? 'outline' : 'primary'} loading={loading}
className="w-full" variant="outline"
icon={pollingEnabled ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />} className="flex-1"
> icon={<Save className="h-4 w-4" />}
{pollingEnabled ? '停止监控' : '启动监控'} >
</Button>
</Button>
<Button
onClick={handleTogglePolling}
loading={loading}
variant={pollingEnabled ? 'outline' : 'primary'}
className="flex-1"
icon={pollingEnabled ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
>
{pollingEnabled ? '停止监控' : '启动监控'}
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>