polish(tui): responsive column hiding — 3-tier priority-based layout
CI / test (pull_request) Successful in 2m29s
CI / lint (pull_request) Successful in 57s
CI / vulncheck (pull_request) Successful in 46s

Columns drop progressively as terminal narrows:
- Compact (<90): #, NAME, STATUS, LATENCY
- Medium (90-119): + TYPE, UPTIME, HISTORY
- Wide (120+): + SSL, RETRIES

Column definitions are data-driven via siteColumns slice.
Row builder uses pickCols() helper so headers and cells can't drift.

Closes #68
This commit was merged in pull request #95.
This commit is contained in:
2026-06-05 17:50:57 -04:00
parent 69a8e0f7ba
commit 33dc84449b
2 changed files with 125 additions and 42 deletions
+114 -34
View File
@@ -33,27 +33,74 @@ type siteFormData struct {
Regions string
}
type colKey int
const (
colNum colKey = iota
colName
colType
colStatus
colLatency
colUptime
colHistory
colSSL
colRetries
)
type columnDef struct {
key colKey
wide string
narrow string
wideW int
narrowW int
minTerm int // minimum terminal width to show (0 = always)
}
var siteColumns = []columnDef{
{colNum, "#", "#", 4, 4, 0},
{colName, "NAME", "NAME", 0, 0, 0},
{colType, "TYPE", "TYPE", 10, 8, mediumBreakpoint},
{colStatus, "STATUS", "STATUS", 10, 10, 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},
}
type tableLayout struct {
nameW, sparkW int
headers []string
colWidths []int
active []colKey
}
func (m Model) computeLayout() tableLayout {
wide := m.isWide()
var fixed int
var active []colKey
var headers []string
var widths []int
var fixed int
for _, c := range siteColumns {
if c.minTerm > 0 && m.termWidth < c.minTerm {
continue
}
active = append(active, c.key)
if wide {
headers = []string{"#", "NAME", "TYPE", "STATUS", "LATENCY", "UPTIME", "HISTORY", "SSL", "RETRIES"}
widths = []int{4, 0, 10, 10, 10, 8, 0, 7, 9}
fixed = 4 + 10 + 10 + 10 + 8 + 7 + 9
headers = append(headers, c.wide)
widths = append(widths, c.wideW)
if c.wideW > 0 {
fixed += c.wideW
}
} else {
headers = []string{"#", "NAME", "TYPE", "STATUS", "LAT", "UP%", "HISTORY", "SSL", "RT"}
widths = []int{4, 0, 8, 10, 7, 8, 0, 5, 5}
fixed = 4 + 8 + 10 + 7 + 8 + 5 + 5
headers = append(headers, c.narrow)
widths = append(widths, c.narrowW)
if c.narrowW > 0 {
fixed += c.narrowW
}
}
}
numCols := len(headers)
@@ -71,7 +118,23 @@ func (m Model) computeLayout() tableLayout {
}
maxName += 4
nameW := avail / 2
hasHistory := false
for _, k := range active {
if k == colHistory {
hasHistory = true
break
}
}
var nameW, sparkW int
if hasHistory {
nameW = avail / 2
sparkW = avail - nameW
} else {
nameW = avail
sparkW = 0
}
if nameW > maxName {
nameW = maxName
}
@@ -81,26 +144,41 @@ func (m Model) computeLayout() tableLayout {
if nameW > 35 {
nameW = 35
}
sparkW := avail - nameW
if sparkW > 0 {
if sparkW < 15 {
sparkW = 15
}
if sparkW > 62 {
sparkW = 62
}
}
widths[1] = nameW
widths[6] = sparkW
for i, k := range active {
if k == colName {
widths[i] = nameW
}
if k == colHistory {
widths[i] = sparkW
}
}
return tableLayout{
nameW: nameW,
sparkW: sparkW,
headers: headers,
colWidths: widths,
active: active,
}
}
func pickCols(active []colKey, allCells map[colKey]string) []string {
row := make([]string, len(active))
for i, k := range active {
row[i] = allCells[k]
}
return row
}
func (m Model) viewSitesTab() string {
if len(m.sites) == 0 {
@@ -137,17 +215,18 @@ func (m Model) viewSitesTab() string {
if site.Type == "group" {
groupRows[i-start] = true
icon := typeIcon("group", m.collapsed[site.ID])
rows = append(rows, []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)),
subtleStyle.Render("—"),
m.groupUptime(site.ID),
m.groupSparkline(site.ID, sparkWidth, rowBg),
subtleStyle.Render("-"),
subtleStyle.Render("—"),
})
cells := map[colKey]string{
colNum: strconv.Itoa(i + 1),
colName: m.zones.Mark(fmt.Sprintf("site-%d", i), icon+" "+limitStr(site.Name, nameW-4)),
colType: "group",
colStatus: fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
colLatency: subtleStyle.Render("—"),
colUptime: m.groupUptime(site.ID),
colHistory: m.groupSparkline(site.ID, sparkWidth, rowBg),
colSSL: subtleStyle.Render("-"),
colRetries: subtleStyle.Render("—"),
}
rows = append(rows, pickCols(layout.active, cells))
continue
}
@@ -184,17 +263,18 @@ func (m Model) viewSitesTab() string {
spark = latencySparkline(hist.Latencies, hist.Statuses, sparkWidth, rowBg)
}
rows = append(rows, []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)),
fmtLatency(site.Latency),
fmtUptime(hist.Statuses),
spark,
fmtSSL(site),
fmtRetries(site),
})
cells := map[colKey]string{
colNum: strconv.Itoa(i + 1),
colName: m.zones.Mark(fmt.Sprintf("site-%d", i), name),
colType: typeIcon(site.Type, false) + " " + site.Type,
colStatus: fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
colLatency: fmtLatency(site.Latency),
colUptime: fmtUptime(hist.Statuses),
colHistory: spark,
colSSL: fmtSSL(site),
colRetries: fmtRetries(site),
}
rows = append(rows, pickCols(layout.active, cells))
}
return rows
},
+4 -1
View File
@@ -15,7 +15,10 @@ var (
type StyleOverride func(row, col int) *lipgloss.Style
const wideBreakpoint = 120
const (
wideBreakpoint = 120
mediumBreakpoint = 90
)
func (m Model) isWide() bool {
return m.termWidth >= wideBreakpoint