diff --git a/inspector.exe b/inspector.exe index 7945ac0..77cb41b 100644 Binary files a/inspector.exe and b/inspector.exe differ diff --git a/internal/config/network_map.go b/internal/config/network_map.go index c58d268..02bead0 100644 --- a/internal/config/network_map.go +++ b/internal/config/network_map.go @@ -56,7 +56,7 @@ func (nm *NetworkMap) FindByIP(ip string) *NetworkNode { // LabelForIP returns a human-readable label for an IP, or the IP itself if unknown func (nm *NetworkMap) LabelForIP(ip string) string { if node := nm.FindByIP(ip); node != nil { - return node.Name + " (" + string(node.Type) + ")" + return node.Name } return ip } diff --git a/internal/tui/model.go b/internal/tui/model.go index 52d69e1..8860a09 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -99,6 +99,30 @@ type Styles struct { Box lipgloss.Style PacketRow lipgloss.Style CallFlow lipgloss.Style + + // SIP Styles + MethodInvite lipgloss.Style + MethodBye lipgloss.Style + MethodRegister lipgloss.Style + MethodOther lipgloss.Style + + Status1xx lipgloss.Style + Status2xx lipgloss.Style + Status3xx lipgloss.Style + Status4xx lipgloss.Style + Status5xx lipgloss.Style + Status6xx lipgloss.Style + + NodeLabel lipgloss.Style + NodePBX lipgloss.Style + NodeProxy lipgloss.Style + NodeGateway lipgloss.Style + NodeCarrier lipgloss.Style + NodeEndpoint lipgloss.Style + NodeDefault lipgloss.Style + + ArrowOut lipgloss.Style + ArrowIn lipgloss.Style } func defaultStyles() Styles { @@ -135,6 +159,60 @@ func defaultStyles() Styles { Border(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#44475A")). Padding(0, 1), + + // SIP Styles Initialization + MethodInvite: lipgloss.NewStyle().Foreground(lipgloss.Color("#8BE9FD")).Bold(true), // Cyan + MethodBye: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5555")).Bold(true), // Red + MethodRegister: lipgloss.NewStyle().Foreground(lipgloss.Color("#50FA7B")).Bold(true), // Green + MethodOther: lipgloss.NewStyle().Foreground(lipgloss.Color("#BD93F9")).Bold(true), // Purple + + Status1xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#F1FA8C")), // Yellow + Status2xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#50FA7B")), // Green + Status3xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#8BE9FD")), // Cyan + Status4xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5555")), // Red + Status5xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5555")).Bold(true), // Red Bold + Status6xx: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5555")).Bold(true).Underline(true), + + NodeLabel: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#000000")). + Background(lipgloss.Color("#BD93F9")). // Purple background + Padding(0, 1). + Bold(true), + + // Node Styles with different background colors + NodePBX: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#FFFFFF")). + Background(lipgloss.Color("#FF79C6")). // Pink + Padding(0, 1). + Bold(true), + NodeProxy: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#000000")). + Background(lipgloss.Color("#8BE9FD")). // Cyan + Padding(0, 1). + Bold(true), + NodeGateway: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#000000")). + Background(lipgloss.Color("#F1FA8C")). // Yellow + Padding(0, 1). + Bold(true), + NodeCarrier: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#FFFFFF")). + Background(lipgloss.Color("#BD93F9")). // Purple + Padding(0, 1). + Bold(true), + NodeEndpoint: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#FFFFFF")). + Background(lipgloss.Color("#6272A4")). // Grey/Blue + Padding(0, 1). + Bold(true), + NodeDefault: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#FFFFFF")). + Background(lipgloss.Color("#44475A")). // Grey + Padding(0, 1). + Bold(true), + + ArrowOut: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6")), // Pink + ArrowIn: lipgloss.NewStyle().Foreground(lipgloss.Color("#F1FA8C")), // Yellow } } @@ -608,6 +686,8 @@ func (m Model) renderAddNodeForm() string { b.WriteString("\n") } b.WriteString("\n") + b.WriteString(m.styles.Help.Render("Node Types: PBX, Proxy, SBC, Carrier, Handset, Firewall")) + b.WriteString("\n") b.WriteString(m.styles.Help.Render("Tab navigate • Enter submit • Esc cancel")) return b.String() } @@ -634,8 +714,19 @@ func (m Model) renderCallDetail() string { // 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) + } b.WriteString(fmt.Sprintf(" Source: %s (%s:%d)\n", srcLabel, first.SourceIP, first.SourcePort)) b.WriteString(fmt.Sprintf(" Destination: %s (%s:%d)\n", dstLabel, first.DestIP, first.DestPort)) @@ -645,22 +736,63 @@ func (m Model) renderCallDetail() string { b.WriteString("Transaction Flow:\n") 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 { + 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 + } } // Format timestamp ts := pkt.Timestamp.Format("15:04:05.000") // Clean packet info line (Timestamp + Arrow + Method/Status) - b.WriteString(fmt.Sprintf(" %d. [%s] %s %s\n", i+1, ts, arrow, pkt.Summary())) + b.WriteString(fmt.Sprintf(" %d. [%s] %s %s\n", + 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 != "" { label := m.networkMap.LabelForIP(mediaIP) + if label != mediaIP { + node := m.networkMap.FindByIP(mediaIP) + label = m.styleForNode(node).Render(label) + } b.WriteString(fmt.Sprintf(" SDP Media: %s (%s)\n", mediaIP, label)) } } @@ -949,3 +1081,32 @@ func extractUser(sipAddr string) string { } return sipAddr } + +// styleForNode returns the style for a given node type +func (m Model) styleForNode(node *config.NetworkNode) lipgloss.Style { + if node == nil { + return m.styles.NodeDefault + } + switch node.Type { + case config.NodeTypePBX: + return m.styles.NodePBX + case config.NodeTypeProxy: + return m.styles.NodeProxy + case config.NodeTypeGateway: + return m.styles.NodeGateway + case config.NodeTypeMediaServer: + // Reusing Proxy style for MediaServer if no specific one defined + return m.styles.NodeProxy + case config.NodeTypeEndpoint: + return m.styles.NodeEndpoint + case config.NodeTypeUnknown: + return m.styles.NodeDefault + default: + // Attempt to match by name if type is custom or carrier + lowerName := strings.ToLower(node.Name) + if strings.Contains(lowerName, "carrier") || strings.Contains(lowerName, "provider") { + return m.styles.NodeCarrier + } + return m.styles.NodeDefault + } +}