docs: update README, API reference, dev-log, project structure with Agent monitoring + quick-alarm + latest fixes

- README.md: React 19, default pwd 123456, Docker Compose deploy, correct module list
- api-reference.md: add Agent monitoring section (report/latest/rules/quick-setup/alerts/tasks/kline)
- dev-log.md: new entry documenting hostname/IP fix, system collector, offline detection, quick alarm
- project-structure.md: add agent/ directory, dev-log.md link, React 19
This commit is contained in:
attacker committed 2026-06-18 16:11:13 +00:00
1 parent c51d0b8b8b
commit 4e3f8cb306
20 files changed
+1315 -1344

No files matched your search

+53 -15
View File
@@ -95,36 +95,74 @@ func (e *AlertEngine) checkResourceAlerts() {
}
}
// 检查主机离线(超过 3 分钟无上报)— 内置规则,无需手动创建
// 检查主机离线(超过 3 分钟无上报)— 内置规则,无需手动创建。
// 以 servers 表为中心:遍历所有服务器,检查其 IP 上是否有 Agent 在最近 3 分钟内上报。
// 如果同一 IP 下有多个 Agent hostname,只要有一个在活跃上报,该服务器就算在线。
func (e *AlertEngine) checkOfflineAlerts() {
offlineThreshold := 3 * time.Minute
// 查所有曾上报的主机
var hostnames []string
e.db.Model(&models.AgentReport{}).Distinct("hostname").Pluck("hostname", &hostnames)
// 获取所有已登记的主机(去重 IP)
var servers []models.Server
e.db.Select("ip, hostname").Group("ip").Find(&servers)
for _, hostname := range hostnames {
var report models.AgentReport
e.db.Where("hostname = ?", hostname).Order("id DESC").First(&report)
if report.ID == 0 {
// 收集所有 agent_reports 中活跃上报的主机名(最近 3 分钟内有上报)
threeMinAgo := time.Now().Add(-offlineThreshold)
var activeHostnames []string
e.db.Model(&models.AgentReport{}).
Where("report_time >= ?", threeMinAgo).
Distinct("hostname").
Pluck("hostname", &activeHostnames)
// 对于每个已登记的服务器,检查是否有活跃 Agent
for _, s := range servers {
if s.IP == "" {
continue
}
isOffline := time.Since(report.ReportTime) > offlineThreshold
// 检查该 IP 对应的服务器是否有活跃 Agent 上报
hasActiveAgent := false
for _, h := range activeHostnames {
// 通过 hostname 精确匹配或 servers 表 IP 匹配
if h == s.Hostname {
hasActiveAgent = true
break
}
// 检查该 hostname 的最新上报——其源 IP 是否匹配此 server IP
// (通过 servers 表中查找 hostname 对应的 IP)
var matchedServer models.Server
e.db.Where("hostname = ?", h).First(&matchedServer)
if matchedServer.ID > 0 && matchedServer.IP == s.IP {
hasActiveAgent = true
break
}
}
// 检查是否有未恢复的离线告警
if hasActiveAgent {
continue // 有活跃 Agent,在线
}
// 该服务器离线:检查是否已有未恢复的离线告警
var existing models.AgentAlert
e.db.Where("hostname = ? AND metric = ? AND resolved = ?",
hostname, "offline", false).First(&existing)
s.Hostname, "offline", false).First(&existing)
if existing.ID == 0 {
// 获取最后一次上报时间计算离线时长
var lastReport models.AgentReport
e.db.Order("id DESC").First(&lastReport)
offlineMinutes := 3.0
if lastReport.ID > 0 {
offlineMinutes = time.Since(lastReport.ReportTime).Minutes()
}
if isOffline && existing.ID == 0 {
alert := models.AgentAlert{
RuleName: "主机离线",
Hostname: hostname,
Hostname: s.Hostname,
Metric: "offline",
Severity: "critical",
Message: fmt.Sprintf("主机 %s 已超过 3 分钟无数据上报,可能已离线", hostname),
Value: time.Since(report.ReportTime).Minutes(),
Message: fmt.Sprintf("主机 %s (%s) 已超过 3 分钟无数据上报,可能已离线", s.Hostname, s.IP),
Value: offlineMinutes,
Threshold: 3,
}
e.db.Create(&alert)