fix: Ensure Packet Details and Transaction Flow scroll correctly by updating viewports in Update loop
This commit is contained in:
@@ -430,6 +430,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
m.detailsViewport.Height = vpHeight
|
||||
m.detailsViewport.Width = leftW - 4
|
||||
|
||||
// Refresh content with new dimensions
|
||||
if m.subView == SubViewCallDetail {
|
||||
m.updateCallDetailView()
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
@@ -491,6 +496,7 @@ func (m *Model) handleViewKeys(msg tea.KeyMsg) tea.Cmd {
|
||||
}
|
||||
case "enter":
|
||||
m.subView = SubViewCallDetail
|
||||
m.updateCallDetailView()
|
||||
}
|
||||
|
||||
case ViewNetworkMap:
|
||||
@@ -642,6 +648,7 @@ func (m *Model) updateSubView(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Since View is pure function of state, it's fine.
|
||||
// But detailsViewport scroll position should reset if packet changes?
|
||||
m.detailsViewport.GotoTop()
|
||||
m.updateCallDetailView()
|
||||
}
|
||||
case "down", "j":
|
||||
flows := m.callFlowStore.GetRecentFlows(20)
|
||||
@@ -654,6 +661,7 @@ func (m *Model) updateSubView(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.viewport.SetYOffset(m.selectedPacketIndex - m.viewport.Height + 1)
|
||||
}
|
||||
m.detailsViewport.GotoTop()
|
||||
m.updateCallDetailView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,18 +981,6 @@ func (m Model) renderCallDetail() string {
|
||||
// Title
|
||||
detailsTitle := m.styles.Title.Render("📦 Packet Details")
|
||||
|
||||
// Content
|
||||
var detailsContent string
|
||||
if m.selectedPacketIndex < len(flow.Packets) {
|
||||
pkt := flow.Packets[m.selectedPacketIndex]
|
||||
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 {
|
||||
detailsContent = "No packet selected"
|
||||
}
|
||||
|
||||
// Update details viewport
|
||||
// Calculate available height: Pane Height - Borders(2) - Header(2 approx)
|
||||
// Actually styles handle borders on the container.
|
||||
@@ -999,75 +995,12 @@ func (m Model) renderCallDetail() string {
|
||||
|
||||
m.detailsViewport.Width = leftW - 4 // Margin/Padding
|
||||
m.detailsViewport.Height = vpHeight
|
||||
m.detailsViewport.SetContent(detailsContent)
|
||||
// Content is set in Update now
|
||||
|
||||
// --- Right Pane (Transaction Flow) ---
|
||||
// Title
|
||||
flowTitle := m.styles.Title.Render("🚀 Transaction Flow")
|
||||
|
||||
var right strings.Builder
|
||||
for i, pkt := range flow.Packets {
|
||||
arrow := "→"
|
||||
arrowStyle := m.styles.ArrowOut
|
||||
if len(flow.Packets) > 0 && pkt.SourceIP != flow.Packets[0].SourceIP {
|
||||
arrow = "←"
|
||||
arrowStyle = m.styles.ArrowIn
|
||||
}
|
||||
|
||||
var summaryStyle lipgloss.Style
|
||||
if pkt.IsRequest {
|
||||
switch pkt.Method {
|
||||
case sip.MethodINVITE:
|
||||
summaryStyle = m.styles.MethodInvite
|
||||
case sip.MethodBYE, sip.MethodCANCEL:
|
||||
summaryStyle = m.styles.MethodBye
|
||||
case sip.MethodREGISTER:
|
||||
summaryStyle = m.styles.MethodRegister
|
||||
default:
|
||||
summaryStyle = m.styles.MethodOther
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case pkt.StatusCode >= 100 && pkt.StatusCode < 200:
|
||||
summaryStyle = m.styles.Status1xx
|
||||
case pkt.StatusCode >= 200 && pkt.StatusCode < 300:
|
||||
summaryStyle = m.styles.Status2xx
|
||||
case pkt.StatusCode >= 300 && pkt.StatusCode < 400:
|
||||
summaryStyle = m.styles.Status3xx
|
||||
case pkt.StatusCode >= 400 && pkt.StatusCode < 500:
|
||||
summaryStyle = m.styles.Status4xx
|
||||
case pkt.StatusCode >= 500 && pkt.StatusCode < 600:
|
||||
summaryStyle = m.styles.Status5xx
|
||||
default:
|
||||
summaryStyle = m.styles.Status6xx
|
||||
}
|
||||
}
|
||||
|
||||
ts := pkt.Timestamp.Format("15:04:05.000")
|
||||
lineStr := fmt.Sprintf("%d. [%s] %s %s", i+1, ts, arrowStyle.Render(arrow), summaryStyle.Render(pkt.Summary()))
|
||||
|
||||
if pkt.SDP != nil {
|
||||
mediaIP := pkt.SDP.GetSDPMediaIP()
|
||||
if mediaIP != "" {
|
||||
label := m.networkMap.LabelForIP(mediaIP)
|
||||
if label != mediaIP {
|
||||
node := m.networkMap.FindByIP(mediaIP)
|
||||
label = m.styleForNode(node).Render(label)
|
||||
}
|
||||
lineStr += fmt.Sprintf(" (SDP: %s %s)", mediaIP, label)
|
||||
}
|
||||
}
|
||||
|
||||
if i == m.selectedPacketIndex {
|
||||
lineStr = m.styles.Active.Render("> " + lineStr)
|
||||
} else {
|
||||
lineStr = " " + lineStr
|
||||
}
|
||||
right.WriteString(lineStr + "\n")
|
||||
}
|
||||
|
||||
m.viewport.SetContent(right.String())
|
||||
|
||||
// Right Pane Height logic
|
||||
rvpHeight := (leftTotalH - 2) - 2 // -2 Borders, -2 Title/Margin
|
||||
if rvpHeight < 0 {
|
||||
@@ -1401,3 +1334,92 @@ func (m Model) styleForNode(node *config.NetworkNode) lipgloss.Style {
|
||||
return m.styles.NodeDefault
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) updateCallDetailView() {
|
||||
flows := m.callFlowStore.GetRecentFlows(20)
|
||||
if m.selectedFlow >= len(flows) || len(flows) == 0 {
|
||||
m.detailsViewport.SetContent("No call selected")
|
||||
m.viewport.SetContent("")
|
||||
return
|
||||
}
|
||||
|
||||
flow := flows[m.selectedFlow]
|
||||
|
||||
// --- Selected Packet Details ---
|
||||
var detailsContent string
|
||||
if m.selectedPacketIndex < len(flow.Packets) {
|
||||
pkt := flow.Packets[m.selectedPacketIndex]
|
||||
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 {
|
||||
detailsContent = "No packet selected"
|
||||
}
|
||||
|
||||
m.detailsViewport.SetContent(detailsContent)
|
||||
|
||||
// --- Transaction Flow ---
|
||||
var right strings.Builder
|
||||
for i, pkt := range flow.Packets {
|
||||
arrow := "→"
|
||||
arrowStyle := m.styles.ArrowOut
|
||||
if len(flow.Packets) > 0 && pkt.SourceIP != flow.Packets[0].SourceIP {
|
||||
arrow = "←"
|
||||
arrowStyle = m.styles.ArrowIn
|
||||
}
|
||||
|
||||
var summaryStyle lipgloss.Style
|
||||
if pkt.IsRequest {
|
||||
switch pkt.Method {
|
||||
case sip.MethodINVITE:
|
||||
summaryStyle = m.styles.MethodInvite
|
||||
case sip.MethodBYE, sip.MethodCANCEL:
|
||||
summaryStyle = m.styles.MethodBye
|
||||
case sip.MethodREGISTER:
|
||||
summaryStyle = m.styles.MethodRegister
|
||||
default:
|
||||
summaryStyle = m.styles.MethodOther
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case pkt.StatusCode >= 100 && pkt.StatusCode < 200:
|
||||
summaryStyle = m.styles.Status1xx
|
||||
case pkt.StatusCode >= 200 && pkt.StatusCode < 300:
|
||||
summaryStyle = m.styles.Status2xx
|
||||
case pkt.StatusCode >= 300 && pkt.StatusCode < 400:
|
||||
summaryStyle = m.styles.Status3xx
|
||||
case pkt.StatusCode >= 400 && pkt.StatusCode < 500:
|
||||
summaryStyle = m.styles.Status4xx
|
||||
case pkt.StatusCode >= 500 && pkt.StatusCode < 600:
|
||||
summaryStyle = m.styles.Status5xx
|
||||
default:
|
||||
summaryStyle = m.styles.Status6xx
|
||||
}
|
||||
}
|
||||
|
||||
ts := pkt.Timestamp.Format("15:04:05.000")
|
||||
lineStr := fmt.Sprintf("%d. [%s] %s %s", i+1, ts, arrowStyle.Render(arrow), summaryStyle.Render(pkt.Summary()))
|
||||
|
||||
if pkt.SDP != nil {
|
||||
mediaIP := pkt.SDP.GetSDPMediaIP()
|
||||
if mediaIP != "" {
|
||||
label := m.networkMap.LabelForIP(mediaIP)
|
||||
if label != mediaIP {
|
||||
node := m.networkMap.FindByIP(mediaIP)
|
||||
label = m.styleForNode(node).Render(label)
|
||||
}
|
||||
lineStr += fmt.Sprintf(" (SDP: %s %s)", mediaIP, label)
|
||||
}
|
||||
}
|
||||
|
||||
if i == m.selectedPacketIndex {
|
||||
lineStr = m.styles.Active.Render("> " + lineStr)
|
||||
} else {
|
||||
lineStr = " " + lineStr
|
||||
}
|
||||
right.WriteString(lineStr + "\n")
|
||||
}
|
||||
|
||||
m.viewport.SetContent(right.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user