feat(tui): add state change history view with outage duration
CI / test (pull_request) Successful in 2m30s
CI / lint (pull_request) Failing after 51s
CI / vulncheck (pull_request) Successful in 46s

Full-screen scrollable history view accessible via [h] from detail
panel. Shows all state transitions with computed outage durations,
event density sparkline for flapping detection, and summary stats.

- Detail panel STATE CHANGES now shows outage duration per recovery
- Event density sparkline highlights flapping periods
- Summary footer: event count, outage count, avg outage duration
- Vim-style navigation (j/k/g/G) + mouse scroll in history view
This commit is contained in:
2026-06-03 19:49:10 -04:00
parent c0ad51af9c
commit bc661f5207
6 changed files with 422 additions and 4 deletions
+6 -2
View File
@@ -176,7 +176,7 @@ func (m Model) viewDetailPanel() string {
stateChanges := m.engine.GetStateChanges(site.ID, 5)
if len(stateChanges) > 0 {
b.WriteString("\n" + subtleStyle.Render(" STATE CHANGES") + "\n")
for _, sc := range stateChanges {
for i, sc := range stateChanges {
ago := fmtDuration(time.Since(sc.ChangedAt))
arrow := subtleStyle.Render(sc.FromStatus) + " → "
if sc.ToStatus == "UP" {
@@ -185,11 +185,15 @@ func (m Model) viewDetailPanel() string {
arrow += dangerStyle.Render(sc.ToStatus)
}
line := fmt.Sprintf(" %s %s", arrow, subtleStyle.Render(ago+" ago"))
if dur := computeOutageDuration(stateChanges, i); dur > 0 {
line += " " + warnStyle.Render("outage "+fmtDuration(dur))
}
if sc.ErrorReason != "" && sc.ToStatus != "UP" {
line += " " + dangerStyle.Render(sc.ErrorReason)
}
b.WriteString(line + "\n")
}
b.WriteString(" " + subtleStyle.Render("[h] History") + "\n")
}
b.WriteString("\n")
@@ -235,7 +239,7 @@ func (m Model) viewDetailPanel() string {
}
b.WriteString("\n\n")
b.WriteString(subtleStyle.Render(" [i/Esc] Back [e] Edit [q] Quit"))
b.WriteString(subtleStyle.Render(" [i/Esc] Back [e] Edit [h] History [q] Quit"))
return lipgloss.NewStyle().Padding(1, 2).Render(b.String())
}