chore(tui): visual polish — detail sections, column headers, alert detail #37
@@ -2,6 +2,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.lerkolabs.com/lerko/uptop/internal/monitor"
|
||||
@@ -173,6 +174,52 @@ func (m Model) viewAlertsTab() string {
|
||||
)
|
||||
}
|
||||
|
||||
func (m Model) viewAlertDetailPanel() string {
|
||||
if m.cursor >= len(m.alerts) {
|
||||
return ""
|
||||
}
|
||||
a := m.alerts[m.cursor]
|
||||
h := m.engine.GetAlertHealth(a.ID)
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(subtleStyle.Render(" Alerts > ") + titleStyle.Render(a.Name) + "\n\n")
|
||||
|
||||
row := func(label, value string) {
|
||||
fmt.Fprintf(&b, " %-16s %s\n", subtleStyle.Render(label), value)
|
||||
}
|
||||
|
||||
row("Type", fmtAlertType(a.Type))
|
||||
|
||||
if h.LastSendAt.IsZero() {
|
||||
row("Health", subtleStyle.Render("never sent"))
|
||||
} else if h.LastSendOK {
|
||||
row("Health", specialStyle.Render("OK"))
|
||||
} else {
|
||||
row("Health", dangerStyle.Render("FAILED"))
|
||||
}
|
||||
|
||||
if !h.LastSendAt.IsZero() {
|
||||
row("Last Sent", h.LastSendAt.Format("2006-01-02 15:04:05")+" ("+fmtAlertLastSent(h)+")")
|
||||
}
|
||||
if h.SendCount > 0 {
|
||||
row("Sends", fmt.Sprintf("%d sent, %d failed", h.SendCount, h.FailCount))
|
||||
}
|
||||
if h.LastError != "" {
|
||||
row("Last Error", dangerStyle.Render(limitStr(h.LastError, 60)))
|
||||
}
|
||||
|
||||
b.WriteString("\n" + subtleStyle.Render(" CONFIGURATION") + "\n")
|
||||
for k, v := range a.Settings {
|
||||
row(k, v)
|
||||
}
|
||||
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString(subtleStyle.Render(" [i/Esc] Back [e] Edit [t] Test [q] Quit"))
|
||||
|
||||
return lipgloss.NewStyle().Padding(1, 2).Render(b.String())
|
||||
}
|
||||
|
||||
func (m *Model) initAlertHuhForm() tea.Cmd {
|
||||
m.alertFormData = &alertFormData{
|
||||
AlertType: "discord",
|
||||
|
||||
@@ -378,7 +378,7 @@ func (m Model) viewSitesTab() string {
|
||||
|
||||
var groupRows map[int]bool
|
||||
return m.renderTable(
|
||||
[]string{"#", "NAME", "TYPE", "STATUS", "LATENCY", "UPTIME", "HISTORY", "SSL", "RETRY"},
|
||||
[]string{"#", "NAME", "TYPE", "STATUS", "LAT", "UPTIME", "HISTORY", "SSL", "RETRY"},
|
||||
len(m.sites),
|
||||
func(start, end int) [][]string {
|
||||
groupRows = make(map[int]bool)
|
||||
@@ -764,6 +764,10 @@ func (m Model) viewDetailPanel() string {
|
||||
fmt.Fprintf(&b, " %-16s %s\n", subtleStyle.Render(label), value)
|
||||
}
|
||||
|
||||
section := func(label string) {
|
||||
b.WriteString("\n" + subtleStyle.Render(" "+label) + "\n")
|
||||
}
|
||||
|
||||
row("Status", fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)))
|
||||
|
||||
if (site.Status == "DOWN" || site.Status == "SSL EXP" || site.Status == "LATE") && site.LastError != "" {
|
||||
@@ -792,6 +796,8 @@ func (m Model) viewDetailPanel() string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section("ENDPOINT")
|
||||
row("Type", site.Type)
|
||||
if site.URL != "" {
|
||||
row("URL", site.URL)
|
||||
@@ -802,20 +808,36 @@ func (m Model) viewDetailPanel() string {
|
||||
if site.Port > 0 {
|
||||
row("Port", strconv.Itoa(site.Port))
|
||||
}
|
||||
|
||||
section("TIMING")
|
||||
row("Interval", fmt.Sprintf("%ds", site.Interval))
|
||||
if site.Timeout > 0 {
|
||||
row("Timeout", fmt.Sprintf("%ds", site.Timeout))
|
||||
}
|
||||
row("Latency", fmtLatency(site.Latency))
|
||||
row("Uptime", fmtUptime(hist.Statuses))
|
||||
if !site.LastCheck.IsZero() {
|
||||
row("Last Check", site.LastCheck.Format("15:04:05"))
|
||||
}
|
||||
|
||||
if site.Type == "http" {
|
||||
section("HTTP")
|
||||
if site.Method != "" && site.Method != "GET" {
|
||||
row("Method", site.Method)
|
||||
row("Codes", site.AcceptedCodes)
|
||||
}
|
||||
codes := site.AcceptedCodes
|
||||
if codes == "" {
|
||||
codes = "200-299"
|
||||
}
|
||||
row("Codes", codes)
|
||||
row("SSL", fmtSSL(site))
|
||||
if site.IgnoreTLS {
|
||||
row("TLS Verify", dangerStyle.Render("disabled"))
|
||||
}
|
||||
}
|
||||
|
||||
if site.MaxRetries > 0 || site.Regions != "" || site.Description != "" {
|
||||
section("CONFIG")
|
||||
if site.MaxRetries > 0 {
|
||||
row("Retries", fmtRetries(site))
|
||||
}
|
||||
@@ -825,8 +847,6 @@ func (m Model) viewDetailPanel() string {
|
||||
if site.Description != "" {
|
||||
row("Description", site.Description)
|
||||
}
|
||||
if !site.LastCheck.IsZero() {
|
||||
row("Last Check", site.LastCheck.Format("15:04:05"))
|
||||
}
|
||||
|
||||
probeResults := m.engine.GetProbeResults(site.ID)
|
||||
|
||||
+14
-1
@@ -68,6 +68,7 @@ const (
|
||||
stateLogs
|
||||
stateUsers
|
||||
stateDetail
|
||||
stateAlertDetail
|
||||
stateFormSite
|
||||
stateFormAlert
|
||||
stateFormUser
|
||||
@@ -384,6 +385,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
case stateAlertDetail:
|
||||
switch msg.String() {
|
||||
case "i", "esc":
|
||||
m.state = stateDashboard
|
||||
case "q":
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
case stateDashboard, stateLogs, stateUsers:
|
||||
switch msg.String() {
|
||||
case "q":
|
||||
@@ -497,6 +506,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "i":
|
||||
if m.currentTab == 0 && len(m.sites) > 0 {
|
||||
m.state = stateDetail
|
||||
} else if m.currentTab == 1 && len(m.alerts) > 0 {
|
||||
m.state = stateAlertDetail
|
||||
}
|
||||
case "x":
|
||||
if m.currentTab == 4 && len(m.maintenanceWindows) > 0 {
|
||||
@@ -818,6 +829,8 @@ func (m Model) View() string {
|
||||
return ""
|
||||
case stateDetail:
|
||||
return m.viewDetailPanel()
|
||||
case stateAlertDetail:
|
||||
return m.viewAlertDetailPanel()
|
||||
default:
|
||||
return m.zones.Scan(m.viewDashboard())
|
||||
}
|
||||
@@ -954,7 +967,7 @@ func (m Model) viewDashboard() string {
|
||||
case 0:
|
||||
keys = "[/]Filter [n]New [e]Edit [i]Info [d]Del [p]Pause [T]Theme [Tab]Switch [q]Quit"
|
||||
case 1:
|
||||
keys = "[n]New [e]Edit [d]Del [t]Test [T]Theme [Tab]Switch [q]Quit"
|
||||
keys = "[n]New [e]Edit [i]Info [d]Del [t]Test [T]Theme [Tab]Switch [q]Quit"
|
||||
case 2:
|
||||
keys = "[f]Filter [T]Theme [Tab]Switch [q]Quit"
|
||||
case 4:
|
||||
|
||||
Reference in New Issue
Block a user