d05bbd007b
Extract shared computeTableLayout() into table_helpers.go — takes column definitions with short/full headers, min/max widths, and a flex column that absorbs surplus space. All tabs now use it: - Alerts: CONFIG column is flex, NAME/TYPE/SENT expand with width - Maint: TITLE column is flex, TYPE/MONITORS/STATUS/dates expand - Nodes: NAME column is flex, REGION/LAST SEEN/VERSION expand - Users: PUBLIC KEY column is flex, USERNAME expands - Sites: uses same colDef type (keeps special dual-flex for NAME+HISTORY) Headers auto-switch short/full based on available width across all tabs.
80 lines
1.7 KiB
Go
80 lines
1.7 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
func (m Model) viewNodesTab() string {
|
|
if len(m.nodes) == 0 {
|
|
return "\n No probe nodes connected."
|
|
}
|
|
|
|
cols := []colDef{
|
|
{"NAME", "NAME", 12, 24, true},
|
|
{"REGION", "REGION", 8, 14, false},
|
|
{"SEEN", "LAST SEEN", 8, 20, false},
|
|
{"VER", "VERSION", 8, 12, false},
|
|
{"STATUS", "STATUS", 8, 10, false},
|
|
}
|
|
headers, widths := m.computeTableLayout(cols, 0)
|
|
nameW := widths[0]
|
|
|
|
return m.renderTable(
|
|
headers,
|
|
len(m.nodes),
|
|
func(start, end int) [][]string {
|
|
var rows [][]string
|
|
for i := start; i < end; i++ {
|
|
node := m.nodes[i]
|
|
name := limitStr(node.Name, nameW-2)
|
|
if name == "" {
|
|
name = node.ID
|
|
}
|
|
region := node.Region
|
|
if region == "" {
|
|
region = subtleStyle.Render("—")
|
|
}
|
|
lastSeen := fmtNodeLastSeen(node.LastSeen)
|
|
version := node.Version
|
|
if version == "" {
|
|
version = subtleStyle.Render("—")
|
|
}
|
|
status := fmtNodeStatus(node.LastSeen)
|
|
rows = append(rows, []string{name, region, lastSeen, version, status})
|
|
}
|
|
return rows
|
|
},
|
|
widths,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func fmtNodeStatus(lastSeen time.Time) string {
|
|
if lastSeen.IsZero() {
|
|
return subtleStyle.Render("UNKNOWN")
|
|
}
|
|
ago := time.Since(lastSeen)
|
|
if ago < 60*time.Second {
|
|
return specialStyle.Render("ONLINE")
|
|
}
|
|
if ago < 5*time.Minute {
|
|
return warnStyle.Render("STALE")
|
|
}
|
|
return dangerStyle.Render("OFFLINE")
|
|
}
|
|
|
|
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()))
|
|
}
|