| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- // A simple TUI application that charts latency times to specified hosts.
- package main
- import (
- "flag"
- "fmt"
- "os"
- "pingo"
- "time"
- "charm.land/bubbles/v2/viewport"
- tea "charm.land/bubbletea/v2"
- "charm.land/lipgloss/v2"
- )
- // the bubbletea.Model for the executable
- type Model struct {
- viewport viewport.Model
- speed time.Duration
- p pingo.Peak
- }
- // timingMsg is used for tracking Peak.Poll timing intervals, emitted by an
- // anonymous function in the update function.
- type timingMsg time.Time
- // lipgloss styles for the tui
- var (
- // footer styles
- titleStyle = lipgloss.NewStyle().
- Align(lipgloss.Center). // implies consumer functions will apply a width
- Italic(true).
- Faint(true)
- // footer style
- footerStyle = lipgloss.NewStyle().
- Align(lipgloss.Center). // implies consumer functions will apply a width
- Italic(true).
- Faint(true)
- )
- // The tea.Cmd command to be called upon model initialization by bubbletea.
- func (m Model) Init() tea.Cmd {
- return tea.Tick(m.speed*time.Millisecond, func(t time.Time) tea.Msg { return timingMsg(t) })
- }
- // The core Update loop function.
- func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- // data prep
- var cmds []tea.Cmd // final return
- var cmd tea.Cmd
- switch msg := msg.(type) {
- case tea.WindowSizeMsg: // handle window resizing
- // update pingo.Peak bubble
- m.p.Width = msg.Width
- m.p.Height = msg.Height - m.getVerticalMargin() - 1
- // if the viewport has not been initialized
- if m.viewport.Width() == 0 && m.viewport.Height() == 0 {
- m.viewport = viewport.New( // init viewport
- viewport.WithHeight(msg.Height-m.getVerticalMargin()),
- viewport.WithWidth(msg.Width),
- )
- m.viewport.YPosition = 1 // tell viewport to render after an empty line
- } else {
- m.viewport.SetHeight(msg.Height - m.getVerticalMargin())
- m.viewport.SetWidth(msg.Width)
- }
- case tea.KeyPressMsg: // handle quit
- k := msg.String()
- if k == "ctrl+c" {
- return m, tea.Quit
- }
- case timingMsg: // handle timingMsg (timing loop)
- cmd = tea.Tick(m.speed*time.Millisecond, func(t time.Time) tea.Msg { return timingMsg(t) })
- cmds = append(cmds, tea.Sequence(m.p.Poll(), cmd))
- }
- // update viewport bubble
- m.viewport.SetContent(m.p.View().Content)
- m.viewport, cmd = m.viewport.Update(msg)
- cmds = append(cmds, cmd)
- // update Peak bubble
- m.p, cmd = m.p.Update(msg)
- cmds = append(cmds, cmd)
- return m, tea.Batch(cmds...)
- }
- func (m Model) View() tea.View {
- m.viewport.SetContent(m.p.View().Content)
- content := fmt.Sprintf("%s%s\n%s", m.header(), m.viewport.View(), m.footer())
- var v tea.View
- v.SetContent(content)
- v.AltScreen = true
- return v
- }
- func (m Model) header() string { return titleStyle.Width(m.viewport.Width()).Render("pingo v0") }
- func (m Model) footer() string {
- return footerStyle.Width(m.viewport.Width()).Render("j/k: down/up\t|\tctrl+c: quit")
- }
- // get lipgloss.Height value of header and footer
- func (m Model) getVerticalMargin() int { return lipgloss.Height(m.header() + m.footer()) }
- func main() {
- // get timing interval in milliseconds
- speed := flag.Int("s", 80, "the interval in milliseconds between pings")
- chartHeight := flag.Int("h", 0,
- "the height of the latency chart. set to 0 to render charts full screen.")
- flag.Parse()
- hosts := flag.Args()
- if len(hosts) == 0 {
- fmt.Println("Must specify hosts!")
- return
- }
- if *speed < 1 {
- fmt.Println("speed must not be below 1")
- os.Exit(1)
- }
- p := tea.NewProgram(Model{
- p: pingo.InitialPeakBubble(hosts, 20, 10, *chartHeight),
- speed: time.Duration(*speed),
- })
- if _, err := p.Run(); err != nil {
- fmt.Printf("Alas, there's been an error: %v", err)
- os.Exit(1)
- }
- }
|