From 7171dcd6f8d69b77fbc2f97dc28e09832b8c3b80 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Sun, 28 Jun 2026 10:41:25 -0400 Subject: [PATCH] feat(tui): expand selected alert config below table Show a detail line below the alert table for the selected row with untruncated config, health status, last sent time, and send/fail counts. Solves the CONFIG column truncation issue without requiring the full-screen detail view. --- internal/tui/tab_alerts.go | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/internal/tui/tab_alerts.go b/internal/tui/tab_alerts.go index d67eb4b..f9b6b3e 100644 --- a/internal/tui/tab_alerts.go +++ b/internal/tui/tab_alerts.go @@ -223,7 +223,72 @@ func (m Model) viewAlertsTab() string { 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) + var detail string + if m.cursor < len(m.alerts) { + a := m.alerts[m.cursor] + h := m.engine.GetAlertHealth(a.ID) + detail = m.alertSelectedDetail(a, h) + } + + return tbl + "\n " + detail + "\n " + m.st.subtleStyle.Render(summary) +} + +func (m Model) alertSelectedDetail(a models.AlertConfig, h monitor.AlertHealth) string { + dot := m.st.subtleStyle.Render(" · ") + label := m.st.subtleStyle + + parts := []string{fmtAlertType(a.Type)} + parts = append(parts, m.fmtAlertConfigFull(a)) + + if !h.LastSendAt.IsZero() { + if h.LastSendOK { + parts = append(parts, m.st.specialStyle.Render("●")+" "+label.Render("sent")+" "+m.fmtTimeAgo(h.LastSendAt)) + } else { + parts = append(parts, m.st.dangerStyle.Render("●")+" "+label.Render("failed")+" "+m.fmtTimeAgo(h.LastSendAt)) + } + } + if h.SendCount > 0 { + parts = append(parts, label.Render(fmt.Sprintf("%d sent, %d failed", h.SendCount, h.FailCount))) + } + + return strings.Join(parts, dot) +} + +func (m Model) fmtAlertConfigFull(alert models.AlertConfig) string { + switch alert.Type { + case "email": + host := alert.Settings["host"] + to := alert.Settings["to"] + if host != "" && to != "" { + return fmt.Sprintf("%s → %s", host, to) + } + if host != "" { + return host + } + return m.st.subtleStyle.Render("—") + case "ntfy": + topic := alert.Settings["topic"] + url := alert.Settings["url"] + if url != "" && topic != "" { + return fmt.Sprintf("%s/%s", url, topic) + } + return m.st.subtleStyle.Render("—") + case "telegram": + if id := alert.Settings["chat_id"]; id != "" { + return fmt.Sprintf("chat:%s", id) + } + return m.st.subtleStyle.Render("—") + case "gotify": + if url := alert.Settings["url"]; url != "" { + return url + } + return m.st.subtleStyle.Render("—") + default: + if val, ok := alert.Settings["url"]; ok && val != "" { + return maskWebhookURL(val) + } + return m.st.subtleStyle.Render("—") + } } func (m Model) viewAlertDetailPanel() string {