diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-24 08:32:39 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-24 08:32:39 +0200 |
| commit | 9c400937d9e32f3ce85c668d9ca52c351f8b5d13 (patch) | |
| tree | 90170eedda64c59a063c0010d1452f6b3e5d71f5 /internal/tui/dashboard/model.go | |
| parent | ba7af922d289a9d0fff1c4ef33764b1852c774f6 (diff) | |
tui: add dashboard tab framework model
Diffstat (limited to 'internal/tui/dashboard/model.go')
| -rw-r--r-- | internal/tui/dashboard/model.go | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go new file mode 100644 index 0000000..3c4d9b8 --- /dev/null +++ b/internal/tui/dashboard/model.go @@ -0,0 +1,151 @@ +package dashboard + +import ( + "fmt" + "ior/internal/statsengine" + "ior/internal/tui" + "ior/internal/tui/messages" + "strings" + "time" + + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +const defaultRefreshMs = 1000 + +// SnapshotSource is the dashboard data source. +type SnapshotSource interface { + Snapshot() *statsengine.Snapshot +} + +type refreshTickMsg struct{} + +// Model is the dashboard tab framework model. +type Model struct { + activeTab Tab + + engine SnapshotSource + latest *statsengine.Snapshot + + width int + height int + + refreshEvery time.Duration + keys tui.KeyMap +} + +// NewModel creates a dashboard model with default refresh cadence. +func NewModel(engine SnapshotSource) Model { + return NewModelWithConfig(engine, defaultRefreshMs, tui.Keys) +} + +// NewModelWithConfig creates a dashboard model with explicit refresh and keys. +func NewModelWithConfig(engine SnapshotSource, refreshMs int, keys tui.KeyMap) Model { + if refreshMs <= 0 { + refreshMs = defaultRefreshMs + } + return Model{ + activeTab: TabOverview, + engine: engine, + refreshEvery: time.Duration(refreshMs) * time.Millisecond, + keys: keys, + } +} + +// Init starts periodic refresh ticks. +func (m Model) Init() tea.Cmd { + return tickCmd(m.refreshEvery) +} + +// Update handles ticks, snapshots, tab changes, and resize events. +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + case refreshTickMsg: + snap := m.snapshot() + return m, tea.Batch( + tickCmd(m.refreshEvery), + func() tea.Msg { return messages.StatsTickMsg{Snap: snap} }, + ) + case messages.StatsTickMsg: + m.latest = msg.Snap + return m, nil + case tea.KeyMsg: + return m.handleKey(msg) + } + return m, nil +} + +func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { + switch { + case key.Matches(msg, m.keys.Tab): + m.activeTab = nextTab(m.activeTab) + case key.Matches(msg, m.keys.ShiftTab): + m.activeTab = prevTab(m.activeTab) + case key.Matches(msg, m.keys.One): + m.activeTab = TabOverview + case key.Matches(msg, m.keys.Two): + m.activeTab = TabSyscalls + case key.Matches(msg, m.keys.Three): + m.activeTab = TabFiles + case key.Matches(msg, m.keys.Four): + m.activeTab = TabProcesses + case key.Matches(msg, m.keys.Five): + m.activeTab = TabLatency + case key.Matches(msg, m.keys.Six): + m.activeTab = TabGaps + } + return m, nil +} + +func (m Model) snapshot() *statsengine.Snapshot { + if m.engine == nil { + return nil + } + return m.engine.Snapshot() +} + +// View renders the tab bar, active tab scaffold, and help bar. +func (m Model) View() string { + var b strings.Builder + b.WriteString(renderTabBar(m.activeTab, m.width)) + b.WriteString("\n") + b.WriteString(renderActiveTab(m.activeTab, m.latest, m.width, m.height)) + b.WriteString("\n") + b.WriteString(renderHelpBar(m.keys)) + return tui.ScreenStyle.Render(b.String()) +} + +func tickCmd(d time.Duration) tea.Cmd { + return tea.Tick(d, func(time.Time) tea.Msg { return refreshTickMsg{} }) +} + +func renderActiveTab(tab Tab, snap *statsengine.Snapshot, width, height int) string { + _ = width + _ = height + + if snap == nil { + return tui.PanelStyle.Render(tab.String() + ": waiting for stats...") + } + + switch tab { + case TabOverview: + return tui.PanelStyle.Render(fmt.Sprintf("Overview: %d syscalls", snap.TotalSyscalls)) + case TabSyscalls: + return tui.PanelStyle.Render(fmt.Sprintf("Syscalls: %d rows", len(snap.Syscalls()))) + case TabFiles: + return tui.PanelStyle.Render(fmt.Sprintf("Files: %d rows", len(snap.Files()))) + case TabProcesses: + return tui.PanelStyle.Render(fmt.Sprintf("Processes: %d rows", len(snap.Processes()))) + case TabLatency: + return tui.PanelStyle.Render("Latency histogram") + case TabGaps: + return tui.PanelStyle.Render("Gap histogram") + default: + return tui.PanelStyle.Render("Unknown tab") + } +} |
