Files
uptop/internal/tui/view_history.go
T
lerko dc79e2baaa
CI / test (pull_request) Successful in 1m49s
CI / lint (pull_request) Successful in 1m16s
CI / vulncheck (pull_request) Successful in 56s
refactor(tui): remove full-page detail, SLA, and history views
All monitor detail content now lives in the sidebar panel.
Delete the full-page viewDetailPanel, viewSLAPanel, viewHistoryPanel,
handleDetailKey, handleSLAKey, handleHistoryKey, openSLAView, and
related dead code (histogram, sparkline tooltip, detailViewport).

Remove stateDetail, stateSLA, stateHistory session states.
Update tests to use detailOpen/detailMode instead of removed states.
2026-06-30 09:28:12 -04:00

106 lines
1.9 KiB
Go

package tui
import (
"strings"
"time"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
)
type historyStats struct {
totalEvents int
outageCount int
totalDowntime time.Duration
}
func computeOutageDuration(changes []models.StateChange, idx int) time.Duration {
sc := changes[idx]
if sc.ToStatus != string(models.StatusUp) {
return 0
}
if idx+1 >= len(changes) {
return 0
}
prev := changes[idx+1]
if prev.ToStatus == string(models.StatusUp) {
return 0
}
dur := sc.ChangedAt.Sub(prev.ChangedAt)
if dur < 0 {
return 0
}
return dur
}
func computeHistoryStats(changes []models.StateChange) historyStats {
var s historyStats
s.totalEvents = len(changes)
for i := range changes {
dur := computeOutageDuration(changes, i)
if dur > 0 {
s.outageCount++
s.totalDowntime += dur
}
}
return s
}
var stateChangeChars = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
func (m Model) stateChangeSparkline(changes []models.StateChange, width int) string {
if len(changes) < 2 || width < 4 {
return ""
}
oldest := changes[len(changes)-1].ChangedAt
newest := changes[0].ChangedAt
span := newest.Sub(oldest)
if span <= 0 {
return ""
}
buckets := make([]int, width)
for _, sc := range changes {
pos := int(float64(sc.ChangedAt.Sub(oldest)) / float64(span) * float64(width-1))
if pos >= width {
pos = width - 1
}
if pos < 0 {
pos = 0
}
buckets[pos]++
}
maxVal := 0
for _, v := range buckets {
if v > maxVal {
maxVal = v
}
}
if maxVal == 0 {
return ""
}
var sb strings.Builder
for _, v := range buckets {
if v == 0 {
sb.WriteRune('·')
continue
}
idx := int(float64(v) / float64(maxVal) * 7)
if idx > 7 {
idx = 7
}
ch := string(stateChangeChars[idx])
switch {
case v >= 3:
sb.WriteString(m.st.dangerStyle.Render(ch))
case v >= 2:
sb.WriteString(m.st.warnStyle.Render(ch))
default:
sb.WriteString(m.st.subtleStyle.Render(ch))
}
}
return sb.String()
}