From ad469c86eba063fdf3e43acd81800e81a8f1da23 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Sun, 21 Jun 2026 13:05:02 -0400 Subject: [PATCH] feat(tui): ntcharts latency line chart in inline detail panel Replace block-element sparkline with ntcharts streamline chart in the inline detail panel. Renders a 4-row line chart with thin line style using the theme accent color. Auto-scales Y axis to latency range. Added github.com/NimbleMarkets/ntcharts v0.5.1 dependency (lipgloss v1 compatible). Min/Avg/Max stats rendered below the chart. --- go.mod | 1 + go.sum | 2 ++ internal/tui/chart.go | 31 ++++++++++++++++++++++++++++++ internal/tui/update.go | 2 +- internal/tui/view_detail_inline.go | 18 +++++++++-------- 5 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 internal/tui/chart.go diff --git a/go.mod b/go.mod index 5f8b4a9..3824c06 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitea.lerkolabs.com/lerkolabs/uptop go 1.26.4 require ( + github.com/NimbleMarkets/ntcharts v0.5.1 github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/harmonica v0.2.0 diff --git a/go.sum b/go.sum index 5c69223..f00d21c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/NimbleMarkets/ntcharts v0.5.1 h1:HWtekubEXfESwi24pyFynwGo2Hulbb9fPh7INMUc1dg= +github.com/NimbleMarkets/ntcharts v0.5.1/go.mod h1:zVeRqYkh2n59YPe1bflaSL4O2aD2ZemNmrbdEqZ70hk= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= diff --git a/internal/tui/chart.go b/internal/tui/chart.go new file mode 100644 index 0000000..4a9594d --- /dev/null +++ b/internal/tui/chart.go @@ -0,0 +1,31 @@ +package tui + +import ( + "time" + + "github.com/NimbleMarkets/ntcharts/canvas/runes" + "github.com/NimbleMarkets/ntcharts/linechart/streamlinechart" + "github.com/charmbracelet/lipgloss" +) + +func (m Model) latencyChart(latencies []time.Duration, statuses []bool, width, height int) string { + if len(latencies) == 0 || width < 10 || height < 3 { + return "" + } + + lineStyle := lipgloss.NewStyle().Foreground(m.theme.Accent) + slc := streamlinechart.New(width, height, + streamlinechart.WithStyles(runes.ThinLineStyle, lineStyle), + ) + + for i, l := range latencies { + ms := float64(l.Milliseconds()) + if i < len(statuses) && !statuses[i] { + ms = 0 + } + slc.Push(ms) + } + slc.Draw() + + return slc.View() +} diff --git a/internal/tui/update.go b/internal/tui/update.go index 1004871..543f935 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -142,7 +142,7 @@ func (m *Model) handleFormMsg(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -const detailInlineHeight = 8 +const detailInlineHeight = 12 func (m *Model) recalcLayout() { chrome := chromeBase diff --git a/internal/tui/view_detail_inline.go b/internal/tui/view_detail_inline.go index da962c5..4bf394d 100644 --- a/internal/tui/view_detail_inline.go +++ b/internal/tui/view_detail_inline.go @@ -67,14 +67,16 @@ func (m Model) viewDetailInline(width int) string { } if len(hist.Latencies) > 0 { - sparkW := width - 30 - if sparkW < 10 { - sparkW = 10 + chartW := width - 4 + if chartW < 20 { + chartW = 20 } - if sparkW > detailSparkWidth { - sparkW = detailSparkWidth + chartH := 4 + chart := m.latencyChart(hist.Latencies, hist.Statuses, chartW, chartH) + if chart != "" { + b.WriteString(chart + "\n") } - spark := m.latencySparkline(hist.Latencies, hist.Statuses, sparkW, m.theme.Bg) + minMs := hist.Latencies[0].Milliseconds() maxMs := hist.Latencies[0].Milliseconds() var sumMs int64 @@ -89,11 +91,11 @@ func (m Model) viewDetailInline(width int) string { sumMs += ms } avgMs := sumMs / int64(len(hist.Latencies)) - stats := fmt.Sprintf("Min %s Avg %s Max %s", + stats := fmt.Sprintf(" Min %s Avg %s Max %s", m.fmtLatency(time.Duration(minMs)*time.Millisecond), m.fmtLatency(time.Duration(avgMs)*time.Millisecond), m.fmtLatency(time.Duration(maxMs)*time.Millisecond)) - b.WriteString(" " + spark + " " + stats + "\n") + b.WriteString(stats + "\n") } keys := m.st.subtleStyle.Render("[h] History [s] SLA [e] Edit [esc] Close")