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