diff --git a/internal/tui/model.go b/internal/tui/model.go index d49e20d..fd1a9ae 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -857,60 +857,89 @@ func (m Model) renderCallDetail() string { // Split Widths and Heights totalWidth := m.width // Subtract borders/padding if any - innerW := totalWidth - 4 + innerW := totalWidth - 2 // Outer margin? leftW := innerW / 2 rightW := innerW - leftW // Left Pane Heights - leftTotalH := m.height - 3 // Nav + Status - leftTopH := leftTotalH / 3 // 1/3 for call summary - leftBotH := leftTotalH - leftTopH // 2/3 for packet details + leftTotalH := m.height - 3 // Nav + Status + // Adjust for borders: we have two boxes stacked. + // Let's say we split space 33% / 66%. + leftTopH := leftTotalH / 3 + leftBotH := leftTotalH - leftTopH + if leftTopH < 10 { leftTopH = 10 - } // Min height - if leftBotH < 0 { - leftBotH = 0 } + // Determine Border Colors based on Focus + detailsBorderColor := lipgloss.Color("#44475A") + flowBorderColor := lipgloss.Color("#44475A") + infoBorderColor := lipgloss.Color("#44475A") // Call info usually static focus + + if m.focusPacketDetails { + detailsBorderColor = lipgloss.Color("#bd93f9") // Active Purple + } else { + flowBorderColor = lipgloss.Color("#bd93f9") + } + + // Definition of Styles + // We want Borders on ALL sides now since they are distinct boxes + baseStyle := lipgloss.NewStyle().Padding(0, 1).Border(lipgloss.RoundedBorder()) + + leftTopStyle := baseStyle.Copy(). + Width(leftW - 2). // -2 for borders + Height(leftTopH - 2). // -2 for borders + BorderForeground(infoBorderColor) + + leftBotStyle := baseStyle.Copy(). + Width(leftW - 2). + Height(leftBotH - 2). + BorderForeground(detailsBorderColor) + + rightStyle := baseStyle.Copy(). + Width(rightW - 2). + Height(leftTotalH - 2). + BorderForeground(flowBorderColor) + // --- Left Pane TOP (Call Info) --- var leftTop strings.Builder + // Title leftTop.WriteString(m.styles.Title.Render("📞 Call Detail")) leftTop.WriteString("\n\n") + + // Content leftTop.WriteString(fmt.Sprintf("Call-ID: %s\n", flow.CallID)) leftTop.WriteString(fmt.Sprintf("From: %s\n", flow.From)) leftTop.WriteString(fmt.Sprintf("To: %s\n", flow.To)) leftTop.WriteString(fmt.Sprintf("State: %s\n", flow.State)) - // Calculate and display duration duration := flow.EndTime.Sub(flow.StartTime) leftTop.WriteString(fmt.Sprintf("Duration: %s\n", duration.Round(time.Millisecond))) - leftTop.WriteString(fmt.Sprintf("Packets: %d\n\n", len(flow.Packets))) - // Network Summary Section leftTop.WriteString("Network Layer:\n") - // Find first packet to get initial IPs if len(flow.Packets) > 0 { first := flow.Packets[0] - srcLabel := m.networkMap.LabelForIP(first.SourceIP) if srcLabel != first.SourceIP { - // Find node type to apply style node := m.networkMap.FindByIP(first.SourceIP) srcLabel = m.styleForNode(node).Render(srcLabel) } - dstLabel := m.networkMap.LabelForIP(first.DestIP) if dstLabel != first.DestIP { node := m.networkMap.FindByIP(first.DestIP) dstLabel = m.styleForNode(node).Render(dstLabel) } - leftTop.WriteString(fmt.Sprintf(" Source: %s (%s:%d)\n", srcLabel, first.SourceIP, first.SourcePort)) leftTop.WriteString(fmt.Sprintf(" Destination: %s (%s:%d)\n", dstLabel, first.DestIP, first.DestPort)) } // --- Left Pane BOTTOM (Selected Packet Details) --- + // Title + detailsTitle := m.styles.Title.Render("📦 Packet Details") + + // Content var detailsContent string if m.selectedPacketIndex < len(flow.Packets) { pkt := flow.Packets[m.selectedPacketIndex] @@ -922,26 +951,35 @@ func (m Model) renderCallDetail() string { detailsContent = "No packet selected" } - // Update details viewport content + // Update details viewport + // Calculate available height: Pane Height - Borders(2) - Header(2 approx) + // Actually styles handle borders on the container. + // Viewport height should be container InnerHeight - TitleHeight - Padding. + // leftBotStyle.GetHeight() returns outer height. + // We set style height explicitly, so we know it. + + vpHeight := (leftBotH - 2) - 2 // -2 Borders, -2 Title/Margin + if vpHeight < 0 { + vpHeight = 0 + } + + m.detailsViewport.Width = leftW - 4 // Margin/Padding + m.detailsViewport.Height = vpHeight 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 + // Title + flowTitle := m.styles.Title.Render("🚀 Transaction Flow") + var right strings.Builder for i, pkt := range flow.Packets { arrow := "→" arrowStyle := m.styles.ArrowOut - - // Simple direction indicator based on whether it matches initial source if len(flow.Packets) > 0 && pkt.SourceIP != flow.Packets[0].SourceIP { arrow = "←" arrowStyle = m.styles.ArrowIn } - // Style the packet summary (Method or Status) var summaryStyle lipgloss.Style if pkt.IsRequest { switch pkt.Method { @@ -971,17 +1009,9 @@ func (m Model) renderCallDetail() string { } } - // Format timestamp 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())) - // Clean packet info line (Timestamp + Arrow + Method/Status) - lineStr := fmt.Sprintf("%d. [%s] %s %s", - i+1, - ts, - arrowStyle.Render(arrow), - summaryStyle.Render(pkt.Summary())) - - // Show SDP info if present if pkt.SDP != nil { mediaIP := pkt.SDP.GetSDPMediaIP() if mediaIP != "" { @@ -994,44 +1024,39 @@ func (m Model) renderCallDetail() string { } } - // Highlight selected line if i == m.selectedPacketIndex { lineStr = m.styles.Active.Render("> " + lineStr) } else { lineStr = " " + lineStr } - right.WriteString(lineStr + "\n") } - // 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") + // Right Pane Height logic + rvpHeight := (leftTotalH - 2) - 2 // -2 Borders, -2 Title/Margin + if rvpHeight < 0 { + rvpHeight = 0 } + m.viewport.Height = rvpHeight + m.viewport.Width = rightW - 4 - // Layout Construction - leftTopStyle := lipgloss.NewStyle().Width(leftW).Height(leftTopH).Padding(1, 2) - 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 Final Layout - // Render left pane parts + // Left Top: Just content leftTopRendered := leftTopStyle.Render(leftTop.String()) - // Use detailsViewport view - leftBotRendered := leftBotStyle.Render(m.detailsViewport.View()) - // Combine Left + // Left Bot: Title + Viewport + leftBotContent := lipgloss.JoinVertical(lipgloss.Left, detailsTitle, "\n", m.detailsViewport.View()) + leftBotRendered := leftBotStyle.Render(leftBotContent) + + // Left Col leftCol := lipgloss.JoinVertical(lipgloss.Left, leftTopRendered, leftBotRendered) - // Render viewport (Right pane) - rightRendered := rightStyle.Render(m.viewport.View()) + // Right: Title + Viewport + rightContent := lipgloss.JoinVertical(lipgloss.Left, flowTitle, "\n", m.viewport.View()) + rightRendered := rightStyle.Render(rightContent) return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightRendered) }