Files
go-otel/cmd/issues-tui/tui.go
T
developer a96e95212a
Build and Publish / Metadata (push) Successful in 8s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 2m16s
issues-tui: wide, short terminal dashboard for current issues
Standalone cmd that runs the same pipeline as the webhook notifications
(alerts.LoadErrorEvents -> alerts.DetectAlerts -> cooldown suppressor) on
a schedule and renders what is firing as a row of severity-colored cards -
built for an ultra-wide, low display. A pattern shows PAGED when the scan
would have notified and ongoing while suppressed; disappearing spikes are
reported as recovered. -once prints one snapshot for cron/pipe use.
Colors follow the otel-ui design-system tokens (dark and light).
2026-09-24 09:00:51 +00:00

96 lines
1.9 KiB
Go

package main
import (
"context"
"time"
tea "github.com/charmbracelet/bubbletea"
)
// modelConfig is the static configuration the dashboard needs beyond the
// scanner.
type modelConfig struct {
Interval time.Duration
Title string
BaseURL string
}
// tickMsg drives the clock and the scan countdown.
type tickMsg time.Time
// snapshotMsg carries a finished scan back into the model.
type snapshotMsg snapshot
type model struct {
sc *scanner
cfg modelConfig
snap snapshot
scanning bool
nextScanAt time.Time
width, height int
ready bool
}
func newModel(sc *scanner, cfg modelConfig) model {
return model{sc: sc, cfg: cfg, nextScanAt: time.Now()}
}
func (m model) Init() tea.Cmd {
return tea.Batch(tickClock(), m.scanCmd())
}
func tickClock() tea.Cmd {
return tea.Tick(time.Second, func(t time.Time) tea.Msg { return tickMsg(t) })
}
func (m *model) scanCmd() tea.Cmd {
sc := m.sc
return func() tea.Msg { return snapshotMsg(sc.Scan(context.Background(), time.Now())) }
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width, m.height, m.ready = msg.Width, msg.Height, true
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
return m, tea.Quit
case "r":
if !m.scanning {
m.scanning = true
return m, m.scanCmd()
}
return m, nil
}
return m, nil
case tickMsg:
now := time.Time(msg)
cmd := tickClock()
if !m.scanning && !now.Before(m.nextScanAt) {
m.scanning = true
return m, tea.Batch(cmd, m.scanCmd())
}
return m, cmd
case snapshotMsg:
m.snap = snapshot(msg)
m.scanning = false
m.nextScanAt = time.Now().Add(m.cfg.Interval)
return m, nil
}
return m, nil
}
func (m model) View() string {
return renderSnapshot(m.snap, snapshotOptions{
Width: m.width, Height: m.height, Title: m.cfg.Title, BaseURL: m.cfg.BaseURL,
Scanning: m.scanning, NextScanAt: m.nextScanAt, Now: time.Now(),
})
}