feat: Implement a new monitoring system with auto-add capabilities and a dedicated management UI.

This commit is contained in:
2026-01-30 22:26:11 +08:00
parent 10cda012af
commit e666d97eb8
3 changed files with 54 additions and 6 deletions

View File

@@ -34,15 +34,24 @@ func StartAutoAddService() {
logger.Info("自动补号检查器已启动(需在前端开启开关)", "", "auto-add") logger.Info("自动补号检查器已启动(需在前端开启开关)", "", "auto-add")
go func() { go func() {
ticker := time.NewTicker(60 * time.Second) // 每分钟检查一次 // 默认检查间隔 60 秒
defer ticker.Stop() checkInterval := 60
for { for {
// 动态读取检查间隔配置
if database.Instance != nil {
if val, _ := database.Instance.GetConfig("monitor_check_interval"); val != "" {
if v, err := strconv.Atoi(val); err == nil && v >= 10 {
checkInterval = v
}
}
}
select { select {
case <-autoAddStopChan: case <-autoAddStopChan:
logger.Info("自动补号检查器已停止", "", "auto-add") logger.Info("自动补号检查器已停止", "", "auto-add")
return return
case <-ticker.C: case <-time.After(time.Duration(checkInterval) * time.Second):
checkAndAutoAdd() checkAndAutoAdd()
} }
} }
@@ -72,6 +81,7 @@ func checkAndAutoAdd() {
autoAddEnabled = true autoAddEnabled = true
} }
if !autoAddEnabled { if !autoAddEnabled {
// 自动补号未开启,静默返回
return return
} }
@@ -83,7 +93,11 @@ func checkAndAutoAdd() {
} }
} }
if time.Since(lastAutoAddTime).Seconds() < float64(minInterval) { elapsed := time.Since(lastAutoAddTime).Seconds()
if elapsed < float64(minInterval) {
// 距离上次补号时间不足,显示等待信息
remaining := float64(minInterval) - elapsed
logger.Info(fmt.Sprintf("自动补号: 等待中 (还需 %.0f 秒)", remaining), "", "auto-add")
return return
} }
@@ -110,6 +124,7 @@ func checkAndAutoAdd() {
deficit := target - current deficit := target - current
if deficit <= 0 { if deficit <= 0 {
logger.Info(fmt.Sprintf("自动补号: 当前 %d >= 目标 %d, 无需补号", current, target), "", "auto-add")
return return
} }

View File

@@ -14,6 +14,7 @@ type MonitorSettings struct {
Target int `json:"target"` Target int `json:"target"`
AutoAdd bool `json:"auto_add"` AutoAdd bool `json:"auto_add"`
MinInterval int `json:"min_interval"` MinInterval int `json:"min_interval"`
CheckInterval int `json:"check_interval"` // 自动补号检查间隔(秒)
PollingEnabled bool `json:"polling_enabled"` PollingEnabled bool `json:"polling_enabled"`
PollingInterval int `json:"polling_interval"` PollingInterval int `json:"polling_interval"`
} }
@@ -34,6 +35,7 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
Target: 50, Target: 50,
AutoAdd: false, AutoAdd: false,
MinInterval: 300, MinInterval: 300,
CheckInterval: 60,
PollingEnabled: false, PollingEnabled: false,
PollingInterval: 60, PollingInterval: 60,
} }
@@ -51,6 +53,11 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
settings.MinInterval = v settings.MinInterval = v
} }
} }
if val, _ := database.Instance.GetConfig("monitor_check_interval"); val != "" {
if v, err := strconv.Atoi(val); err == nil {
settings.CheckInterval = v
}
}
if val, _ := database.Instance.GetConfig("monitor_polling_enabled"); val == "true" { if val, _ := database.Instance.GetConfig("monitor_polling_enabled"); val == "true" {
settings.PollingEnabled = true settings.PollingEnabled = true
} }
@@ -85,6 +92,7 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
logger.Info("收到保存监控设置请求: target="+strconv.Itoa(settings.Target)+ logger.Info("收到保存监控设置请求: target="+strconv.Itoa(settings.Target)+
", auto_add="+strconv.FormatBool(settings.AutoAdd)+ ", auto_add="+strconv.FormatBool(settings.AutoAdd)+
", check_interval="+strconv.Itoa(settings.CheckInterval)+
", polling="+strconv.FormatBool(settings.PollingEnabled), "", "monitor") ", polling="+strconv.FormatBool(settings.PollingEnabled), "", "monitor")
// 保存到数据库 // 保存到数据库
@@ -98,6 +106,14 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
if err := database.Instance.SetConfig("monitor_min_interval", strconv.Itoa(settings.MinInterval)); err != nil { if err := database.Instance.SetConfig("monitor_min_interval", strconv.Itoa(settings.MinInterval)); err != nil {
saveErrors = append(saveErrors, "min_interval: "+err.Error()) saveErrors = append(saveErrors, "min_interval: "+err.Error())
} }
// 检查间隔最小10秒
checkInterval := settings.CheckInterval
if checkInterval < 10 {
checkInterval = 10
}
if err := database.Instance.SetConfig("monitor_check_interval", strconv.Itoa(checkInterval)); err != nil {
saveErrors = append(saveErrors, "check_interval: "+err.Error())
}
if err := database.Instance.SetConfig("monitor_polling_enabled", strconv.FormatBool(settings.PollingEnabled)); err != nil { if err := database.Instance.SetConfig("monitor_polling_enabled", strconv.FormatBool(settings.PollingEnabled)); err != nil {
saveErrors = append(saveErrors, "polling_enabled: "+err.Error()) saveErrors = append(saveErrors, "polling_enabled: "+err.Error())
} }
@@ -115,8 +131,9 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
// 输出日志 // 输出日志
logger.Success("监控设置已保存: target="+strconv.Itoa(settings.Target)+ logger.Success("监控设置已保存: target="+strconv.Itoa(settings.Target)+
", auto_add="+strconv.FormatBool(settings.AutoAdd)+ ", auto_add="+strconv.FormatBool(settings.AutoAdd)+
", check_interval="+strconv.Itoa(checkInterval)+"s"+
", polling="+strconv.FormatBool(settings.PollingEnabled)+ ", polling="+strconv.FormatBool(settings.PollingEnabled)+
", interval="+strconv.Itoa(settings.PollingInterval)+"s", "", "monitor") ", polling_interval="+strconv.Itoa(settings.PollingInterval)+"s", "", "monitor")
Success(w, map[string]interface{}{ Success(w, map[string]interface{}{
"message": "设置已保存", "message": "设置已保存",

View File

@@ -64,6 +64,7 @@ export default function Monitor() {
const [targetInput, setTargetInput] = useState(50) const [targetInput, setTargetInput] = useState(50)
const [autoAdd, setAutoAdd] = useState(false) const [autoAdd, setAutoAdd] = useState(false)
const [minInterval, setMinInterval] = useState(300) const [minInterval, setMinInterval] = useState(300)
const [checkInterval, setCheckInterval] = useState(60)
const [pollingEnabled, setPollingEnabled] = useState(false) const [pollingEnabled, setPollingEnabled] = useState(false)
const [pollingInterval, setPollingInterval] = useState(60) const [pollingInterval, setPollingInterval] = useState(60)
@@ -125,6 +126,7 @@ export default function Monitor() {
target: targetInput, target: targetInput,
auto_add: autoAdd, auto_add: autoAdd,
min_interval: minInterval, min_interval: minInterval,
check_interval: checkInterval,
polling_enabled: pollingEnabled, polling_enabled: pollingEnabled,
polling_interval: pollingInterval, polling_interval: pollingInterval,
}), }),
@@ -169,6 +171,7 @@ export default function Monitor() {
target: targetInput, target: targetInput,
auto_add: autoAdd, auto_add: autoAdd,
min_interval: minInterval, min_interval: minInterval,
check_interval: checkInterval,
polling_enabled: newPollingEnabled, polling_enabled: newPollingEnabled,
polling_interval: pollingInterval, polling_interval: pollingInterval,
}), }),
@@ -195,6 +198,7 @@ export default function Monitor() {
target: targetInput, target: targetInput,
auto_add: autoAdd, auto_add: autoAdd,
min_interval: minInterval, min_interval: minInterval,
check_interval: checkInterval,
polling_enabled: pollingEnabled, polling_enabled: pollingEnabled,
polling_interval: pollingInterval, polling_interval: pollingInterval,
}), }),
@@ -257,19 +261,21 @@ export default function Monitor() {
const target = s.target || 50 const target = s.target || 50
const autoAddVal = s.auto_add || false const autoAddVal = s.auto_add || false
const minIntervalVal = s.min_interval || 300 const minIntervalVal = s.min_interval || 300
const checkIntervalVal = s.check_interval || 60
const pollingEnabledVal = s.polling_enabled || false const pollingEnabledVal = s.polling_enabled || false
const interval = s.polling_interval || 60 const interval = s.polling_interval || 60
setTargetInput(target) setTargetInput(target)
setAutoAdd(autoAddVal) setAutoAdd(autoAddVal)
setMinInterval(minIntervalVal) setMinInterval(minIntervalVal)
setCheckInterval(checkIntervalVal)
setPollingEnabled(pollingEnabledVal) setPollingEnabled(pollingEnabledVal)
setPollingInterval(interval) setPollingInterval(interval)
savedPollingIntervalRef.current = interval savedPollingIntervalRef.current = interval
setCountdown(interval) setCountdown(interval)
// 返回加载的配置用于后续刷新 // 返回加载的配置用于后续刷新
return { target, autoAdd: autoAddVal, minInterval: minIntervalVal, pollingEnabled: pollingEnabledVal, pollingInterval: interval } return { target, autoAdd: autoAddVal, minInterval: minIntervalVal, checkInterval: checkIntervalVal, pollingEnabled: pollingEnabledVal, pollingInterval: interval }
} }
} }
} catch (e) { } catch (e) {
@@ -515,6 +521,16 @@ export default function Monitor() {
hint="两次自动补号的最小间隔" hint="两次自动补号的最小间隔"
disabled={!autoAdd} disabled={!autoAdd}
/> />
<Input
label="检查间隔 (秒)"
type="number"
min={10}
max={300}
value={checkInterval}
onChange={(e) => setCheckInterval(Number(e.target.value))}
hint="自动补号检查频率 (10-300秒)"
disabled={!autoAdd}
/>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button onClick={handleSetTarget} loading={loading} className="w-full"> <Button onClick={handleSetTarget} loading={loading} className="w-full">