refactor(core): remove store global singleton, thread store explicitly

Remove store.Get()/SetGlobal()/Current. Store is now passed explicitly
to all consumers via constructor parameters and function arguments.

- TUI Model holds store field, set via InitialModel(isAdmin, store)
- monitor.StartEngine(s) and InitHistoryFromStore(s) accept store
- server.Start(cfg, s) closes over store in HTTP handlers
- main.go threads store to SSH server, TUI, monitor, server
- isKeyAllowed receives store as parameter

No more hidden dependency on package-level mutable state in store pkg.
Monitor package still uses package-level state (LiveState, etc.) — will
be encapsulated into Engine struct in Phase 7.
This commit is contained in:
2026-05-15 00:45:07 -04:00
parent d4f4012c8a
commit a6bb9a7aff
9 changed files with 62 additions and 94 deletions
+2 -3
View File
@@ -3,7 +3,6 @@ package tui
import (
"fmt"
"go-upkeep/internal/monitor"
"go-upkeep/internal/store"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
@@ -278,11 +277,11 @@ func (m *Model) submitAlertForm() {
}
if m.editID > 0 {
if err := store.Get().UpdateAlert(m.editID, d.Name, d.AlertType, settings); err != nil {
if err := m.store.UpdateAlert(m.editID, d.Name, d.AlertType, settings); err != nil {
monitor.AddLog("Update alert failed: " + err.Error())
}
} else {
if err := store.Get().AddAlert(d.Name, d.AlertType, settings); err != nil {
if err := m.store.AddAlert(d.Name, d.AlertType, settings); err != nil {
monitor.AddLog("Add alert failed: " + err.Error())
}
}
+8 -11
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"go-upkeep/internal/models"
"go-upkeep/internal/monitor"
"go-upkeep/internal/store"
"net/url"
"strconv"
"strings"
@@ -361,14 +360,12 @@ func (m *Model) initSiteHuhForm() tea.Cmd {
}
alertOpts := []huh.Option[string]{huh.NewOption("None", "0")}
if s := store.Get(); s != nil {
if alerts, err := s.GetAllAlerts(); err == nil {
for _, a := range alerts {
alertOpts = append(alertOpts, huh.NewOption(
fmt.Sprintf("%s (%s)", a.Name, a.Type),
strconv.Itoa(a.ID),
))
}
if alerts, err := m.store.GetAllAlerts(); err == nil {
for _, a := range alerts {
alertOpts = append(alertOpts, huh.NewOption(
fmt.Sprintf("%s (%s)", a.Name, a.Type),
strconv.Itoa(a.ID),
))
}
}
@@ -560,12 +557,12 @@ func (m *Model) submitSiteForm() {
}
if m.editID > 0 {
if err := store.Get().UpdateSite(site); err != nil {
if err := m.store.UpdateSite(site); err != nil {
monitor.AddLog("Update site failed: " + err.Error())
}
monitor.UpdateSiteConfig(site)
} else {
if err := store.Get().AddSite(site); err != nil {
if err := m.store.AddSite(site); err != nil {
monitor.AddLog("Add site failed: " + err.Error())
}
}
+2 -3
View File
@@ -3,7 +3,6 @@ package tui
import (
"fmt"
"go-upkeep/internal/monitor"
"go-upkeep/internal/store"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
@@ -146,11 +145,11 @@ func (m *Model) initUserHuhForm() tea.Cmd {
func (m *Model) submitUserForm() {
d := m.userFormData
if m.editID > 0 {
if err := store.Get().UpdateUser(m.editID, d.Username, d.PublicKey, d.Role); err != nil {
if err := m.store.UpdateUser(m.editID, d.Username, d.PublicKey, d.Role); err != nil {
monitor.AddLog("Update user failed: " + err.Error())
}
} else {
if err := store.Get().AddUser(d.Username, d.PublicKey, d.Role); err != nil {
if err := m.store.AddUser(d.Username, d.PublicKey, d.Role); err != nil {
monitor.AddLog("Add user failed: " + err.Error())
}
}
+26 -33
View File
@@ -68,6 +68,7 @@ type Model struct {
deleteTab int
collapsed map[int]bool
store store.Store
// harmonica animation state
pulseSpring harmonica.Spring
@@ -80,7 +81,7 @@ type Model struct {
users []models.User
}
func InitialModel(isAdmin bool) Model {
func InitialModel(isAdmin bool, s store.Store) Model {
vpLogs := viewport.New(100, 20)
vpLogs.SetContent("Waiting for logs...")
z := zone.New()
@@ -90,6 +91,7 @@ func InitialModel(isAdmin bool) Model {
logViewport: vpLogs,
maxTableRows: 5,
isAdmin: isAdmin,
store: s,
zones: z,
pulseSpring: spring,
collapsed: make(map[int]bool),
@@ -107,25 +109,23 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if keyMsg, ok := msg.(tea.KeyMsg); ok {
switch keyMsg.String() {
case "y", "Y":
if s := store.Get(); s != nil {
switch m.deleteTab {
case 0:
if err := s.DeleteSite(m.deleteID); err != nil {
monitor.AddLog("Delete site failed: " + err.Error())
}
monitor.RemoveSite(m.deleteID)
m.adjustCursor(len(m.sites) - 1)
case 1:
if err := s.DeleteAlert(m.deleteID); err != nil {
monitor.AddLog("Delete alert failed: " + err.Error())
}
m.adjustCursor(len(m.alerts) - 1)
case 3:
if err := s.DeleteUser(m.deleteID); err != nil {
monitor.AddLog("Delete user failed: " + err.Error())
}
m.adjustCursor(len(m.users) - 1)
switch m.deleteTab {
case 0:
if err := m.store.DeleteSite(m.deleteID); err != nil {
monitor.AddLog("Delete site failed: " + err.Error())
}
monitor.RemoveSite(m.deleteID)
m.adjustCursor(len(m.sites) - 1)
case 1:
if err := m.store.DeleteAlert(m.deleteID); err != nil {
monitor.AddLog("Delete alert failed: " + err.Error())
}
m.adjustCursor(len(m.alerts) - 1)
case 3:
if err := m.store.DeleteUser(m.deleteID); err != nil {
monitor.AddLog("Delete user failed: " + err.Error())
}
m.adjustCursor(len(m.users) - 1)
}
m.refreshData()
m.state = stateDashboard
@@ -319,9 +319,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
site := m.sites[m.cursor]
monitor.ToggleSitePause(site.ID)
site.Paused = !site.Paused
if s := store.Get(); s != nil {
_ = s.UpdateSitePaused(site.ID, site.Paused)
}
_ = m.store.UpdateSitePaused(site.ID, site.Paused)
m.refreshData()
}
case "d", "backspace":
@@ -470,23 +468,18 @@ func (m *Model) refreshData() {
}
ordered = append(ordered, ungrouped...)
m.sites = ordered
if s := store.Get(); s != nil {
if alerts, err := s.GetAllAlerts(); err == nil {
m.alerts = alerts
}
if m.isAdmin {
if users, err := s.GetAllUsers(); err == nil {
m.users = users
}
if alerts, err := m.store.GetAllAlerts(); err == nil {
m.alerts = alerts
}
if m.isAdmin {
if users, err := m.store.GetAllUsers(); err == nil {
m.users = users
}
}
m.logViewport.SetContent(strings.Join(monitor.GetLogs(), "\n"))
}
func (m *Model) submitForm() {
if store.Get() == nil {
return
}
switch m.state {
case stateFormSite:
if m.siteFormData != nil {