From fb709b34c5d9fa32a3d70854b7d748861e0b6ca1 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Thu, 4 Jun 2026 17:03:04 -0400 Subject: [PATCH] refactor(tui): status icons, clean STATUS column, relative time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STATUS column shows icon + clean state only (▲ UP, ▼ DOWN, ◆ LATE, ◆ STALE, ◇ PAUSED, ◼ MAINT, ○ PENDING). Error classification (DNS/TLS/TMO) removed from STATUS — stays in NAME inline hint. - Detail panel Last Check shows relative time ("12s ago") instead of absolute timestamp. - Extract shared fmtTimeAgo() to format.go, consolidate duplicate formatters in tab_alerts.go and tab_nodes.go. --- internal/tui/format.go | 39 ++++++++++++++++++++++++------------- internal/tui/format_test.go | 29 ++++++++++++--------------- internal/tui/tab_alerts.go | 16 +-------------- internal/tui/tab_nodes.go | 13 +------------ internal/tui/tab_sites.go | 4 ++-- internal/tui/view_detail.go | 5 ++--- 6 files changed, 44 insertions(+), 62 deletions(-) diff --git a/internal/tui/format.go b/internal/tui/format.go index 25fa9cb..b6c304f 100644 --- a/internal/tui/format.go +++ b/internal/tui/format.go @@ -152,33 +152,46 @@ func fmtRetries(site models.Site) string { return s } -func fmtStatus(status string, paused bool, inMaint bool, errCategory ErrorCategory) string { +func fmtStatus(status string, paused bool, inMaint bool) string { if paused { - return warnStyle.Render("PAUSED") + return warnStyle.Render("◇ PAUSED") } if inMaint { - return maintStyle.Render("MAINT") + return maintStyle.Render("◼ MAINT") } switch status { case "DOWN": - label := "DOWN" - if errCategory != ErrCatUnknown { - label = "DOWN:" + string(errCategory) - } - return dangerStyle.Render(label) + return dangerStyle.Render("▼ DOWN") case "SSL EXP": - return dangerStyle.Render(status) + return dangerStyle.Render("▼ SSL EXP") case "LATE": - return warnStyle.Render(status) + return warnStyle.Render("◆ LATE") case "STALE": - return staleStyle.Render(status) + return staleStyle.Render("◆ STALE") case "PENDING": - return subtleStyle.Render(status) + return subtleStyle.Render("○ PENDING") default: - return specialStyle.Render(status) + return specialStyle.Render("▲ " + status) } } +func fmtTimeAgo(t time.Time) string { + if t.IsZero() { + return subtleStyle.Render("never") + } + d := time.Since(t) + if d < time.Minute { + return fmt.Sprintf("%ds ago", int(d.Seconds())) + } + if d < time.Hour { + return fmt.Sprintf("%dm ago", int(d.Minutes())) + } + if d < 24*time.Hour { + return fmt.Sprintf("%dh ago", int(d.Hours())) + } + return fmt.Sprintf("%dd ago", int(d.Hours())/24) +} + func fmtDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf("%ds", int(d.Seconds())) diff --git a/internal/tui/format_test.go b/internal/tui/format_test.go index f27d668..74fed89 100644 --- a/internal/tui/format_test.go +++ b/internal/tui/format_test.go @@ -55,32 +55,27 @@ func TestSiteOrder(t *testing.T) { } } -func TestFmtStatus_ErrorCategory(t *testing.T) { +func TestFmtStatus(t *testing.T) { tests := []struct { status string paused bool inMaint bool - cat ErrorCategory wantSub string }{ - {"DOWN", false, false, ErrCatDNS, "DOWN:DNS"}, - {"DOWN", false, false, ErrCatTLS, "DOWN:TLS"}, - {"DOWN", false, false, ErrCatHTTP, "DOWN:HTTP"}, - {"DOWN", false, false, ErrCatTCP, "DOWN:TCP"}, - {"DOWN", false, false, ErrCatTimeout, "DOWN:TMO"}, - {"DOWN", false, false, ErrCatICMP, "DOWN:ICMP"}, - {"DOWN", false, false, ErrCatPrivate, "DOWN:PRIV"}, - {"DOWN", false, false, ErrCatUnknown, "DOWN"}, - {"UP", false, false, ErrCatUnknown, "UP"}, - {"SSL EXP", false, false, ErrCatUnknown, "SSL EXP"}, - {"DOWN", true, false, ErrCatDNS, "PAUSED"}, - {"DOWN", false, true, ErrCatDNS, "MAINT"}, + {"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"}, } for _, tt := range tests { - got := fmtStatus(tt.status, tt.paused, tt.inMaint, tt.cat) + got := fmtStatus(tt.status, tt.paused, tt.inMaint) if !containsPlain(got, tt.wantSub) { - t.Errorf("fmtStatus(%q, paused=%v, maint=%v, %q): %q missing %q", - tt.status, tt.paused, tt.inMaint, tt.cat, got, tt.wantSub) + t.Errorf("fmtStatus(%q, paused=%v, maint=%v): %q missing %q", + tt.status, tt.paused, tt.inMaint, got, tt.wantSub) } } } diff --git a/internal/tui/tab_alerts.go b/internal/tui/tab_alerts.go index cd0971a..1249fbb 100644 --- a/internal/tui/tab_alerts.go +++ b/internal/tui/tab_alerts.go @@ -3,7 +3,6 @@ package tui import ( "fmt" "strings" - "time" "gitea.lerkolabs.com/lerkolabs/uptop/internal/monitor" tea "github.com/charmbracelet/bubbletea" @@ -146,20 +145,7 @@ func fmtAlertHealth(h monitor.AlertHealth) string { } func fmtAlertLastSent(h monitor.AlertHealth) string { - if h.LastSendAt.IsZero() { - return subtleStyle.Render("never") - } - d := time.Since(h.LastSendAt) - if d < time.Minute { - return fmt.Sprintf("%ds ago", int(d.Seconds())) - } - if d < time.Hour { - return fmt.Sprintf("%dm ago", int(d.Minutes())) - } - if d < 24*time.Hour { - return fmt.Sprintf("%dh ago", int(d.Hours())) - } - return fmt.Sprintf("%dd ago", int(d.Hours())/24) + return fmtTimeAgo(h.LastSendAt) } func (m Model) viewAlertsTab() string { diff --git a/internal/tui/tab_nodes.go b/internal/tui/tab_nodes.go index 14f49f4..ac53828 100644 --- a/internal/tui/tab_nodes.go +++ b/internal/tui/tab_nodes.go @@ -1,7 +1,6 @@ package tui import ( - "fmt" "time" ) @@ -66,15 +65,5 @@ func fmtNodeStatus(lastSeen time.Time) string { } func fmtNodeLastSeen(t time.Time) string { - if t.IsZero() { - return subtleStyle.Render("never") - } - ago := time.Since(t) - if ago < time.Minute { - return fmt.Sprintf("%ds ago", int(ago.Seconds())) - } - if ago < time.Hour { - return fmt.Sprintf("%dm ago", int(ago.Minutes())) - } - return fmt.Sprintf("%dh ago", int(ago.Hours())) + return fmtTimeAgo(t) } diff --git a/internal/tui/tab_sites.go b/internal/tui/tab_sites.go index 5d552be..0e4bb46 100644 --- a/internal/tui/tab_sites.go +++ b/internal/tui/tab_sites.go @@ -128,7 +128,7 @@ func (m Model) viewSitesTab() string { strconv.Itoa(i + 1), m.zones.Mark(fmt.Sprintf("site-%d", i), icon+" "+limitStr(site.Name, nameW-4)), "group", - fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID), ErrCatUnknown), + fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)), subtleStyle.Render("—"), m.groupUptime(site.ID), m.groupSparkline(site.ID, sparkWidth), @@ -175,7 +175,7 @@ func (m Model) viewSitesTab() string { strconv.Itoa(i + 1), m.zones.Mark(fmt.Sprintf("site-%d", i), name), typeIcon(site.Type, false) + " " + site.Type, - fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID), classifyError(site.LastError, site.Type, site.StatusCode)), + fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)), fmtLatency(site.Latency), fmtUptime(hist.Statuses), spark, diff --git a/internal/tui/view_detail.go b/internal/tui/view_detail.go index 5431d0c..19fc22a 100644 --- a/internal/tui/view_detail.go +++ b/internal/tui/view_detail.go @@ -41,8 +41,7 @@ func (m Model) viewDetailPanel() string { b.WriteString("\n" + subtleStyle.Render(" "+label) + "\n") } - errCat := classifyError(site.LastError, site.Type, site.StatusCode) - row("Status", fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID), errCat)) + row("Status", 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 != "" { errWidth := m.termWidth - chromePadH - 19 @@ -128,7 +127,7 @@ func (m Model) viewDetailPanel() string { row("Latency", fmtLatency(site.Latency)) row("Uptime", fmtUptime(hist.Statuses)) if !site.LastCheck.IsZero() { - row("Last Check", site.LastCheck.Format("15:04:05")) + row("Last Check", fmtTimeAgo(site.LastCheck)) } if site.Type == "http" {