a5fd3aec90
Thread context.Context from callers instead of creating context.Background() at every call site. Engine stores its lifecycle ctx for use by triggerAlert goroutines. TUI Model carries ctx for store operations in Cmd closures. CLI commands create a root ctx and thread it through openStore, seed functions, and init methods. Only two intentional context.Background() remain in non-root positions: engine constructor default (overridden by Start) and drainWrites (parent already cancelled at shutdown).
159 lines
3.5 KiB
Go
159 lines
3.5 KiB
Go
package monitor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
|
)
|
|
|
|
func (e *Engine) SetActive(active bool) {
|
|
e.activeMu.Lock()
|
|
defer e.activeMu.Unlock()
|
|
if e.isActive != active {
|
|
e.isActive = active
|
|
status := "RESUMED (Active)"
|
|
if !active {
|
|
status = "PAUSED (Passive)"
|
|
}
|
|
e.AddLog(fmt.Sprintf("Engine %s", status))
|
|
}
|
|
}
|
|
|
|
func (e *Engine) IsActive() bool {
|
|
e.activeMu.RLock()
|
|
defer e.activeMu.RUnlock()
|
|
return e.isActive
|
|
}
|
|
|
|
func (e *Engine) GetAllSites() []models.Site {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
sites := make([]models.Site, 0, len(e.liveState))
|
|
for _, s := range e.liveState {
|
|
sites = append(sites, s)
|
|
}
|
|
return sites
|
|
}
|
|
|
|
func (e *Engine) GetLiveState() map[int]models.Site {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
cp := make(map[int]models.Site, len(e.liveState))
|
|
for k, v := range e.liveState {
|
|
cp[k] = v
|
|
}
|
|
return cp
|
|
}
|
|
|
|
func (e *Engine) addToTokenIndex(site models.Site) {
|
|
if site.Type == "push" && site.Token != "" {
|
|
e.tokenIndex[site.Token] = site.ID
|
|
}
|
|
}
|
|
|
|
func (e *Engine) removeFromTokenIndex(id int) {
|
|
for token, sid := range e.tokenIndex {
|
|
if sid == id {
|
|
delete(e.tokenIndex, token)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *Engine) UpdateSiteConfig(cfg models.SiteConfig) {
|
|
e.mu.Lock()
|
|
if existing, ok := e.liveState[cfg.ID]; ok {
|
|
e.removeFromTokenIndex(cfg.ID)
|
|
existing.SiteConfig = cfg
|
|
e.liveState[cfg.ID] = existing
|
|
e.addToTokenIndex(existing)
|
|
}
|
|
e.mu.Unlock()
|
|
|
|
e.signalRecheck(cfg.ID)
|
|
}
|
|
|
|
func (e *Engine) RemoveSite(id int) {
|
|
e.mu.Lock()
|
|
e.removeFromTokenIndex(id)
|
|
delete(e.liveState, id)
|
|
e.mu.Unlock()
|
|
e.removeHistory(id)
|
|
|
|
e.probeResultsMu.Lock()
|
|
delete(e.probeResults, id)
|
|
e.probeResultsMu.Unlock()
|
|
|
|
e.recheckMu.Lock()
|
|
delete(e.recheck, id)
|
|
e.recheckMu.Unlock()
|
|
}
|
|
|
|
func (e *Engine) ToggleSitePause(id int) bool {
|
|
var (
|
|
paused bool
|
|
name string
|
|
)
|
|
_, ok := e.applyState(id, func(s *models.Site) {
|
|
s.Paused = !s.Paused
|
|
paused = s.Paused
|
|
name = s.Name
|
|
})
|
|
if !ok {
|
|
return false
|
|
}
|
|
if paused {
|
|
e.AddLog(fmt.Sprintf("Monitor '%s' paused", name))
|
|
} else {
|
|
e.AddLog(fmt.Sprintf("Monitor '%s' resumed", name))
|
|
}
|
|
return paused
|
|
}
|
|
|
|
// applyState atomically reads, mutates, and writes back the live entry for id.
|
|
// The mutator runs under the engine write lock and receives a pointer to the
|
|
// CURRENT live state, so concurrent config edits, pauses, and heartbeats are
|
|
// never clobbered by a stale snapshot. The mutator must only touch runtime /
|
|
// check-result fields — config fields (Name/URL/Type/Token/Interval/AlertID/…)
|
|
// are owned by UpdateSiteConfig and must not be written here. Returns the
|
|
// post-mutation copy and whether the site still exists.
|
|
func (e *Engine) applyState(id int, mutate func(s *models.Site)) (models.Site, bool) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
cur, ok := e.liveState[id]
|
|
if !ok {
|
|
return models.Site{}, false
|
|
}
|
|
mutate(&cur)
|
|
e.liveState[id] = cur
|
|
return cur, true
|
|
}
|
|
|
|
func (e *Engine) GetDisplayStatus(site models.Site) string {
|
|
if site.Paused {
|
|
return "PAUSED"
|
|
}
|
|
if e.isInMaintenance(site.ID) {
|
|
return "MAINT"
|
|
}
|
|
return string(site.Status)
|
|
}
|
|
|
|
func (e *Engine) GetStateChanges(ctx context.Context, siteID int, limit int) []models.StateChange {
|
|
changes, err := e.db.GetStateChanges(ctx, siteID, limit)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return changes
|
|
}
|
|
|
|
func (e *Engine) GetStateChangesSince(ctx context.Context, siteID int, since time.Time) []models.StateChange {
|
|
changes, err := e.db.GetStateChangesSince(ctx, siteID, since)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return changes
|
|
}
|