|
@@ -1,9 +1,52 @@
|
|
|
-// TODO document TUI lifecycle
|
|
|
|
|
-// TODO viewport separation
|
|
|
|
|
|
|
+// Pingo defines an extensible TUI based on the bubbletea framework (v2). An
|
|
|
|
|
+// executable entry point is defined via cmd/pingo.go. For more on this topic,
|
|
|
|
|
+// see the Readme.
|
|
|
//
|
|
//
|
|
|
-// BLOCKERS: header/footer height func
|
|
|
|
|
|
|
+// The pingo TUI is a fully fledged and extensible "bubble" (read: widget) that
|
|
|
|
|
+// can be fully implemented as an element in further terminal applications.
|
|
|
//
|
|
//
|
|
|
-// TODO header/footer height func
|
|
|
|
|
|
|
+// NOTE: the bubbletea framework and subsequent "bubble" concept are beyond the
|
|
|
|
|
+// scope of this documentation. For more, see the Readme.
|
|
|
|
|
+//
|
|
|
|
|
+// Pingo defines a constructor function for the Model. It takes three arguments:
|
|
|
|
|
+//
|
|
|
|
|
+// - addresses([]string): the hosts to ping. The length must be greater than
|
|
|
|
|
+// or equal to 1
|
|
|
|
|
+//
|
|
|
|
|
+// - speed(time.Duration): the polling interval
|
|
|
|
|
+//
|
|
|
|
|
+// - chartHeight(int): the desired height of the resulting charts. This
|
|
|
|
|
+// argument is integral to ensuring desired rendering of charts, when
|
|
|
|
|
+// displaying multiple hosts.
|
|
|
|
|
+//
|
|
|
|
|
+// NOTE: if chartHeight is 0, the chart will render to Model.Height
|
|
|
|
|
+//
|
|
|
|
|
+// NOTE: chartHeight is ignored when only one address or host is provided
|
|
|
|
|
+//
|
|
|
|
|
+// For more, please please see InitialModel()
|
|
|
|
|
+//
|
|
|
|
|
+// Pingo defines two bubbletea.Cmd functions:
|
|
|
|
|
+//
|
|
|
|
|
+// - Model.Tick() tea.Cmd: emits a bubbletea.TickMsg after the time.Duration
|
|
|
|
|
+// specified via Model.UpdateSpeed
|
|
|
|
|
+//
|
|
|
|
|
+// NOTE: Model.Tick() is optional. If you choose not to use Model.Tick(),
|
|
|
|
|
+// it is recommended to enforce some minimum rate mechanism for calling
|
|
|
|
|
+// Poll(). Some servers maintain a ping rate limit, and is is possible to
|
|
|
|
|
+// exceed this rate trivially with the Poll() function. (Trust us, we know
|
|
|
|
|
+// from experience)
|
|
|
|
|
+//
|
|
|
|
|
+// NOTE: Model.Tick() is automatically emit by Model.Init() - therefore,
|
|
|
|
|
+// you can control the timing of polling by overloading the Init function.
|
|
|
|
|
+//
|
|
|
|
|
+// - Model.Poll() tea.Msg: used to asynchronously call all Model.Addresses.Poll()
|
|
|
|
|
+// functions.
|
|
|
|
|
+//
|
|
|
|
|
+// NOTE: Model.Poll() is automatically injected into the Model.Update()
|
|
|
|
|
+// life cycle after Model.Tick() resolves by Model.Update(). Functionally,
|
|
|
|
|
+// this means you can omit either Model.Tick() or Model.Poll(), respectively.
|
|
|
|
|
+//
|
|
|
|
|
+// For more, see the Readme or ./examples
|
|
|
package pingo
|
|
package pingo
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
@@ -17,7 +60,7 @@ import (
|
|
|
"github.com/NimbleMarkets/ntcharts/linechart/streamlinechart"
|
|
"github.com/NimbleMarkets/ntcharts/linechart/streamlinechart"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// Style Defintions
|
|
|
|
|
|
|
+// Style Definitions
|
|
|
var (
|
|
var (
|
|
|
// A style for chart headers
|
|
// A style for chart headers
|
|
|
headerStyle = lipgloss.NewStyle().
|
|
headerStyle = lipgloss.NewStyle().
|
|
@@ -70,13 +113,12 @@ type ( // tea.Msg signatures
|
|
|
|
|
|
|
|
// Bubbletea model
|
|
// Bubbletea model
|
|
|
type Model struct {
|
|
type Model struct {
|
|
|
- width int
|
|
|
|
|
Addresses []Address // as defined in internal/tui/types.go
|
|
Addresses []Address // as defined in internal/tui/types.go
|
|
|
viewport viewport.Model
|
|
viewport viewport.Model
|
|
|
UpdateSpeed time.Duration
|
|
UpdateSpeed time.Duration
|
|
|
ChartHeight int
|
|
ChartHeight int
|
|
|
- ModelHeight int
|
|
|
|
|
- ModelWidth int
|
|
|
|
|
|
|
+ Height int
|
|
|
|
|
+ Width int
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func InitialModel(addresses []string, speed time.Duration, chartHeight int) Model {
|
|
func InitialModel(addresses []string, speed time.Duration, chartHeight int) Model {
|
|
@@ -111,21 +153,21 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
switch msg := msg.(type) {
|
|
switch msg := msg.(type) {
|
|
|
// if case is KeyMsg (keypress)
|
|
// if case is KeyMsg (keypress)
|
|
|
case tea.WindowSizeMsg:
|
|
case tea.WindowSizeMsg:
|
|
|
- if m.ModelWidth == 0 && m.ModelHeight == 0 {
|
|
|
|
|
|
|
+ if m.Width == 0 && m.Height == 0 {
|
|
|
m.viewport = viewport.New(
|
|
m.viewport = viewport.New(
|
|
|
viewport.WithHeight(10),
|
|
viewport.WithHeight(10),
|
|
|
viewport.WithWidth(msg.Width),
|
|
viewport.WithWidth(msg.Width),
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- m.ModelWidth = msg.Width
|
|
|
|
|
- m.ModelHeight = msg.Height
|
|
|
|
|
|
|
+ m.Width = msg.Width
|
|
|
|
|
+ m.Height = msg.Height
|
|
|
for i, address := range m.Addresses {
|
|
for i, address := range m.Addresses {
|
|
|
- address.MaxResults = m.ModelWidth
|
|
|
|
|
|
|
+ address.MaxResults = m.Width
|
|
|
m.Addresses[i] = address
|
|
m.Addresses[i] = address
|
|
|
}
|
|
}
|
|
|
- m.viewport.SetHeight(m.ModelHeight - m.getVerticalMargin())
|
|
|
|
|
- m.viewport.SetWidth(m.ModelWidth)
|
|
|
|
|
|
|
+ m.viewport.SetHeight(m.Height - m.getVerticalMargin())
|
|
|
|
|
+ m.viewport.SetWidth(m.Width)
|
|
|
m.viewport.YPosition = 1
|
|
m.viewport.YPosition = 1
|
|
|
|
|
|
|
|
case tea.KeyPressMsg:
|
|
case tea.KeyPressMsg:
|
|
@@ -167,9 +209,9 @@ func (m Model) Render() string {
|
|
|
for _, address := range m.Addresses {
|
|
for _, address := range m.Addresses {
|
|
|
if len(address.Results) == 0 {
|
|
if len(address.Results) == 0 {
|
|
|
output = output + fmt.Sprintf("\n%s\tloading...", headerStyle.Render(address.Address))
|
|
output = output + fmt.Sprintf("\n%s\tloading...", headerStyle.Render(address.Address))
|
|
|
- } else if m.ModelWidth != 0 && m.ModelHeight != 0 {
|
|
|
|
|
|
|
+ } else if m.Width != 0 && m.Height != 0 {
|
|
|
if slices.Contains(address.Results, -1) {
|
|
if slices.Contains(address.Results, -1) {
|
|
|
- output = output + blockStyle.Width(m.ModelWidth).Render(headerStyle.Render(
|
|
|
|
|
|
|
+ output = output + blockStyle.Width(m.Width).Render(headerStyle.Render(
|
|
|
fmt.Sprintf("\n%s\t%s",
|
|
fmt.Sprintf("\n%s\t%s",
|
|
|
secondaryColor.Render(address.Address),
|
|
secondaryColor.Render(address.Address),
|
|
|
infoStyle.Render("(connection unstable)"),
|
|
infoStyle.Render("(connection unstable)"),
|
|
@@ -177,23 +219,23 @@ func (m Model) Render() string {
|
|
|
))
|
|
))
|
|
|
} else {
|
|
} else {
|
|
|
output = output + fmt.Sprintf("\n%s",
|
|
output = output + fmt.Sprintf("\n%s",
|
|
|
- blockStyle.Width(m.ModelWidth).Render(headerStyle.Render(address.Address)))
|
|
|
|
|
|
|
+ blockStyle.Width(m.Width).Render(headerStyle.Render(address.Address)))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Linechart
|
|
// Linechart
|
|
|
// set chartHeight - vertical margin
|
|
// set chartHeight - vertical margin
|
|
|
- chartHeight := m.ModelHeight - m.getVerticalMargin()
|
|
|
|
|
|
|
+ chartHeight := m.Height - m.getVerticalMargin()
|
|
|
|
|
|
|
|
var slc streamlinechart.Model
|
|
var slc streamlinechart.Model
|
|
|
|
|
|
|
|
if m.ChartHeight == 0 && len(m.Addresses) == 1 { // catch user specified fullscreen
|
|
if m.ChartHeight == 0 && len(m.Addresses) == 1 { // catch user specified fullscreen
|
|
|
// render chart at fullscreen
|
|
// render chart at fullscreen
|
|
|
- slc = streamlinechart.New(m.ModelWidth, chartHeight)
|
|
|
|
|
|
|
+ slc = streamlinechart.New(m.Width, chartHeight)
|
|
|
} else if m.ChartHeight == 0 && len(m.Addresses) > 1 { // catch user specified fullscreen
|
|
} else if m.ChartHeight == 0 && len(m.Addresses) > 1 { // catch user specified fullscreen
|
|
|
// render chart at fullscreen minus a few lines to hint at scrolling
|
|
// render chart at fullscreen minus a few lines to hint at scrolling
|
|
|
- slc = streamlinechart.New(m.ModelWidth, chartHeight-5)
|
|
|
|
|
|
|
+ slc = streamlinechart.New(m.Width, chartHeight-5)
|
|
|
} else {
|
|
} else {
|
|
|
- slc = streamlinechart.New(m.ModelWidth, m.ChartHeight)
|
|
|
|
|
|
|
+ slc = streamlinechart.New(m.Width, m.ChartHeight)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for _, v := range address.Results {
|
|
for _, v := range address.Results {
|
|
@@ -208,10 +250,10 @@ func (m Model) Render() string {
|
|
|
return output
|
|
return output
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (m Model) header() string { return titleStyle.Width(m.ModelWidth).Render("pingo v0") }
|
|
|
|
|
|
|
+func (m Model) header() string { return titleStyle.Width(m.Width).Render("pingo v0") }
|
|
|
|
|
|
|
|
func (m Model) footer() string {
|
|
func (m Model) footer() string {
|
|
|
- return footerStyle.Width(m.ModelWidth).Render("j/k: down/up\t|\tq/ctrl-c/esc: quit")
|
|
|
|
|
|
|
+ return footerStyle.Width(m.Width).Render("j/k: down/up\t|\tq/ctrl-c/esc: quit")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (m Model) getVerticalMargin() int { return lipgloss.Height(m.header() + m.footer()) }
|
|
func (m Model) getVerticalMargin() int { return lipgloss.Height(m.header() + m.footer()) }
|