Fix high prio recommendations, add error / health screen

This commit is contained in:
2026-07-28 15:40:11 +02:00
parent 948f3e1a79
commit 7511311297
19 changed files with 1885 additions and 148 deletions

157
health.go Normal file
View File

@@ -0,0 +1,157 @@
package main
import (
"context"
"fmt"
"sort"
"strings"
"sync"
"time"
)
type requestCoordinator struct {
mu sync.Mutex
id uint64
cancel context.CancelFunc
}
func (r *requestCoordinator) start(timeout time.Duration) (context.Context, context.CancelFunc, uint64) {
r.mu.Lock()
defer r.mu.Unlock()
if r.cancel != nil {
r.cancel()
}
r.id++
ctx, cancel := context.WithTimeout(context.Background(), timeout)
r.cancel = cancel
return ctx, cancel, r.id
}
func (r *requestCoordinator) current(id uint64) bool {
if id == 0 {
return true
}
r.mu.Lock()
defer r.mu.Unlock()
return r.id == id
}
type HealthLevel string
const (
healthOK HealthLevel = "ok"
healthInfo HealthLevel = "info"
healthWarning HealthLevel = "warning"
healthError HealthLevel = "error"
healthUnknown HealthLevel = "unknown"
)
type HealthComponent struct {
Name string
Level HealthLevel
Summary string
Detail string
UpdatedAt time.Time
}
type HealthEvent struct {
Component string
Level HealthLevel
Message string
At time.Time
}
type RateLimitSnapshot struct {
Limit int
Remaining int
Used int
ResetAt time.Time
RetryAfter time.Time
UpdatedAt time.Time
}
type healthProvider interface {
HealthReport() []HealthComponent
RateLimit() RateLimitSnapshot
}
type healthTracker struct {
mu sync.Mutex
components map[string]HealthComponent
rate RateLimitSnapshot
}
func (h *healthTracker) set(component HealthComponent) {
h.mu.Lock()
defer h.mu.Unlock()
if h.components == nil {
h.components = make(map[string]HealthComponent)
}
if component.UpdatedAt.IsZero() {
component.UpdatedAt = time.Now()
}
h.components[component.Name] = component
}
func (h *healthTracker) setRate(rate RateLimitSnapshot) {
h.mu.Lock()
defer h.mu.Unlock()
h.rate = rate
}
func (h *healthTracker) report() []HealthComponent {
h.mu.Lock()
defer h.mu.Unlock()
components := make([]HealthComponent, 0, len(h.components))
for _, component := range h.components {
components = append(components, component)
}
sort.Slice(components, func(i, j int) bool {
return strings.ToLower(components[i].Name) < strings.ToLower(components[j].Name)
})
return components
}
func (h *healthTracker) rateLimit() RateLimitSnapshot {
h.mu.Lock()
defer h.mu.Unlock()
return h.rate
}
func (m *App) recordHealth(component string, level HealthLevel, message string) {
if strings.TrimSpace(message) == "" {
return
}
event := HealthEvent{Component: component, Level: level, Message: message, At: time.Now()}
const maximumHealthEvents = 100
m.healthEvents = append(m.healthEvents, event)
if len(m.healthEvents) > maximumHealthEvents {
m.healthEvents = append([]HealthEvent(nil), m.healthEvents[len(m.healthEvents)-maximumHealthEvents:]...)
}
}
func healthLevelLabel(level HealthLevel) string {
switch level {
case healthOK:
return "OK"
case healthInfo:
return "INFO"
case healthWarning:
return "WARN"
case healthError:
return "ERROR"
default:
return "UNKNOWN"
}
}
func healthComponentText(component HealthComponent) string {
text := component.Summary
if component.Detail != "" {
text += " — " + component.Detail
}
if !component.UpdatedAt.IsZero() {
text += " (" + component.UpdatedAt.Local().Format("15:04:05") + ")"
}
return fmt.Sprintf("%-7s %-20s %s", healthLevelLabel(component.Level), component.Name, text)
}