feat: Add scrollable packet details viewport and TAB focus switching
This commit is contained in:
@@ -82,6 +82,10 @@ type Model struct {
|
||||
flowList list.Model
|
||||
viewport viewport.Model
|
||||
|
||||
// Packet Details View
|
||||
detailsViewport viewport.Model
|
||||
focusPacketDetails bool
|
||||
|
||||
// File browser for pcap import
|
||||
fileBrowser FileBrowserModel
|
||||
loadedPcapPath string
|
||||
@@ -568,13 +572,30 @@ func (m *Model) updateSubView(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case SubViewCallDetail:
|
||||
var cmd tea.Cmd
|
||||
// We handle scrolling manually via selection now
|
||||
|
||||
// Handle Focus Switching
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "esc", "q":
|
||||
m.subView = SubViewNone
|
||||
m.selectedPacketIndex = 0 // Reset selection
|
||||
m.focusPacketDetails = false
|
||||
return m, nil
|
||||
case "tab":
|
||||
m.focusPacketDetails = !m.focusPacketDetails
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Route keys based on focus
|
||||
if m.focusPacketDetails {
|
||||
// Forward keys to Details Viewport for scrolling
|
||||
m.detailsViewport, cmd = m.detailsViewport.Update(msg)
|
||||
return m, cmd
|
||||
} else {
|
||||
// Handle Flow Navigation
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "up", "k":
|
||||
if m.selectedPacketIndex > 0 {
|
||||
m.selectedPacketIndex--
|
||||
@@ -582,6 +603,11 @@ func (m *Model) updateSubView(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.selectedPacketIndex < m.viewport.YOffset {
|
||||
m.viewport.SetYOffset(m.selectedPacketIndex)
|
||||
}
|
||||
// Force update of details content happens in View currently, but for state consistency
|
||||
// we should probably do it here or let View handle it.
|
||||
// Since View is pure function of state, it's fine.
|
||||
// But detailsViewport scroll position should reset if packet changes?
|
||||
m.detailsViewport.GotoTop()
|
||||
}
|
||||
case "down", "j":
|
||||
flows := m.callFlowStore.GetRecentFlows(20)
|
||||
@@ -593,16 +619,12 @@ func (m *Model) updateSubView(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.selectedPacketIndex >= m.viewport.YOffset+m.viewport.Height {
|
||||
m.viewport.SetYOffset(m.selectedPacketIndex - m.viewport.Height + 1)
|
||||
}
|
||||
m.detailsViewport.GotoTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We generally don't need viewport.Update for keys anymore as we handle scroll manually
|
||||
// But we might need it for resizing or other generic msgs?
|
||||
// Actually viewport.Update handles scroll keys by default.
|
||||
// If we return cmd from it, it might conflict.
|
||||
// Let's NOT call viewport.Update for keys if we consumed them.
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
@@ -889,25 +911,22 @@ func (m Model) renderCallDetail() string {
|
||||
}
|
||||
|
||||
// --- Left Pane BOTTOM (Selected Packet Details) ---
|
||||
var leftBot strings.Builder
|
||||
leftBot.WriteString(m.styles.Title.Render("📦 Packet Details"))
|
||||
leftBot.WriteString("\n\n")
|
||||
|
||||
var detailsContent string
|
||||
if m.selectedPacketIndex < len(flow.Packets) {
|
||||
pkt := flow.Packets[m.selectedPacketIndex]
|
||||
leftBot.WriteString(fmt.Sprintf("Time: %s\n", pkt.Timestamp.Format("15:04:05.000")))
|
||||
leftBot.WriteString(fmt.Sprintf("IP: %s -> %s\n\n", pkt.SourceIP, pkt.DestIP))
|
||||
|
||||
// Render Payload/Raw
|
||||
// Truncate if too long?
|
||||
raw := pkt.Raw
|
||||
// Simple header parsing or just raw dump?
|
||||
// Raw dump is useful.
|
||||
leftBot.WriteString(raw)
|
||||
detailsContent = fmt.Sprintf("Time: %s\nIP: %s -> %s\n\n%s",
|
||||
pkt.Timestamp.Format("15:04:05.000"),
|
||||
pkt.SourceIP, pkt.DestIP,
|
||||
pkt.Raw)
|
||||
} else {
|
||||
leftBot.WriteString("No packet selected")
|
||||
detailsContent = "No packet selected"
|
||||
}
|
||||
|
||||
// Update details viewport content
|
||||
m.detailsViewport.SetContent(detailsContent)
|
||||
m.detailsViewport.Width = leftW - 2 // Account for padding/borders roughly
|
||||
m.detailsViewport.Height = leftBotH
|
||||
|
||||
// --- Right Pane (Transaction Flow) ---
|
||||
var right strings.Builder
|
||||
// right.WriteString("Transaction Flow:\n\n") // Removed header to save space or added to viewport content
|
||||
@@ -988,14 +1007,25 @@ func (m Model) renderCallDetail() string {
|
||||
// Set content to viewport
|
||||
m.viewport.SetContent(right.String())
|
||||
|
||||
// Determine Border Colors based on Focus
|
||||
detailsBorderColor := lipgloss.Color("#44475A")
|
||||
flowBorderColor := lipgloss.Color("#44475A")
|
||||
|
||||
if m.focusPacketDetails {
|
||||
detailsBorderColor = lipgloss.Color("#bd93f9") // Active Purple
|
||||
} else {
|
||||
flowBorderColor = lipgloss.Color("#bd93f9")
|
||||
}
|
||||
|
||||
// Layout Construction
|
||||
leftTopStyle := lipgloss.NewStyle().Width(leftW).Height(leftTopH).Padding(1, 2)
|
||||
leftBotStyle := lipgloss.NewStyle().Width(leftW).Height(leftBotH).Padding(1, 2).Border(lipgloss.NormalBorder(), true, false, false, false).BorderForeground(lipgloss.Color("#44475A")) // Top border for bottom pane
|
||||
rightStyle := lipgloss.NewStyle().Width(rightW).Padding(0, 1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(lipgloss.Color("#44475A")) // Left border for right pane
|
||||
leftBotStyle := lipgloss.NewStyle().Width(leftW).Height(leftBotH).Padding(0, 1).Border(lipgloss.NormalBorder(), true, false, false, false).BorderForeground(detailsBorderColor)
|
||||
rightStyle := lipgloss.NewStyle().Width(rightW).Padding(0, 1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(flowBorderColor)
|
||||
|
||||
// Render left pane parts
|
||||
leftTopRendered := leftTopStyle.Render(leftTop.String())
|
||||
leftBotRendered := leftBotStyle.Render(leftBot.String())
|
||||
// Use detailsViewport view
|
||||
leftBotRendered := leftBotStyle.Render(m.detailsViewport.View())
|
||||
|
||||
// Combine Left
|
||||
leftCol := lipgloss.JoinVertical(lipgloss.Left, leftTopRendered, leftBotRendered)
|
||||
|
||||
Reference in New Issue
Block a user