feat(tui): add summary stats bar below sparse tab tables
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.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user