feat: TUI visual improvements (nav bar, flow details) and timestamp support

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-19 14:34:43 +01:00
parent efb50ffc8e
commit 063650976b
6 changed files with 229 additions and 13 deletions

View File

@@ -129,16 +129,18 @@ func (r *PcapReader) extractSIPPacket(packet gopacket.Packet, packetNum int) *si
payloadStr := string(payload)
// Log first 200 chars to see full SIP headers
preview := payloadStr
if len(preview) > 200 {
preview = preview[:200]
}
logger.Debug("PcapReader: Packet %d full payload preview: %q", packetNum, preview)
// Log first 200 chars to see full SIP headers (only on trace/debug if needed)
/*
preview := payloadStr
if len(preview) > 200 {
preview = preview[:200]
}
logger.Debug("PcapReader: Packet %d full payload preview: %q", packetNum, preview)
*/
// Check if it looks like SIP
if !isSIPPayload(payloadStr) {
logger.Debug("PcapReader: Packet %d is not SIP", packetNum)
// logger.Debug("PcapReader: Packet %d is not SIP", packetNum)
return nil
}
@@ -160,6 +162,7 @@ func (r *PcapReader) extractSIPPacket(packet gopacket.Packet, packetNum int) *si
sipPacket.SourcePort = srcPort
sipPacket.DestIP = dstIP
sipPacket.DestPort = dstPort
sipPacket.Timestamp = packet.Metadata().Timestamp
return sipPacket
}

View File

@@ -3,6 +3,7 @@ package sip
import (
"regexp"
"strings"
"time"
)
// Method represents a SIP method
@@ -27,6 +28,7 @@ const (
// Packet represents a parsed SIP packet
type Packet struct {
Timestamp time.Time
Raw string
IsRequest bool
Method Method

View File

@@ -635,14 +635,21 @@ func (m Model) renderCallDetail() string {
if !pkt.IsRequest {
arrow = "←"
}
b.WriteString(fmt.Sprintf(" %d. %s %s\n", i+1, arrow, pkt.Summary()))
// Format timestamp
ts := pkt.Timestamp.Format("15:04:05.000")
// Detailed packet info line
b.WriteString(fmt.Sprintf(" %d. [%s] %s %s %s:%d -> %s:%d\n",
i+1, ts, arrow, pkt.Summary(),
pkt.SourceIP, pkt.SourcePort, pkt.DestIP, pkt.DestPort))
// Show SDP info if present
if pkt.SDP != nil {
mediaIP := pkt.SDP.GetSDPMediaIP()
if mediaIP != "" {
label := m.networkMap.LabelForIP(mediaIP)
b.WriteString(fmt.Sprintf(" SDP Media: %s\n", label))
b.WriteString(fmt.Sprintf(" SDP Media: %s (%s)\n", mediaIP, label))
}
}
}
@@ -655,17 +662,45 @@ func (m Model) renderCallDetail() string {
func (m Model) renderNav() string {
tabs := []string{"[1] Dashboard", "[2] Capture", "[3] Analysis", "[4] Network Map"}
var rendered []string
// Calculate tab width for even distribution
tabWidth := m.width / len(tabs)
if tabWidth < 15 {
tabWidth = 15
}
// Style for active and inactive tabs with fixed width
activeStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FFFFFF")).
Background(lipgloss.Color("#7D56F4")).
Width(tabWidth).
Align(lipgloss.Center)
inactiveStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#AFAFAF")).
Background(lipgloss.Color("#282A36")).
Width(tabWidth).
Align(lipgloss.Center)
var rendered []string
for i, tab := range tabs {
if View(i) == m.currentView {
rendered = append(rendered, m.styles.Active.Render(tab))
rendered = append(rendered, activeStyle.Render(tab))
} else {
rendered = append(rendered, m.styles.Inactive.Render(tab))
rendered = append(rendered, inactiveStyle.Render(tab))
}
}
return lipgloss.JoinHorizontal(lipgloss.Top, rendered...) + "\n"
// Join tabs and add background bar
navBar := lipgloss.JoinHorizontal(lipgloss.Top, rendered...)
// Fill remaining width with background
barStyle := lipgloss.NewStyle().
Background(lipgloss.Color("#282A36")).
Width(m.width)
return barStyle.Render(navBar) + "\n"
}
func (m Model) renderStatusBar() string {