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.Height = vpHeight
|
||||||
m.detailsViewport.Width = leftW - 4
|
m.detailsViewport.Width = leftW - 4
|
||||||
|
|
||||||
|
// Refresh content with new dimensions
|
||||||
|
if m.subView == SubViewCallDetail {
|
||||||
|
m.updateCallDetailView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
@@ -491,6 +496,7 @@ func (m *Model) handleViewKeys(msg tea.KeyMsg) tea.Cmd {
|
|||||||
}
|
}
|
||||||
case "enter":
|
case "enter":
|
||||||
m.subView = SubViewCallDetail
|
m.subView = SubViewCallDetail
|
||||||
|
m.updateCallDetailView()
|
||||||
}
|
}
|
||||||
|
|
||||||
case ViewNetworkMap:
|
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.
|
// Since View is pure function of state, it's fine.
|
||||||
// But detailsViewport scroll position should reset if packet changes?
|
// But detailsViewport scroll position should reset if packet changes?
|
||||||
m.detailsViewport.GotoTop()
|
m.detailsViewport.GotoTop()
|
||||||
|
m.updateCallDetailView()
|
||||||
}
|
}
|
||||||
case "down", "j":
|
case "down", "j":
|
||||||
flows := m.callFlowStore.GetRecentFlows(20)
|
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.viewport.SetYOffset(m.selectedPacketIndex - m.viewport.Height + 1)
|
||||||
}
|
}
|
||||||
m.detailsViewport.GotoTop()
|
m.detailsViewport.GotoTop()
|
||||||
|
m.updateCallDetailView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -973,18 +981,6 @@ func (m Model) renderCallDetail() string {
|
|||||||
// Title
|
// Title
|
||||||
detailsTitle := m.styles.Title.Render("📦 Packet Details")
|
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
|
// Update details viewport
|
||||||
// Calculate available height: Pane Height - Borders(2) - Header(2 approx)
|
// Calculate available height: Pane Height - Borders(2) - Header(2 approx)
|
||||||
// Actually styles handle borders on the container.
|
// 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.Width = leftW - 4 // Margin/Padding
|
||||||
m.detailsViewport.Height = vpHeight
|
m.detailsViewport.Height = vpHeight
|
||||||
m.detailsViewport.SetContent(detailsContent)
|
// Content is set in Update now
|
||||||
|
|
||||||
// --- Right Pane (Transaction Flow) ---
|
// --- Right Pane (Transaction Flow) ---
|
||||||
// Title
|
// Title
|
||||||
flowTitle := m.styles.Title.Render("🚀 Transaction Flow")
|
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
|
// Right Pane Height logic
|
||||||
rvpHeight := (leftTotalH - 2) - 2 // -2 Borders, -2 Title/Margin
|
rvpHeight := (leftTotalH - 2) - 2 // -2 Borders, -2 Title/Margin
|
||||||
if rvpHeight < 0 {
|
if rvpHeight < 0 {
|
||||||
@@ -1401,3 +1334,92 @@ func (m Model) styleForNode(node *config.NetworkNode) lipgloss.Style {
|
|||||||
return m.styles.NodeDefault
|
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