release: 2026.05.1 — distributed probing, config-as-code, TUI polish #15

Merged
lerko merged 47 commits from develop into main 2026-05-16 20:03:54 +00:00
10 changed files with 185 additions and 93 deletions
Showing only changes of commit d4f4012c8a - Show all commits
+7 -3
View File
@@ -174,7 +174,8 @@ func startSSHServer(port int) {
} }
func seedDemoData(s store.Store) { func seedDemoData(s store.Store) {
if existing := s.GetSites(); len(existing) > 0 { existing, _ := s.GetSites()
if len(existing) > 0 {
return return
} }
fmt.Println("Seeding demo data...") fmt.Println("Seeding demo data...")
@@ -187,7 +188,7 @@ func seedDemoData(s store.Store) {
"from": "oncall@example.com", "to": "team@example.com", "from": "oncall@example.com", "to": "team@example.com",
}) })
alerts := s.GetAllAlerts() alerts, _ := s.GetAllAlerts()
alertID := 0 alertID := 0
if len(alerts) > 0 { if len(alerts) > 0 {
alertID = alerts[0].ID alertID = alerts[0].ID
@@ -206,7 +207,10 @@ func seedDemoData(s store.Store) {
} }
func isKeyAllowed(incomingKey ssh.PublicKey) bool { func isKeyAllowed(incomingKey ssh.PublicKey) bool {
users := store.Get().GetAllUsers() users, err := store.Get().GetAllUsers()
if err != nil {
return false
}
for _, u := range users { for _, u := range users {
allowedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(u.PublicKey)) allowedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(u.PublicKey))
if err != nil { if err != nil {
+6 -2
View File
@@ -25,7 +25,11 @@ func InitHistoryFromStore() {
if s == nil { if s == nil {
return return
} }
all := s.LoadAllHistory(maxHistoryLen) all, err := s.LoadAllHistory(maxHistoryLen)
if err != nil {
AddLog("Failed to load check history: " + err.Error())
return
}
historyMu.Lock() historyMu.Lock()
defer historyMu.Unlock() defer historyMu.Unlock()
for siteID, records := range all { for siteID, records := range all {
@@ -71,7 +75,7 @@ func RecordCheck(siteID int, latency time.Duration, isUp bool) {
} }
if s := store.Get(); s != nil { if s := store.Get(); s != nil {
go s.SaveCheck(siteID, latency.Nanoseconds(), isUp) go func() { _ = s.SaveCheck(siteID, latency.Nanoseconds(), isUp) }()
} }
} }
+8 -3
View File
@@ -128,7 +128,12 @@ func StartEngine() {
continue continue
} }
sites := s_instance.GetSites() sites, err := s_instance.GetSites()
if err != nil {
AddLog(fmt.Sprintf("Failed to load sites: %v", err))
time.Sleep(5 * time.Second)
continue
}
for _, s := range sites { for _, s := range sites {
Mutex.RLock() Mutex.RLock()
_, exists := LiveState[s.ID] _, exists := LiveState[s.ID]
@@ -406,8 +411,8 @@ func triggerAlert(alertID int, title, message string) {
if s_instance == nil { if s_instance == nil {
return return
} }
cfg, ok := s_instance.GetAlert(alertID) cfg, err := s_instance.GetAlert(alertID)
if !ok { if err != nil {
return return
} }
provider := alert.GetProvider(cfg) provider := alert.GetProvider(cfg)
+6 -1
View File
@@ -185,7 +185,12 @@ func Start(cfg ServerConfig) {
http.Error(w, "Unauthorized: UPKEEP_CLUSTER_SECRET required", 401) http.Error(w, "Unauthorized: UPKEEP_CLUSTER_SECRET required", 401)
return return
} }
data := store.Get().ExportData() data, err := store.Get().ExportData()
if err != nil {
log.Printf("Export failed: %v", err)
http.Error(w, "Export failed", 500)
return
}
json.NewEncoder(w).Encode(data) json.NewEncoder(w).Encode(data)
}) })
+97 -49
View File
@@ -48,7 +48,7 @@ func (s *SQLStore) Init() error {
return nil return nil
} }
func (s *SQLStore) GetSites() []models.Site { func (s *SQLStore) GetSites() ([]models.Site, error) {
bf := s.dialect.BoolFalse() bf := s.dialect.BoolFalse()
query := fmt.Sprintf( query := fmt.Sprintf(
"SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s) FROM sites", "SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s) FROM sites",
@@ -56,107 +56,132 @@ func (s *SQLStore) GetSites() []models.Site {
) )
rows, err := s.db.Query(query) rows, err := s.db.Query(query)
if err != nil { if err != nil {
return []models.Site{} return nil, err
} }
defer rows.Close() defer rows.Close()
var sites []models.Site var sites []models.Site
for rows.Next() { for rows.Next() {
var st models.Site var st models.Site
rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID, if err := rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID,
&st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout, &st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout,
&st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType, &st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType,
&st.DNSServer, &st.IgnoreTLS, &st.Paused) &st.DNSServer, &st.IgnoreTLS, &st.Paused); err != nil {
return sites, err
}
sites = append(sites, st) sites = append(sites, st)
} }
return sites return sites, rows.Err()
} }
func (s *SQLStore) AddSite(site models.Site) { func (s *SQLStore) AddSite(site models.Site) error {
token := "" token := ""
if site.Type == "push" { if site.Type == "push" {
token = generateToken() token = generateToken()
} }
s.db.Exec(s.q("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), _, err := s.db.Exec(s.q("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries,
site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused) site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused)
return err
} }
func (s *SQLStore) UpdateSite(site models.Site) { func (s *SQLStore) UpdateSite(site models.Site) error {
var existingToken string var existingToken string
s.db.QueryRow(s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken) s.db.QueryRow(s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken)
if site.Type == "push" && existingToken == "" { if site.Type == "push" && existingToken == "" {
existingToken = generateToken() existingToken = generateToken()
} }
s.db.Exec(s.q("UPDATE sites SET name=?, url=?, type=?, token=?, interval=?, alert_id=?, check_ssl=?, threshold=?, max_retries=?, hostname=?, port=?, timeout=?, method=?, description=?, parent_id=?, accepted_codes=?, dns_resolve_type=?, dns_server=?, ignore_tls=?, paused=? WHERE id=?"), _, err := s.db.Exec(s.q("UPDATE sites SET name=?, url=?, type=?, token=?, interval=?, alert_id=?, check_ssl=?, threshold=?, max_retries=?, hostname=?, port=?, timeout=?, method=?, description=?, parent_id=?, accepted_codes=?, dns_resolve_type=?, dns_server=?, ignore_tls=?, paused=? WHERE id=?"),
site.Name, site.URL, site.Type, existingToken, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, site.Name, site.URL, site.Type, existingToken, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries,
site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.ID) site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.ID)
return err
} }
func (s *SQLStore) UpdateSitePaused(id int, paused bool) { func (s *SQLStore) UpdateSitePaused(id int, paused bool) error {
s.db.Exec(s.q("UPDATE sites SET paused=? WHERE id=?"), paused, id) _, err := s.db.Exec(s.q("UPDATE sites SET paused=? WHERE id=?"), paused, id)
return err
} }
func (s *SQLStore) DeleteSite(id int) { func (s *SQLStore) DeleteSite(id int) error {
s.db.Exec(s.q("DELETE FROM sites WHERE id=?"), id) _, err := s.db.Exec(s.q("DELETE FROM sites WHERE id=?"), id)
if err != nil {
return err
}
s.dialect.ResetSequenceOnEmpty(s.db, "sites") s.dialect.ResetSequenceOnEmpty(s.db, "sites")
return nil
} }
func (s *SQLStore) GetAllAlerts() []models.AlertConfig { func (s *SQLStore) GetAllAlerts() ([]models.AlertConfig, error) {
rows, err := s.db.Query("SELECT id, name, type, settings FROM alerts") rows, err := s.db.Query("SELECT id, name, type, settings FROM alerts")
if err != nil { if err != nil {
return []models.AlertConfig{} return nil, err
} }
defer rows.Close() defer rows.Close()
var alerts []models.AlertConfig var alerts []models.AlertConfig
for rows.Next() { for rows.Next() {
var a models.AlertConfig var a models.AlertConfig
var settingsJSON string var settingsJSON string
rows.Scan(&a.ID, &a.Name, &a.Type, &settingsJSON) if err := rows.Scan(&a.ID, &a.Name, &a.Type, &settingsJSON); err != nil {
return alerts, err
}
json.Unmarshal([]byte(settingsJSON), &a.Settings) json.Unmarshal([]byte(settingsJSON), &a.Settings)
alerts = append(alerts, a) alerts = append(alerts, a)
} }
return alerts return alerts, rows.Err()
} }
func (s *SQLStore) GetAlert(id int) (models.AlertConfig, bool) { func (s *SQLStore) GetAlert(id int) (models.AlertConfig, error) {
var a models.AlertConfig var a models.AlertConfig
var settingsJSON string var settingsJSON string
err := s.db.QueryRow(s.q("SELECT id, name, type, settings FROM alerts WHERE id = ?"), id).Scan(&a.ID, &a.Name, &a.Type, &settingsJSON) err := s.db.QueryRow(s.q("SELECT id, name, type, settings FROM alerts WHERE id = ?"), id).Scan(&a.ID, &a.Name, &a.Type, &settingsJSON)
if err != nil { if err != nil {
return a, false return a, err
} }
json.Unmarshal([]byte(settingsJSON), &a.Settings) json.Unmarshal([]byte(settingsJSON), &a.Settings)
return a, true return a, nil
} }
func (s *SQLStore) AddAlert(name, aType string, settings map[string]string) { func (s *SQLStore) AddAlert(name, aType string, settings map[string]string) error {
jsonBytes, _ := json.Marshal(settings) jsonBytes, err := json.Marshal(settings)
s.db.Exec(s.q("INSERT INTO alerts (name, type, settings) VALUES (?, ?, ?)"), name, aType, string(jsonBytes)) if err != nil {
return err
}
_, err = s.db.Exec(s.q("INSERT INTO alerts (name, type, settings) VALUES (?, ?, ?)"), name, aType, string(jsonBytes))
return err
} }
func (s *SQLStore) UpdateAlert(id int, name, aType string, settings map[string]string) { func (s *SQLStore) UpdateAlert(id int, name, aType string, settings map[string]string) error {
jsonBytes, _ := json.Marshal(settings) jsonBytes, err := json.Marshal(settings)
s.db.Exec(s.q("UPDATE alerts SET name=?, type=?, settings=? WHERE id=?"), name, aType, string(jsonBytes), id) if err != nil {
return err
}
_, err = s.db.Exec(s.q("UPDATE alerts SET name=?, type=?, settings=? WHERE id=?"), name, aType, string(jsonBytes), id)
return err
} }
func (s *SQLStore) DeleteAlert(id int) { func (s *SQLStore) DeleteAlert(id int) error {
s.db.Exec(s.q("DELETE FROM alerts WHERE id=?"), id) _, err := s.db.Exec(s.q("DELETE FROM alerts WHERE id=?"), id)
if err != nil {
return err
}
s.dialect.ResetSequenceOnEmpty(s.db, "alerts") s.dialect.ResetSequenceOnEmpty(s.db, "alerts")
return nil
} }
func (s *SQLStore) GetAllUsers() []models.User { func (s *SQLStore) GetAllUsers() ([]models.User, error) {
rows, err := s.db.Query("SELECT id, username, public_key, role FROM users") rows, err := s.db.Query("SELECT id, username, public_key, role FROM users")
if err != nil { if err != nil {
return []models.User{} return nil, err
} }
defer rows.Close() defer rows.Close()
var users []models.User var users []models.User
for rows.Next() { for rows.Next() {
var u models.User var u models.User
rows.Scan(&u.ID, &u.Username, &u.PublicKey, &u.Role) if err := rows.Scan(&u.ID, &u.Username, &u.PublicKey, &u.Role); err != nil {
return users, err
}
users = append(users, u) users = append(users, u)
} }
return users return users, rows.Err()
} }
func (s *SQLStore) AddUser(username, publicKey, role string) error { func (s *SQLStore) AddUser(username, publicKey, role string) error {
@@ -174,14 +199,18 @@ func (s *SQLStore) DeleteUser(id int) error {
return err return err
} }
func (s *SQLStore) SaveCheck(siteID int, latencyNs int64, isUp bool) { func (s *SQLStore) SaveCheck(siteID int, latencyNs int64, isUp bool) error {
s.db.Exec(s.q("INSERT INTO check_history (site_id, latency_ns, is_up) VALUES (?, ?, ?)"), siteID, latencyNs, isUp) _, err := s.db.Exec(s.q("INSERT INTO check_history (site_id, latency_ns, is_up) VALUES (?, ?, ?)"), siteID, latencyNs, isUp)
s.db.Exec(s.q(`DELETE FROM check_history WHERE site_id = ? AND id NOT IN ( if err != nil {
return err
}
_, err = s.db.Exec(s.q(`DELETE FROM check_history WHERE site_id = ? AND id NOT IN (
SELECT id FROM check_history WHERE site_id = ? ORDER BY checked_at DESC LIMIT 1000 SELECT id FROM check_history WHERE site_id = ? ORDER BY checked_at DESC LIMIT 1000
)`), siteID, siteID) )`), siteID, siteID)
return err
} }
func (s *SQLStore) LoadAllHistory(limit int) map[int][]models.CheckRecord { func (s *SQLStore) LoadAllHistory(limit int) (map[int][]models.CheckRecord, error) {
result := make(map[int][]models.CheckRecord) result := make(map[int][]models.CheckRecord)
rows, err := s.db.Query(s.q(` rows, err := s.db.Query(s.q(`
SELECT site_id, latency_ns, is_up FROM ( SELECT site_id, latency_ns, is_up FROM (
@@ -190,12 +219,14 @@ func (s *SQLStore) LoadAllHistory(limit int) map[int][]models.CheckRecord {
FROM check_history FROM check_history
) sub WHERE rn <= ?`), limit) ) sub WHERE rn <= ?`), limit)
if err != nil { if err != nil {
return result return result, err
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var r models.CheckRecord var r models.CheckRecord
rows.Scan(&r.SiteID, &r.LatencyNs, &r.IsUp) if err := rows.Scan(&r.SiteID, &r.LatencyNs, &r.IsUp); err != nil {
return result, err
}
result[r.SiteID] = append(result[r.SiteID], r) result[r.SiteID] = append(result[r.SiteID], r)
} }
for id, records := range result { for id, records := range result {
@@ -204,15 +235,23 @@ func (s *SQLStore) LoadAllHistory(limit int) map[int][]models.CheckRecord {
} }
result[id] = records result[id] = records
} }
return result return result, rows.Err()
} }
func (s *SQLStore) ExportData() models.Backup { func (s *SQLStore) ExportData() (models.Backup, error) {
return models.Backup{ sites, err := s.GetSites()
Sites: s.GetSites(), if err != nil {
Alerts: s.GetAllAlerts(), return models.Backup{}, err
Users: s.GetAllUsers(),
} }
alerts, err := s.GetAllAlerts()
if err != nil {
return models.Backup{}, err
}
users, err := s.GetAllUsers()
if err != nil {
return models.Backup{}, err
}
return models.Backup{Sites: sites, Alerts: alerts, Users: users}, nil
} }
func (s *SQLStore) ImportData(data models.Backup) error { func (s *SQLStore) ImportData(data models.Backup) error {
@@ -225,16 +264,25 @@ func (s *SQLStore) ImportData(data models.Backup) error {
s.dialect.ImportWipe(tx) s.dialect.ImportWipe(tx)
for _, u := range data.Users { for _, u := range data.Users {
tx.Exec(s.q("INSERT INTO users (username, public_key, role) VALUES (?, ?, ?)"), u.Username, u.PublicKey, u.Role) if _, err := tx.Exec(s.q("INSERT INTO users (username, public_key, role) VALUES (?, ?, ?)"), u.Username, u.PublicKey, u.Role); err != nil {
return err
}
} }
for _, a := range data.Alerts { for _, a := range data.Alerts {
jsonBytes, _ := json.Marshal(a.Settings) jsonBytes, err := json.Marshal(a.Settings)
tx.Exec(s.q("INSERT INTO alerts (id, name, type, settings) VALUES (?, ?, ?, ?)"), a.ID, a.Name, a.Type, string(jsonBytes)) if err != nil {
return err
}
if _, err := tx.Exec(s.q("INSERT INTO alerts (id, name, type, settings) VALUES (?, ?, ?, ?)"), a.ID, a.Name, a.Type, string(jsonBytes)); err != nil {
return err
}
} }
for _, st := range data.Sites { for _, st := range data.Sites {
tx.Exec(s.q("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), if _, err := tx.Exec(s.q("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
st.ID, st.Name, st.URL, st.Type, st.Token, st.Interval, st.AlertID, st.CheckSSL, st.ExpiryThreshold, st.MaxRetries, st.ID, st.Name, st.URL, st.Type, st.Token, st.Interval, st.AlertID, st.CheckSSL, st.ExpiryThreshold, st.MaxRetries,
st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS, st.Paused) st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS, st.Paused); err != nil {
return err
}
} }
s.dialect.ImportResetSequences(tx) s.dialect.ImportResetSequences(tx)
+14 -14
View File
@@ -8,31 +8,31 @@ type Store interface {
Init() error Init() error
// Sites // Sites
GetSites() []models.Site GetSites() ([]models.Site, error)
AddSite(site models.Site) AddSite(site models.Site) error
UpdateSite(site models.Site) UpdateSite(site models.Site) error
UpdateSitePaused(id int, paused bool) UpdateSitePaused(id int, paused bool) error
DeleteSite(id int) DeleteSite(id int) error
// Alerts // Alerts
GetAllAlerts() []models.AlertConfig GetAllAlerts() ([]models.AlertConfig, error)
GetAlert(id int) (models.AlertConfig, bool) GetAlert(id int) (models.AlertConfig, error)
AddAlert(name, aType string, settings map[string]string) AddAlert(name, aType string, settings map[string]string) error
UpdateAlert(id int, name, aType string, settings map[string]string) UpdateAlert(id int, name, aType string, settings map[string]string) error
DeleteAlert(id int) DeleteAlert(id int) error
// Users // Users
GetAllUsers() []models.User GetAllUsers() ([]models.User, error)
AddUser(username, publicKey, role string) error AddUser(username, publicKey, role string) error
UpdateUser(id int, username, publicKey, role string) error UpdateUser(id int, username, publicKey, role string) error
DeleteUser(id int) error DeleteUser(id int) error
// History // History
SaveCheck(siteID int, latencyNs int64, isUp bool) SaveCheck(siteID int, latencyNs int64, isUp bool) error
LoadAllHistory(limit int) map[int][]models.CheckRecord LoadAllHistory(limit int) (map[int][]models.CheckRecord, error)
// Backup & Restore // Backup & Restore
ExportData() models.Backup ExportData() (models.Backup, error)
ImportData(data models.Backup) error ImportData(data models.Backup) error
} }
+7 -2
View File
@@ -2,6 +2,7 @@ package tui
import ( import (
"fmt" "fmt"
"go-upkeep/internal/monitor"
"go-upkeep/internal/store" "go-upkeep/internal/store"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@@ -277,9 +278,13 @@ func (m *Model) submitAlertForm() {
} }
if m.editID > 0 { if m.editID > 0 {
store.Get().UpdateAlert(m.editID, d.Name, d.AlertType, settings) if err := store.Get().UpdateAlert(m.editID, d.Name, d.AlertType, settings); err != nil {
monitor.AddLog("Update alert failed: " + err.Error())
}
} else { } else {
store.Get().AddAlert(d.Name, d.AlertType, settings) if err := store.Get().AddAlert(d.Name, d.AlertType, settings); err != nil {
monitor.AddLog("Add alert failed: " + err.Error())
}
} }
m.state = stateDashboard m.state = stateDashboard
} }
+14 -8
View File
@@ -361,12 +361,14 @@ func (m *Model) initSiteHuhForm() tea.Cmd {
} }
alertOpts := []huh.Option[string]{huh.NewOption("None", "0")} alertOpts := []huh.Option[string]{huh.NewOption("None", "0")}
if store.Get() != nil { if s := store.Get(); s != nil {
for _, a := range store.Get().GetAllAlerts() { if alerts, err := s.GetAllAlerts(); err == nil {
alertOpts = append(alertOpts, huh.NewOption( for _, a := range alerts {
fmt.Sprintf("%s (%s)", a.Name, a.Type), alertOpts = append(alertOpts, huh.NewOption(
strconv.Itoa(a.ID), fmt.Sprintf("%s (%s)", a.Name, a.Type),
)) strconv.Itoa(a.ID),
))
}
} }
} }
@@ -558,10 +560,14 @@ func (m *Model) submitSiteForm() {
} }
if m.editID > 0 { if m.editID > 0 {
store.Get().UpdateSite(site) if err := store.Get().UpdateSite(site); err != nil {
monitor.AddLog("Update site failed: " + err.Error())
}
monitor.UpdateSiteConfig(site) monitor.UpdateSiteConfig(site)
} else { } else {
store.Get().AddSite(site) if err := store.Get().AddSite(site); err != nil {
monitor.AddLog("Add site failed: " + err.Error())
}
} }
m.state = stateDashboard m.state = stateDashboard
} }
+7 -2
View File
@@ -2,6 +2,7 @@ package tui
import ( import (
"fmt" "fmt"
"go-upkeep/internal/monitor"
"go-upkeep/internal/store" "go-upkeep/internal/store"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@@ -145,9 +146,13 @@ func (m *Model) initUserHuhForm() tea.Cmd {
func (m *Model) submitUserForm() { func (m *Model) submitUserForm() {
d := m.userFormData d := m.userFormData
if m.editID > 0 { if m.editID > 0 {
store.Get().UpdateUser(m.editID, d.Username, d.PublicKey, d.Role) if err := store.Get().UpdateUser(m.editID, d.Username, d.PublicKey, d.Role); err != nil {
monitor.AddLog("Update user failed: " + err.Error())
}
} else { } else {
store.Get().AddUser(d.Username, d.PublicKey, d.Role) if err := store.Get().AddUser(d.Username, d.PublicKey, d.Role); err != nil {
monitor.AddLog("Add user failed: " + err.Error())
}
} }
m.state = stateUsers m.state = stateUsers
} }
+19 -9
View File
@@ -107,17 +107,23 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if keyMsg, ok := msg.(tea.KeyMsg); ok { if keyMsg, ok := msg.(tea.KeyMsg); ok {
switch keyMsg.String() { switch keyMsg.String() {
case "y", "Y": case "y", "Y":
if store.Get() != nil { if s := store.Get(); s != nil {
switch m.deleteTab { switch m.deleteTab {
case 0: case 0:
store.Get().DeleteSite(m.deleteID) if err := s.DeleteSite(m.deleteID); err != nil {
monitor.AddLog("Delete site failed: " + err.Error())
}
monitor.RemoveSite(m.deleteID) monitor.RemoveSite(m.deleteID)
m.adjustCursor(len(m.sites) - 1) m.adjustCursor(len(m.sites) - 1)
case 1: case 1:
store.Get().DeleteAlert(m.deleteID) if err := s.DeleteAlert(m.deleteID); err != nil {
monitor.AddLog("Delete alert failed: " + err.Error())
}
m.adjustCursor(len(m.alerts) - 1) m.adjustCursor(len(m.alerts) - 1)
case 3: case 3:
store.Get().DeleteUser(m.deleteID) if err := s.DeleteUser(m.deleteID); err != nil {
monitor.AddLog("Delete user failed: " + err.Error())
}
m.adjustCursor(len(m.users) - 1) m.adjustCursor(len(m.users) - 1)
} }
} }
@@ -313,8 +319,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
site := m.sites[m.cursor] site := m.sites[m.cursor]
monitor.ToggleSitePause(site.ID) monitor.ToggleSitePause(site.ID)
site.Paused = !site.Paused site.Paused = !site.Paused
if store.Get() != nil { if s := store.Get(); s != nil {
store.Get().UpdateSitePaused(site.ID, site.Paused) _ = s.UpdateSitePaused(site.ID, site.Paused)
} }
m.refreshData() m.refreshData()
} }
@@ -464,10 +470,14 @@ func (m *Model) refreshData() {
} }
ordered = append(ordered, ungrouped...) ordered = append(ordered, ungrouped...)
m.sites = ordered m.sites = ordered
if store.Get() != nil { if s := store.Get(); s != nil {
m.alerts = store.Get().GetAllAlerts() if alerts, err := s.GetAllAlerts(); err == nil {
m.alerts = alerts
}
if m.isAdmin { if m.isAdmin {
m.users = store.Get().GetAllUsers() if users, err := s.GetAllUsers(); err == nil {
m.users = users
}
} }
} }
m.logViewport.SetContent(strings.Join(monitor.GetLogs(), "\n")) m.logViewport.SetContent(strings.Join(monitor.GetLogs(), "\n"))