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