feat(tui): classify error reasons on DOWN monitors
CI / test (pull_request) Successful in 2m30s
CI / lint (pull_request) Successful in 1m7s
CI / vulncheck (pull_request) Successful in 46s

Categorize raw error strings into DNS/TCP/TLS/HTTP/ICMP/TMO/PRIV
so users get instant triage from the monitor list without opening
the detail panel.

- Status column shows DOWN:DNS, DOWN:TLS, DOWN:HTTP, etc.
- Inline NAME column errors prefixed with category tag [DNS], [TLS]
- Detail panel shows connection chain checklist for HTTP monitors
  (✓ DNS → ✓ TCP → ✗ TLS → · HTTP) pinpointing failure layer
- All display-side only — no database or model changes
This commit is contained in:
2026-06-03 16:33:12 -04:00
parent 5d362fdbe6
commit 3d7ab5a49e
6 changed files with 464 additions and 8 deletions
+30 -1
View File
@@ -40,7 +40,8 @@ func (m Model) viewDetailPanel() string {
b.WriteString("\n" + subtleStyle.Render(" "+label) + "\n")
}
row("Status", fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)))
errCat := classifyError(site.LastError, site.Type, site.StatusCode)
row("Status", fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID), errCat))
if (site.Status == "DOWN" || site.Status == "SSL EXP" || site.Status == "LATE") && site.LastError != "" {
row("Error", dangerStyle.Render(limitStr(site.LastError, 60)))
@@ -50,6 +51,34 @@ func (m Model) viewDetailPanel() string {
row("HTTP Code", strconv.Itoa(site.StatusCode))
}
if (site.Status == "DOWN" || site.Status == "SSL EXP") && site.LastError != "" {
chain := connectionChain(site.LastError, site.Type, site.StatusCode, strings.HasPrefix(site.URL, "https"))
if len(chain) > 0 {
b.WriteString("\n")
for _, step := range chain {
var icon string
switch step.Status {
case stepPassed:
icon = specialStyle.Render("✓")
case stepFailed:
icon = dangerStyle.Render("✗")
case stepSkipped:
icon = subtleStyle.Render("·")
}
line := fmt.Sprintf(" %s %-16s", icon, step.Name)
if step.Detail != "" {
switch step.Status {
case stepFailed:
line += " " + dangerStyle.Render(step.Detail)
case stepSkipped:
line += " " + subtleStyle.Render(step.Detail)
}
}
b.WriteString(line + "\n")
}
}
}
if !site.StatusChangedAt.IsZero() {
dur := time.Since(site.StatusChangedAt)
row("State Since", site.StatusChangedAt.Format("2006-01-02 15:04:05")+" ("+fmtDuration(dur)+")")