dc79e2baaa
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.
106 lines
1.9 KiB
Go
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()
|
|
}
|