From faf7d36c642d9f182fd01485b3de754b12eb4e78 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 23:29:45 -0400 Subject: [PATCH] refactor(tui): replace # column with colored status dot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the row-number column and add a colored status dot as the first column. Dot uses the same icon/color as the existing status indicators (▲ green for up, ▼ red for down, ◼ purple for maint, etc). Matches the maint strip visual pattern. Status text column retained for explicit label. --- internal/tui/format.go | 21 +++++++++++ internal/tui/tab_sites.go | 76 ++++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/internal/tui/format.go b/internal/tui/format.go index 31f0881..7d51f9e 100644 --- a/internal/tui/format.go +++ b/internal/tui/format.go @@ -162,6 +162,27 @@ func (m Model) fmtRetries(site models.Site) string { return s } +func (m Model) fmtStatusDot(status models.Status, paused bool, inMaint bool) string { + if paused { + return m.st.warnStyle.Render("◇") + } + if inMaint { + return m.st.maintStyle.Render("◼") + } + switch status { + case models.StatusDown, models.StatusSSLExp: + return m.st.dangerStyle.Render("▼") + case models.StatusLate: + return m.st.warnStyle.Render("◆") + case models.StatusStale: + return m.st.staleStyle.Render("◆") + case models.StatusPending: + return m.st.subtleStyle.Render("○") + default: + return m.st.specialStyle.Render("▲") + } +} + func (m Model) fmtStatus(status models.Status, paused bool, inMaint bool) string { if paused { return m.st.warnStyle.Render("◇ PAUSED") diff --git a/internal/tui/tab_sites.go b/internal/tui/tab_sites.go index 16493b2..dc8501d 100644 --- a/internal/tui/tab_sites.go +++ b/internal/tui/tab_sites.go @@ -34,10 +34,9 @@ type siteFormData struct { type colKey int const ( - colNum colKey = iota + colDot colKey = iota colName colType - colStatus colLatency colUptime colHistory @@ -55,17 +54,18 @@ type columnDef struct { } var siteColumns = []columnDef{ - {colNum, "#", "#", 4, 4, 0}, + {colDot, "", "", 3, 3, 0}, {colName, "NAME", "NAME", 0, 0, 0}, - {colType, "TYPE", "TYPE", 10, 8, mediumBreakpoint}, - {colStatus, "STATUS", "STATUS", 10, 10, 0}, + {colType, "TYPE", "TYPE", 10, 8, 0}, {colLatency, "LATENCY", "LAT", 10, 7, 0}, - {colUptime, "UPTIME", "UP%", 8, 8, mediumBreakpoint}, - {colHistory, "HISTORY", "HISTORY", 0, 0, mediumBreakpoint}, - {colSSL, "SSL", "SSL", 7, 5, wideBreakpoint}, - {colRetries, "RETRIES", "RT", 9, 5, wideBreakpoint}, + {colUptime, "UPTIME", "UP%", 8, 8, 0}, + {colHistory, "HISTORY", "HISTORY", 0, 0, 0}, + {colSSL, "SSL", "SSL", 7, 5, 0}, + {colRetries, "RETRIES", "RT", 9, 5, 0}, } +var columnDropOrder = []colKey{colHistory, colRetries, colSSL, colUptime, colType} + type tableLayout struct { nameW, sparkW int headers []string @@ -76,17 +76,51 @@ type tableLayout struct { func (m Model) computeLayout() tableLayout { wide := m.isWide() - var active []colKey - var headers []string - var widths []int - var fixed int - cw := m.contentWidth if cw == 0 { cw = m.termWidth } + + dropped := make(map[colKey]bool) + minNameW := 20 + minSparkW := 12 + + for attempt := 0; attempt <= len(columnDropOrder); attempt++ { + var fixed int + var flexCount int + for _, c := range siteColumns { + if dropped[c.key] { + continue + } + w := c.narrowW + if wide { + w = c.wideW + } + if w > 0 { + fixed += w + } else { + flexCount++ + } + } + numCols := len(siteColumns) - len(dropped) + borderOverhead := 2 + (numCols - 1) + avail := cw - chromePadH - 2 - borderOverhead - fixed + minFlex := minNameW + if flexCount > 1 { + minFlex += minSparkW + } + if avail >= minFlex || attempt >= len(columnDropOrder) { + break + } + dropped[columnDropOrder[attempt]] = true + } + + var active []colKey + var headers []string + var widths []int + var fixed int for _, c := range siteColumns { - if c.minTerm > 0 && cw < c.minTerm { + if dropped[c.key] { continue } active = append(active, c.key) @@ -106,12 +140,12 @@ func (m Model) computeLayout() tableLayout { } sortColMap := map[int]colKey{ - sortStatus: colStatus, + sortStatus: colDot, sortName: colName, sortLatency: colLatency, } sortableKeys := map[colKey]string{ - colStatus: "sort-status", + colDot: "sort-status", colName: "sort-name", colLatency: "sort-latency", } @@ -245,11 +279,11 @@ func (m Model) viewSitesTab() string { if site.Type == "group" { groupRows[i-start] = true icon := typeIcon("group", m.collapsed[site.ID]) + inMaint := m.isMonitorInMaintenance(site.ID) cells := map[colKey]string{ - colNum: strconv.Itoa(i + 1), + colDot: m.fmtStatusDot(site.Status, site.Paused, inMaint), colName: m.zones.Mark(fmt.Sprintf("site-%d", i), icon+" "+limitStr(site.Name, nameW-4)), colType: "group", - colStatus: m.fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)), colLatency: m.st.subtleStyle.Render("—"), colUptime: m.groupUptime(site.ID), colHistory: m.groupSparkline(site.ID, sparkWidth, rowBg), @@ -293,11 +327,11 @@ func (m Model) viewSitesTab() string { spark = m.latencySparkline(hist.Latencies, hist.Statuses, sparkWidth, rowBg) } + inMaint := m.isMonitorInMaintenance(site.ID) cells := map[colKey]string{ - colNum: strconv.Itoa(i + 1), + colDot: m.fmtStatusDot(site.Status, site.Paused, inMaint), colName: m.zones.Mark(fmt.Sprintf("site-%d", i), name), colType: typeIcon(site.Type, false) + " " + site.Type, - colStatus: m.fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)), colLatency: m.fmtLatency(site.Latency), colUptime: m.fmtUptimeMaint(hist.Statuses, site.ID), colHistory: spark,