refactor(models): typed Status constants with IsBroken() predicate

Replace ~150 bare status string comparisons with typed models.Status
constants (StatusUp, StatusDown, StatusPending, StatusLate, StatusStale,
StatusSSLExp). Single IsBroken() method replaces the duplicated
isBroken lambda in monitor.go and isDown function in sla.go.

Adding a new status value (e.g. DEGRADED) now requires one constant
definition instead of grep-and-pray across 16 files.

CheckResult.Status stays string — the checker is the boundary between
raw protocol results and typed status. Cast happens at the edge in
handleStatusChange.
This commit is contained in:
2026-06-11 15:56:51 -04:00
parent c3ae0bd80a
commit f00acbc280
16 changed files with 152 additions and 137 deletions
+9 -9
View File
@@ -143,16 +143,16 @@ func (m Model) fmtRetries(site models.Site) string {
dispCount = site.MaxRetries
}
s := fmt.Sprintf("%d/%d", dispCount, site.MaxRetries)
if site.Status == "DOWN" {
if site.Status == models.StatusDown {
return m.st.dangerStyle.Render(s)
}
if site.Status == "UP" && site.FailureCount > 0 {
if site.Status == models.StatusUp && site.FailureCount > 0 {
return m.st.warnStyle.Render(s)
}
return s
}
func (m Model) fmtStatus(status string, paused bool, inMaint bool) string {
func (m Model) fmtStatus(status models.Status, paused bool, inMaint bool) string {
if paused {
return m.st.warnStyle.Render("◇ PAUSED")
}
@@ -160,18 +160,18 @@ func (m Model) fmtStatus(status string, paused bool, inMaint bool) string {
return m.st.maintStyle.Render("◼ MAINT")
}
switch status {
case "DOWN":
case models.StatusDown:
return m.st.dangerStyle.Render("▼ DOWN")
case "SSL EXP":
case models.StatusSSLExp:
return m.st.dangerStyle.Render("▼ SSL EXP")
case "LATE":
case models.StatusLate:
return m.st.warnStyle.Render("◆ LATE")
case "STALE":
case models.StatusStale:
return m.st.staleStyle.Render("◆ STALE")
case "PENDING":
case models.StatusPending:
return m.st.subtleStyle.Render("○ PENDING")
default:
return m.st.specialStyle.Render("▲ " + status)
return m.st.specialStyle.Render("▲ " + string(status))
}
}
+9 -9
View File
@@ -56,19 +56,19 @@ func TestSiteOrder(t *testing.T) {
func TestFmtStatus(t *testing.T) {
tests := []struct {
status string
status models.Status
paused bool
inMaint bool
wantSub string
}{
{"DOWN", false, false, "▼ DOWN"},
{"UP", false, false, "▲ UP"},
{"SSL EXP", false, false, "▼ SSL EXP"},
{"LATE", false, false, "◆ LATE"},
{"STALE", false, false, "◆ STALE"},
{"PENDING", false, false, "○ PENDING"},
{"DOWN", true, false, "◇ PAUSED"},
{"DOWN", false, true, "◼ MAINT"},
{models.StatusDown, false, false, "▼ DOWN"},
{models.StatusUp, false, false, "▲ UP"},
{models.StatusSSLExp, false, false, "▼ SSL EXP"},
{models.StatusLate, false, false, "◆ LATE"},
{models.StatusStale, false, false, "◆ STALE"},
{models.StatusPending, false, false, "○ PENDING"},
{models.StatusDown, true, false, "◇ PAUSED"},
{models.StatusDown, false, true, "◼ MAINT"},
}
for _, tt := range tests {
got := styledModel.fmtStatus(tt.status, tt.paused, tt.inMaint)
+1 -1
View File
@@ -240,7 +240,7 @@ func (m Model) viewSitesTab() string {
name = limitStr(name, nameW-2)
}
if (site.Status == "DOWN" || site.Status == "SSL EXP" || site.Status == "LATE" || site.Status == "STALE") && site.LastError != "" {
if (site.Status == models.StatusDown || site.Status == models.StatusSSLExp || site.Status == models.StatusLate || site.Status == models.StatusStale) && site.LastError != "" {
nameLen := len([]rune(name))
errSpace := nameW - nameLen - 3
if errSpace > 10 {
+1 -1
View File
@@ -455,7 +455,7 @@ func (m *Model) handleSLAData(msg slaDataMsg) (tea.Model, tea.Cmd) {
}
period := slaPeriods[msg.periodIdx]
var currentStatus string
var currentStatus models.Status
for _, s := range m.sites {
if s.ID == msg.siteID {
currentStatus = s.Status
+4 -3
View File
@@ -6,6 +6,7 @@ import (
"strings"
"time"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
"github.com/charmbracelet/lipgloss"
)
@@ -16,7 +17,7 @@ func sinApprox(x float64) float64 {
func (m Model) pulseIndicator() string {
hasDown := false
for _, s := range m.sites {
if !s.Paused && !m.isMonitorInMaintenance(s.ID) && (s.Status == "DOWN" || s.Status == "SSL EXP") {
if !s.Paused && !m.isMonitorInMaintenance(s.ID) && (s.Status == models.StatusDown || s.Status == models.StatusSSLExp) {
hasDown = true
break
}
@@ -127,9 +128,9 @@ func (m Model) computeStats() dashboardStats {
continue
}
switch site.Status {
case "DOWN", "SSL EXP":
case models.StatusDown, models.StatusSSLExp:
s.downCount++
case "LATE":
case models.StatusLate:
s.lateCount++
}
}
+4 -4
View File
@@ -45,7 +45,7 @@ func (m Model) viewDetailPanel() string {
row("Status", m.fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)))
if (site.Status == "DOWN" || site.Status == "SSL EXP" || site.Status == "LATE" || site.Status == "STALE") && site.LastError != "" {
if (site.Status == models.StatusDown || site.Status == models.StatusSSLExp || site.Status == models.StatusLate || site.Status == models.StatusStale) && site.LastError != "" {
errWidth := m.termWidth - chromePadH - 19
if errWidth < 30 {
errWidth = 30
@@ -58,7 +58,7 @@ func (m Model) viewDetailPanel() string {
row("HTTP Code", strconv.Itoa(site.StatusCode))
}
if (site.Status == "DOWN" || site.Status == "SSL EXP") && site.LastError != "" {
if (site.Status == models.StatusDown || site.Status == models.StatusSSLExp) && site.LastError != "" {
chain := connectionChain(site.LastError, site.Type, site.StatusCode, strings.HasPrefix(site.URL, "https"))
if len(chain) > 0 {
b.WriteString("\n")
@@ -189,7 +189,7 @@ func (m Model) viewDetailPanel() string {
for i, sc := range stateChanges {
ago := fmtDuration(time.Since(sc.ChangedAt))
arrow := m.st.subtleStyle.Render(sc.FromStatus) + " → "
if sc.ToStatus == "UP" {
if sc.ToStatus == string(models.StatusUp) {
arrow += m.st.specialStyle.Render(sc.ToStatus)
} else {
arrow += m.st.dangerStyle.Render(sc.ToStatus)
@@ -198,7 +198,7 @@ func (m Model) viewDetailPanel() string {
if dur := computeOutageDuration(stateChanges, i); dur > 0 {
line += " " + m.st.warnStyle.Render("outage "+fmtDuration(dur))
}
if sc.ErrorReason != "" && sc.ToStatus != "UP" {
if sc.ErrorReason != "" && sc.ToStatus != string(models.StatusUp) {
line += " " + m.st.dangerStyle.Render(sc.ErrorReason)
}
b.WriteString(line + "\n")
+6 -6
View File
@@ -17,14 +17,14 @@ type historyStats struct {
func computeOutageDuration(changes []models.StateChange, idx int) time.Duration {
sc := changes[idx]
if sc.ToStatus != "UP" {
if sc.ToStatus != string(models.StatusUp) {
return 0
}
if idx+1 >= len(changes) {
return 0
}
prev := changes[idx+1]
if prev.ToStatus == "UP" {
if prev.ToStatus == string(models.StatusUp) {
return 0
}
dur := sc.ChangedAt.Sub(prev.ChangedAt)
@@ -122,11 +122,11 @@ func (m Model) buildHistoryContent() string {
arrow := m.st.subtleStyle.Render(sc.FromStatus) + " → "
switch sc.ToStatus {
case "UP":
case string(models.StatusUp):
arrow += m.st.specialStyle.Render(sc.ToStatus)
case "LATE":
case string(models.StatusLate):
arrow += m.st.warnStyle.Render(sc.ToStatus)
case "STALE":
case string(models.StatusStale):
arrow += m.st.staleStyle.Render(sc.ToStatus)
default:
arrow += m.st.dangerStyle.Render(sc.ToStatus)
@@ -138,7 +138,7 @@ func (m Model) buildHistoryContent() string {
}
reason := ""
if sc.ErrorReason != "" && sc.ToStatus != "UP" {
if sc.ErrorReason != "" && sc.ToStatus != string(models.StatusUp) {
reason = m.st.dangerStyle.Render(limitStr(sc.ErrorReason, reasonWidth))
}