diff --git a/cmd/goupkeep/main.go b/cmd/goupkeep/main.go index 0ae9cb0..26c01b3 100644 --- a/cmd/goupkeep/main.go +++ b/cmd/goupkeep/main.go @@ -112,6 +112,7 @@ func main() { fmt.Printf("Imported %d monitors and %d alerts from Uptime Kuma v%s\n", len(backup.Sites), len(backup.Alerts), kb.Version) } + monitor.InitHistoryFromStore() monitor.StartEngine() server.Start(server.ServerConfig{ diff --git a/internal/models/models.go b/internal/models/models.go index 467fd33..cdf4c68 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -50,7 +50,13 @@ type User struct { Role string } -// Phase 5: Backup Structure +type CheckRecord struct { + SiteID int + LatencyNs int64 + IsUp bool + CheckedAt time.Time +} + type Backup struct { Sites []Site `json:"sites"` Alerts []AlertConfig `json:"alerts"` diff --git a/internal/monitor/history.go b/internal/monitor/history.go index c6fbcbe..8642255 100644 --- a/internal/monitor/history.go +++ b/internal/monitor/history.go @@ -1,6 +1,7 @@ package monitor import ( + "go-upkeep/internal/store" "sync" "time" ) @@ -19,6 +20,31 @@ var ( historyMu sync.RWMutex ) +func InitHistoryFromStore() { + s := store.Get() + if s == nil { + return + } + all := s.LoadAllHistory(maxHistoryLen) + historyMu.Lock() + defer historyMu.Unlock() + for siteID, records := range all { + h := &SiteHistory{} + for _, r := range records { + h.TotalChecks++ + if r.IsUp { + h.UpChecks++ + } + h.Latencies = append(h.Latencies, time.Duration(r.LatencyNs)) + h.Statuses = append(h.Statuses, r.IsUp) + } + histories[siteID] = h + } + if len(all) > 0 { + AddLog("Loaded check history from database") + } +} + func RecordCheck(siteID int, latency time.Duration, isUp bool) { historyMu.Lock() defer historyMu.Unlock() @@ -43,6 +69,10 @@ func RecordCheck(siteID int, latency time.Duration, isUp bool) { if len(h.Statuses) > maxHistoryLen { h.Statuses = h.Statuses[len(h.Statuses)-maxHistoryLen:] } + + if s := store.Get(); s != nil { + go s.SaveCheck(siteID, latency.Nanoseconds(), isUp) + } } func GetHistory(siteID int) (SiteHistory, bool) { diff --git a/internal/monitor/monitor.go b/internal/monitor/monitor.go index 76a55bb..1bb02fe 100644 --- a/internal/monitor/monitor.go +++ b/internal/monitor/monitor.go @@ -243,7 +243,7 @@ func checkByID(id int) { case "dns": checkDNS(site) case "group": - // groups don't perform checks + checkGroup(site) } } @@ -437,6 +437,44 @@ func checkPort(site models.Site) { handleStatusChange(updatedSite, "UP", 0, latency) } +func checkGroup(site models.Site) { + Mutex.RLock() + status := "UP" + hasChildren := false + allPaused := true + for _, child := range LiveState { + if child.ParentID != site.ID || child.Type == "group" { + continue + } + hasChildren = true + if !child.Paused { + allPaused = false + } + if child.Paused { + continue + } + if child.Status == "DOWN" || child.Status == "SSL EXP" { + status = "DOWN" + } else if child.Status == "PENDING" && status != "DOWN" { + status = "PENDING" + } + } + Mutex.RUnlock() + + if !hasChildren { + status = "PENDING" + } + + Mutex.Lock() + s := LiveState[site.ID] + s.Status = status + if hasChildren && allPaused { + s.Paused = true + } + LiveState[site.ID] = s + Mutex.Unlock() +} + func checkDNS(site models.Site) { host := site.Hostname if host == "" { diff --git a/internal/server/server.go b/internal/server/server.go index 89460ed..f814cc3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -185,6 +185,12 @@ func renderStatusPage(w http.ResponseWriter, title string) {