Files
uptop/internal/tui/tab_logs_sidebar.go
T
lerko 7109b6fa1c feat(tui): panel focus with click, scroll, and keyboard
Click any panel (Monitors, Logs, Detail) to focus it — accent border
follows focus. Mouse wheel scrolls the focused panel.

Keyboard: l toggles log panel focus. Arrow keys scroll logs when
focused, navigate monitors when not. Esc returns focus to monitors.

Log sidebar now supports scroll offset — tracks position across
renders without a viewport. Mouse wheel scrolls 3 lines, keyboard
scrolls 1.
2026-06-20 19:44:35 -04:00

111 lines
2.2 KiB
Go

package tui
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
func (m Model) renderCompactLogLine(line string, maxW int) string {
sev := classifyLog(line)
var tag string
switch sev {
case severityDown:
tag = m.st.dangerStyle.Render("▼")
case severityUp:
tag = m.st.specialStyle.Render("▲")
case severityWarn:
tag = m.st.warnStyle.Render("◆")
case severitySystem:
tag = m.st.titleStyle.Render("●")
default:
tag = m.st.subtleStyle.Render("·")
}
ts := ""
msg := line
if len(line) > 10 && line[0] == '[' {
if idx := strings.Index(line, "]"); idx > 0 && idx < 12 {
ts = line[1:idx]
msg = strings.TrimSpace(line[idx+1:])
}
}
msg = strings.TrimPrefix(msg, "Monitor ")
msg = strings.TrimPrefix(msg, "Push ")
// prefix: " HH:MM ● " = 9 visible chars, or " ● " = 3 without timestamp
prefixW := 3
if ts != "" {
prefixW = len(ts) + 4
}
msgW := maxW - prefixW
if msgW < 5 {
msgW = 5
}
msg = limitStr(msg, msgW)
if ts != "" {
return " " + m.st.subtleStyle.Render(ts) + " " + tag + " " + msg
}
return " " + tag + " " + msg
}
func (m Model) viewLogsSidebar(width, maxLines int) string {
logs := m.engine.GetLogs()
if len(logs) == 0 {
return m.st.subtleStyle.Render(" No logs yet")
}
sidebarStyle := lipgloss.NewStyle().Width(width).MaxWidth(width)
var all []string
for _, line := range logs {
if strings.TrimSpace(line) == "" {
continue
}
if m.logFilterImportant && !isImportantLog(classifyLog(line)) {
continue
}
all = append(all, m.renderCompactLogLine(line, width))
}
start := m.logScrollOffset
if start > len(all) {
start = len(all)
}
end := start + maxLines
if end > len(all) {
end = len(all)
}
visible := all[start:end]
return sidebarStyle.Render(strings.Join(visible, "\n"))
}
func (m *Model) scrollLogs(delta int) {
logs := m.engine.GetLogs()
total := 0
for _, line := range logs {
if strings.TrimSpace(line) == "" {
continue
}
if m.logFilterImportant && !isImportantLog(classifyLog(line)) {
continue
}
total++
}
m.logScrollOffset += delta
if m.logScrollOffset < 0 {
m.logScrollOffset = 0
}
if m.logScrollOffset > total-1 {
m.logScrollOffset = total - 1
}
if m.logScrollOffset < 0 {
m.logScrollOffset = 0
}
}