diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-25 09:23:19 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-25 09:23:19 +0200 |
| commit | 1279ffb8f2efba54ff005cce91ba65c149cb1ee6 (patch) | |
| tree | 102483e8d836501b3b935e0674d6608fbe9f4f1f /internal/tui/dashboard | |
| parent | b3625cc67c81b4c1bd654a9fcdaf624d76306b07 (diff) | |
Improve TUI layout and increase sparkline height
Diffstat (limited to 'internal/tui/dashboard')
| -rw-r--r-- | internal/tui/dashboard/files.go | 18 | ||||
| -rw-r--r-- | internal/tui/dashboard/files_test.go | 7 | ||||
| -rw-r--r-- | internal/tui/dashboard/histogram.go | 9 | ||||
| -rw-r--r-- | internal/tui/dashboard/model.go | 6 | ||||
| -rw-r--r-- | internal/tui/dashboard/model_test.go | 6 | ||||
| -rw-r--r-- | internal/tui/dashboard/overview.go | 3 | ||||
| -rw-r--r-- | internal/tui/dashboard/sparkline.go | 7 | ||||
| -rw-r--r-- | internal/tui/dashboard/tabs.go | 7 | ||||
| -rw-r--r-- | internal/tui/dashboard/tabs_test.go | 6 |
9 files changed, 41 insertions, 28 deletions
diff --git a/internal/tui/dashboard/files.go b/internal/tui/dashboard/files.go index 945869e..faade8d 100644 --- a/internal/tui/dashboard/files.go +++ b/internal/tui/dashboard/files.go @@ -17,18 +17,19 @@ func renderFilesWithOffset(snap *statsengine.Snapshot, width, height, offset int return "Files: waiting for stats..." } - rows := fileRows(snap.Files()) + pathWidth := filePathWidth(width) + rows := fileRows(snap.Files(), pathWidth) if len(rows) == 0 { return "Files: no data" } columns := []table.Column{ - {Title: "Path", Width: filePathWidth(width)}, {Title: "Accesses", Width: 8}, {Title: "Read", Width: 9}, {Title: "Write", Width: 9}, {Title: "Avg Latency", Width: 11}, {Title: "Max Latency", Width: 11}, + {Title: "Path", Width: pathWidth}, } tbl := table.New( @@ -43,16 +44,16 @@ func renderFilesWithOffset(snap *statsengine.Snapshot, width, height, offset int return tbl.View() + fmt.Sprintf("\nRow %d/%d", cursor+1, len(rows)) } -func fileRows(files []statsengine.FileSnapshot) []table.Row { +func fileRows(files []statsengine.FileSnapshot, pathWidth int) []table.Row { rows := make([]table.Row, 0, len(files)) for _, f := range files { rows = append(rows, table.Row{ - truncatePathMiddle(f.Path, 48), strconv.FormatUint(f.Accesses, 10), formatBytes(float64(f.BytesRead)), formatBytes(float64(f.BytesWritten)), formatDurationNs(f.AvgLatencyNs), formatDurationUintNs(f.MaxLatencyNs), + truncatePathMiddle(f.Path, pathWidth), }) } return rows @@ -62,15 +63,12 @@ func filePathWidth(width int) int { if width <= 0 { return 24 } - // Reserve enough room for non-path columns and table separators so - // latency columns remain visible even on narrower terminals. - w := width - 70 + // Keep fixed metrics visible and let path consume the remaining space. + // Fixed columns sum to 48 chars; reserve extra for separators/padding. + w := width - 58 if w < 14 { return 14 } - if w > 52 { - return 52 - } return w } diff --git a/internal/tui/dashboard/files_test.go b/internal/tui/dashboard/files_test.go index b0a5dbf..6d73b14 100644 --- a/internal/tui/dashboard/files_test.go +++ b/internal/tui/dashboard/files_test.go @@ -49,3 +49,10 @@ func TestTruncatePathMiddle(t *testing.T) { t.Fatalf("expected head and tail preservation, got %q", got) } } + +func TestFilePathWidthExpandsOnWideTerminal(t *testing.T) { + got := filePathWidth(180) + if got <= 80 { + t.Fatalf("expected wide path column to use remaining space, got %d", got) + } +} diff --git a/internal/tui/dashboard/histogram.go b/internal/tui/dashboard/histogram.go index b2bb88e..1e68a7b 100644 --- a/internal/tui/dashboard/histogram.go +++ b/internal/tui/dashboard/histogram.go @@ -29,6 +29,15 @@ func renderGapsTab(snap *statsengine.Snapshot, width, height int) string { return strings.Join([]string{hist, spark}, "\n") } +func renderLatencyGapsTab(snap *statsengine.Snapshot, width, height int) string { + if snap == nil { + return common.PanelStyle.Render("Latency+Gaps: waiting for stats...") + } + lat := renderLatencyTab(snap, width, height) + gap := renderGapsTab(snap, width, height) + return strings.Join([]string{lat, gap}, "\n") +} + func renderHistogram(hist statsengine.HistogramSnapshot, title string, width, height int) string { buckets := hist.Buckets() if len(buckets) == 0 { diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index 407802f..78da351 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -127,7 +127,7 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.activeTab = TabLatency handled = true case key.Matches(msg, m.keys.Six): - m.activeTab = TabGaps + m.activeTab = TabStream handled = true case key.Matches(msg, m.keys.Seven): m.activeTab = TabStream @@ -278,9 +278,7 @@ func renderActiveTab(tab Tab, snap *statsengine.Snapshot, streamModel *eventstre case TabProcesses: return renderProcessesWithOffset(snap, width, height, processesOffset) case TabLatency: - return renderLatencyTab(snap, width, height) - case TabGaps: - return renderGapsTab(snap, width, height) + return renderLatencyGapsTab(snap, width, height) default: return common.PanelStyle.Render("Unknown tab") } diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go index b0ce933..29b698d 100644 --- a/internal/tui/dashboard/model_test.go +++ b/internal/tui/dashboard/model_test.go @@ -47,6 +47,12 @@ func TestKeySwitchingChangesActiveTab(t *testing.T) { if model.activeTab != TabStream { t.Fatalf("expected stream tab on key 7, got %v", model.activeTab) } + + next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'6'}}) + model = next.(Model) + if model.activeTab != TabStream { + t.Fatalf("expected stream tab on key 6, got %v", model.activeTab) + } } func TestArrowAndViKeysCycleTabs(t *testing.T) { diff --git a/internal/tui/dashboard/overview.go b/internal/tui/dashboard/overview.go index 1bdb64f..9feafab 100644 --- a/internal/tui/dashboard/overview.go +++ b/internal/tui/dashboard/overview.go @@ -214,8 +214,5 @@ func sparklineWidth(width int) int { if w < 8 { return 8 } - if w > 80 { - return 80 - } return w } diff --git a/internal/tui/dashboard/sparkline.go b/internal/tui/dashboard/sparkline.go index 1531ca6..9c1f2c4 100644 --- a/internal/tui/dashboard/sparkline.go +++ b/internal/tui/dashboard/sparkline.go @@ -11,8 +11,10 @@ func renderSparkline(data []float64, width int) string { samples := sampleForWidth(data, width) min, max := minMax(samples) + line := "" if min == max { - return repeatRune('▄', len(samples)) + line = repeatRune('▄', len(samples)) + return line + "\n" + line } out := make([]rune, len(samples)) @@ -28,7 +30,8 @@ func renderSparkline(data []float64, width int) string { } out[i] = sparkChars[idx] } - return string(out) + line = string(out) + return line + "\n" + line } func sampleForWidth(data []float64, width int) []float64 { diff --git a/internal/tui/dashboard/tabs.go b/internal/tui/dashboard/tabs.go index 9aae218..a2fe366 100644 --- a/internal/tui/dashboard/tabs.go +++ b/internal/tui/dashboard/tabs.go @@ -22,8 +22,6 @@ const ( TabProcesses // TabLatency is the latency histogram tab. TabLatency - // TabGaps is the inter-syscall gap tab. - TabGaps // TabStream is the live event stream tab. TabStream ) @@ -34,7 +32,6 @@ var allTabs = []Tab{ TabFiles, TabProcesses, TabLatency, - TabGaps, TabStream, } @@ -49,9 +46,7 @@ func (t Tab) String() string { case TabProcesses: return "Processes" case TabLatency: - return "Latency" - case TabGaps: - return "Gaps" + return "Latency+Gaps" case TabStream: return "Stream" default: diff --git a/internal/tui/dashboard/tabs_test.go b/internal/tui/dashboard/tabs_test.go index d40cc68..0fc36f2 100644 --- a/internal/tui/dashboard/tabs_test.go +++ b/internal/tui/dashboard/tabs_test.go @@ -6,8 +6,8 @@ import ( ) func TestTabNavigationWraps(t *testing.T) { - if got := nextTab(TabGaps); got != TabStream { - t.Fatalf("expected next after gaps to be stream, got %v", got) + if got := nextTab(TabLatency); got != TabStream { + t.Fatalf("expected next after latency+gaps to be stream, got %v", got) } if got := nextTab(TabStream); got != TabOverview { t.Fatalf("expected wrap to overview from stream, got %v", got) @@ -19,7 +19,7 @@ func TestTabNavigationWraps(t *testing.T) { func TestRenderTabBarContainsLabels(t *testing.T) { out := renderTabBar(TabOverview, 80) - for _, label := range []string{"Overview", "Syscalls", "Files", "Processes", "Latency", "Gaps", "Stream"} { + for _, label := range []string{"Overview", "Syscalls", "Files", "Processes", "Latency+Gaps", "Stream"} { if !strings.Contains(out, label) { t.Fatalf("expected tab label %q in tab bar", label) } |
