style: Restore titles and apply full-height rounded borders to Call Detail view

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-19 16:51:24 +01:00
parent c4794ad787
commit daefefb5aa

View File

@@ -857,60 +857,89 @@ func (m Model) renderCallDetail() string {
// Split Widths and Heights // Split Widths and Heights
totalWidth := m.width totalWidth := m.width
// Subtract borders/padding if any // Subtract borders/padding if any
innerW := totalWidth - 4 innerW := totalWidth - 2 // Outer margin?
leftW := innerW / 2 leftW := innerW / 2
rightW := innerW - leftW rightW := innerW - leftW
// Left Pane Heights // Left Pane Heights
leftTotalH := m.height - 3 // Nav + Status leftTotalH := m.height - 3 // Nav + Status
leftTopH := leftTotalH / 3 // 1/3 for call summary // Adjust for borders: we have two boxes stacked.
leftBotH := leftTotalH - leftTopH // 2/3 for packet details // Let's say we split space 33% / 66%.
leftTopH := leftTotalH / 3
leftBotH := leftTotalH - leftTopH
if leftTopH < 10 { if leftTopH < 10 {
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) --- // --- Left Pane TOP (Call Info) ---
var leftTop strings.Builder var leftTop strings.Builder
// Title
leftTop.WriteString(m.styles.Title.Render("📞 Call Detail")) leftTop.WriteString(m.styles.Title.Render("📞 Call Detail"))
leftTop.WriteString("\n\n") leftTop.WriteString("\n\n")
// Content
leftTop.WriteString(fmt.Sprintf("Call-ID: %s\n", flow.CallID)) leftTop.WriteString(fmt.Sprintf("Call-ID: %s\n", flow.CallID))
leftTop.WriteString(fmt.Sprintf("From: %s\n", flow.From)) leftTop.WriteString(fmt.Sprintf("From: %s\n", flow.From))
leftTop.WriteString(fmt.Sprintf("To: %s\n", flow.To)) leftTop.WriteString(fmt.Sprintf("To: %s\n", flow.To))
leftTop.WriteString(fmt.Sprintf("State: %s\n", flow.State)) leftTop.WriteString(fmt.Sprintf("State: %s\n", flow.State))
// Calculate and display duration
duration := flow.EndTime.Sub(flow.StartTime) duration := flow.EndTime.Sub(flow.StartTime)
leftTop.WriteString(fmt.Sprintf("Duration: %s\n", duration.Round(time.Millisecond))) leftTop.WriteString(fmt.Sprintf("Duration: %s\n", duration.Round(time.Millisecond)))
leftTop.WriteString(fmt.Sprintf("Packets: %d\n\n", len(flow.Packets))) leftTop.WriteString(fmt.Sprintf("Packets: %d\n\n", len(flow.Packets)))
// Network Summary Section
leftTop.WriteString("Network Layer:\n") leftTop.WriteString("Network Layer:\n")
// Find first packet to get initial IPs
if len(flow.Packets) > 0 { if len(flow.Packets) > 0 {
first := flow.Packets[0] first := flow.Packets[0]
srcLabel := m.networkMap.LabelForIP(first.SourceIP) srcLabel := m.networkMap.LabelForIP(first.SourceIP)
if srcLabel != first.SourceIP { if srcLabel != first.SourceIP {
// Find node type to apply style
node := m.networkMap.FindByIP(first.SourceIP) node := m.networkMap.FindByIP(first.SourceIP)
srcLabel = m.styleForNode(node).Render(srcLabel) srcLabel = m.styleForNode(node).Render(srcLabel)
} }
dstLabel := m.networkMap.LabelForIP(first.DestIP) dstLabel := m.networkMap.LabelForIP(first.DestIP)
if dstLabel != first.DestIP { if dstLabel != first.DestIP {
node := m.networkMap.FindByIP(first.DestIP) node := m.networkMap.FindByIP(first.DestIP)
dstLabel = m.styleForNode(node).Render(dstLabel) dstLabel = m.styleForNode(node).Render(dstLabel)
} }
leftTop.WriteString(fmt.Sprintf(" Source: %s (%s:%d)\n", srcLabel, first.SourceIP, first.SourcePort)) 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)) leftTop.WriteString(fmt.Sprintf(" Destination: %s (%s:%d)\n", dstLabel, first.DestIP, first.DestPort))
} }
// --- Left Pane BOTTOM (Selected Packet Details) --- // --- Left Pane BOTTOM (Selected Packet Details) ---
// Title
detailsTitle := m.styles.Title.Render("📦 Packet Details")
// Content
var detailsContent string var detailsContent string
if m.selectedPacketIndex < len(flow.Packets) { if m.selectedPacketIndex < len(flow.Packets) {
pkt := flow.Packets[m.selectedPacketIndex] pkt := flow.Packets[m.selectedPacketIndex]
@@ -922,26 +951,35 @@ func (m Model) renderCallDetail() string {
detailsContent = "No packet selected" 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.SetContent(detailsContent)
m.detailsViewport.Width = leftW - 2 // Account for padding/borders roughly
m.detailsViewport.Height = leftBotH
// --- Right Pane (Transaction Flow) --- // --- Right Pane (Transaction Flow) ---
var right strings.Builder // Title
// right.WriteString("Transaction Flow:\n\n") // Removed header to save space or added to viewport content flowTitle := m.styles.Title.Render("🚀 Transaction Flow")
var right strings.Builder
for i, pkt := range flow.Packets { for i, pkt := range flow.Packets {
arrow := "→" arrow := "→"
arrowStyle := m.styles.ArrowOut 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 { if len(flow.Packets) > 0 && pkt.SourceIP != flow.Packets[0].SourceIP {
arrow = "←" arrow = "←"
arrowStyle = m.styles.ArrowIn arrowStyle = m.styles.ArrowIn
} }
// Style the packet summary (Method or Status)
var summaryStyle lipgloss.Style var summaryStyle lipgloss.Style
if pkt.IsRequest { if pkt.IsRequest {
switch pkt.Method { switch pkt.Method {
@@ -971,17 +1009,9 @@ func (m Model) renderCallDetail() string {
} }
} }
// Format timestamp
ts := pkt.Timestamp.Format("15:04:05.000") 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 { if pkt.SDP != nil {
mediaIP := pkt.SDP.GetSDPMediaIP() mediaIP := pkt.SDP.GetSDPMediaIP()
if mediaIP != "" { if mediaIP != "" {
@@ -994,44 +1024,39 @@ func (m Model) renderCallDetail() string {
} }
} }
// Highlight selected line
if i == m.selectedPacketIndex { if i == m.selectedPacketIndex {
lineStr = m.styles.Active.Render("> " + lineStr) lineStr = m.styles.Active.Render("> " + lineStr)
} else { } else {
lineStr = " " + lineStr lineStr = " " + lineStr
} }
right.WriteString(lineStr + "\n") right.WriteString(lineStr + "\n")
} }
// Set content to viewport
m.viewport.SetContent(right.String()) m.viewport.SetContent(right.String())
// Determine Border Colors based on Focus // Right Pane Height logic
detailsBorderColor := lipgloss.Color("#44475A") rvpHeight := (leftTotalH - 2) - 2 // -2 Borders, -2 Title/Margin
flowBorderColor := lipgloss.Color("#44475A") if rvpHeight < 0 {
rvpHeight = 0
if m.focusPacketDetails {
detailsBorderColor = lipgloss.Color("#bd93f9") // Active Purple
} else {
flowBorderColor = lipgloss.Color("#bd93f9")
} }
m.viewport.Height = rvpHeight
m.viewport.Width = rightW - 4
// Layout Construction // Render Final Layout
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 left pane parts // Left Top: Just content
leftTopRendered := leftTopStyle.Render(leftTop.String()) 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) leftCol := lipgloss.JoinVertical(lipgloss.Left, leftTopRendered, leftBotRendered)
// Render viewport (Right pane) // Right: Title + Viewport
rightRendered := rightStyle.Render(m.viewport.View()) rightContent := lipgloss.JoinVertical(lipgloss.Left, flowTitle, "\n", m.viewport.View())
rightRendered := rightStyle.Render(rightContent)
return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightRendered) return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightRendered)
} }