From d760420f7cb6367bcbacb1abe7b4ca70c8a72157 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Sat, 20 Jun 2026 16:44:12 -0400 Subject: [PATCH] feat(tui): add summary stats bar below sparse tab tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alerts: channel count, type count, total sent, failures. Nodes: online/total, leader ID, region count. Maint: active, scheduled, ended counts. Muted subtitle style — adds useful context without visual noise. --- internal/tui/tab_alerts.go | 21 ++++++++++++++++++++- internal/tui/tab_maint.go | 17 ++++++++++++++++- internal/tui/tab_nodes.go | 28 +++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/internal/tui/tab_alerts.go b/internal/tui/tab_alerts.go index c936cad..1926eb1 100644 --- a/internal/tui/tab_alerts.go +++ b/internal/tui/tab_alerts.go @@ -185,7 +185,7 @@ func (m Model) viewAlertsTab() string { nameW := widths[2] cfgW := widths[4] - return m.renderTable( + tbl := m.renderTable( headers, len(m.alerts), func(start, end int) [][]string { @@ -206,6 +206,25 @@ func (m Model) viewAlertsTab() string { }, widths, nil, ) + + var totalSent, totalFail int + for _, a := range m.alerts { + h := m.engine.GetAlertHealth(a.ID) + totalSent += h.SendCount + totalFail += h.FailCount + } + types := make(map[string]bool) + for _, a := range m.alerts { + types[a.Type] = true + } + failLabel := "failures" + if totalFail == 1 { + failLabel = "failure" + } + summary := fmt.Sprintf("%d channels · %d types · %d sent · %d %s", + len(m.alerts), len(types), totalSent, totalFail, failLabel) + + return tbl + "\n " + m.st.subtleStyle.Render(summary) } func (m Model) viewAlertDetailPanel() string { diff --git a/internal/tui/tab_maint.go b/internal/tui/tab_maint.go index 43dd67c..bc732cb 100644 --- a/internal/tui/tab_maint.go +++ b/internal/tui/tab_maint.go @@ -107,7 +107,7 @@ func (m Model) viewMaintTab() string { monW := widths[3] timeW := widths[5] - return m.renderTable( + tbl := m.renderTable( headers, len(m.maintenanceWindows), func(start, end int) [][]string { @@ -130,6 +130,21 @@ func (m Model) viewMaintTab() string { widths, nil, ) + + now := time.Now() + var active, scheduled, ended int + for _, mw := range m.maintenanceWindows { + if mw.StartTime.After(now) { + scheduled++ + } else if !mw.EndTime.IsZero() && mw.EndTime.Before(now) { + ended++ + } else { + active++ + } + } + summary := fmt.Sprintf("%d active · %d scheduled · %d ended", active, scheduled, ended) + + return tbl + "\n " + m.st.subtleStyle.Render(summary) } func (m *Model) initMaintHuhForm() tea.Cmd { diff --git a/internal/tui/tab_nodes.go b/internal/tui/tab_nodes.go index 49c8493..6aebe26 100644 --- a/internal/tui/tab_nodes.go +++ b/internal/tui/tab_nodes.go @@ -1,6 +1,8 @@ package tui import ( + "fmt" + "strings" "time" ) @@ -20,7 +22,7 @@ func (m Model) viewNodesTab() string { } nameW := widths[0] - return m.renderTable( + tbl := m.renderTable( headers, len(m.nodes), func(start, end int) [][]string { @@ -48,6 +50,30 @@ func (m Model) viewNodesTab() string { widths, nil, ) + + var online int + regions := make(map[string]bool) + var leader string + for _, n := range m.nodes { + if time.Since(n.LastSeen) < 60*time.Second { + online++ + } + if n.Region != "" { + regions[n.Region] = true + } + if n.Name == "leader" { + leader = n.ID + } + } + parts := []string{fmt.Sprintf("%d/%d online", online, len(m.nodes))} + if leader != "" { + parts = append(parts, fmt.Sprintf("leader: %s", leader)) + } + if len(regions) > 0 { + parts = append(parts, fmt.Sprintf("%d regions", len(regions))) + } + + return tbl + "\n " + m.st.subtleStyle.Render(strings.Join(parts, " · ")) } func (m Model) fmtNodeStatus(lastSeen time.Time) string {