feat: Initial project structure with TUI, SSH, SIP parser, and capture modules
This commit is contained in:
216
internal/tui/model.go
Normal file
216
internal/tui/model.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"telephony-inspector/internal/config"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// View represents the current screen in the TUI
|
||||
type View int
|
||||
|
||||
const (
|
||||
ViewDashboard View = iota
|
||||
ViewCapture
|
||||
ViewAnalysis
|
||||
ViewNetworkMap
|
||||
)
|
||||
|
||||
// Model holds the application state
|
||||
type Model struct {
|
||||
currentView View
|
||||
width int
|
||||
height int
|
||||
|
||||
// Network map configuration
|
||||
networkMap *config.NetworkMap
|
||||
|
||||
// Capture state
|
||||
capturing bool
|
||||
packetCount int
|
||||
lastPackets []string // Last N packet summaries
|
||||
|
||||
// Style definitions
|
||||
styles Styles
|
||||
}
|
||||
|
||||
// Styles holds the lipgloss styles for the TUI
|
||||
type Styles struct {
|
||||
Title lipgloss.Style
|
||||
Subtitle lipgloss.Style
|
||||
Active lipgloss.Style
|
||||
Inactive lipgloss.Style
|
||||
Help lipgloss.Style
|
||||
StatusBar lipgloss.Style
|
||||
}
|
||||
|
||||
func defaultStyles() Styles {
|
||||
return Styles{
|
||||
Title: lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#7D56F4")).
|
||||
MarginBottom(1),
|
||||
Subtitle: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#AFAFAF")),
|
||||
Active: lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#04B575")),
|
||||
Inactive: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#626262")),
|
||||
Help: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#626262")).
|
||||
MarginTop(1),
|
||||
StatusBar: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#7D56F4")).
|
||||
Foreground(lipgloss.Color("#FFFFFF")).
|
||||
Padding(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// NewModel creates a new TUI model with default values
|
||||
func NewModel() Model {
|
||||
return Model{
|
||||
currentView: ViewDashboard,
|
||||
networkMap: config.NewNetworkMap(),
|
||||
lastPackets: make([]string, 0, 20),
|
||||
styles: defaultStyles(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the model
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update handles messages and updates the model
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
return m, tea.Quit
|
||||
case "1":
|
||||
m.currentView = ViewDashboard
|
||||
case "2":
|
||||
m.currentView = ViewCapture
|
||||
case "3":
|
||||
m.currentView = ViewAnalysis
|
||||
case "4":
|
||||
m.currentView = ViewNetworkMap
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// View renders the TUI
|
||||
func (m Model) View() string {
|
||||
var content string
|
||||
|
||||
switch m.currentView {
|
||||
case ViewDashboard:
|
||||
content = m.viewDashboard()
|
||||
case ViewCapture:
|
||||
content = m.viewCapture()
|
||||
case ViewAnalysis:
|
||||
content = m.viewAnalysis()
|
||||
case ViewNetworkMap:
|
||||
content = m.viewNetworkMap()
|
||||
}
|
||||
|
||||
// Navigation bar
|
||||
nav := m.renderNav()
|
||||
|
||||
// Status bar
|
||||
status := m.styles.StatusBar.Render(" Telephony Inspector v0.1.0 ")
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, nav, content, status)
|
||||
}
|
||||
|
||||
func (m Model) renderNav() string {
|
||||
tabs := []string{"[1] Dashboard", "[2] Capture", "[3] Analysis", "[4] Network Map"}
|
||||
var rendered []string
|
||||
|
||||
for i, tab := range tabs {
|
||||
if View(i) == m.currentView {
|
||||
rendered = append(rendered, m.styles.Active.Render(tab))
|
||||
} else {
|
||||
rendered = append(rendered, m.styles.Inactive.Render(tab))
|
||||
}
|
||||
}
|
||||
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, rendered...) + "\n"
|
||||
}
|
||||
|
||||
func (m Model) viewDashboard() string {
|
||||
title := m.styles.Title.Render("📞 Dashboard")
|
||||
|
||||
info := lipgloss.JoinVertical(lipgloss.Left,
|
||||
m.styles.Subtitle.Render("SIP Telephony Inspector"),
|
||||
"",
|
||||
"• Capture SIP traffic via SSH + tcpdump",
|
||||
"• Analyze SIP/SDP packets in real-time",
|
||||
"• Map network topology for better debugging",
|
||||
"",
|
||||
m.styles.Help.Render("Press 1-4 to navigate, q to quit"),
|
||||
)
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title, info)
|
||||
}
|
||||
|
||||
func (m Model) viewCapture() string {
|
||||
title := m.styles.Title.Render("🔍 Capture")
|
||||
|
||||
status := "Status: "
|
||||
if m.capturing {
|
||||
status += m.styles.Active.Render("● Capturing")
|
||||
} else {
|
||||
status += m.styles.Inactive.Render("○ Stopped")
|
||||
}
|
||||
|
||||
packetInfo := fmt.Sprintf("Packets captured: %d", m.packetCount)
|
||||
|
||||
help := m.styles.Help.Render("[c] Connect SSH [s] Start/Stop Capture [q] Quit")
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title, status, packetInfo, "", help)
|
||||
}
|
||||
|
||||
func (m Model) viewAnalysis() string {
|
||||
title := m.styles.Title.Render("📊 Analysis")
|
||||
|
||||
content := lipgloss.JoinVertical(lipgloss.Left,
|
||||
"Call flows will appear here once packets are captured.",
|
||||
"",
|
||||
"Features:",
|
||||
"• Group packets by Call-ID",
|
||||
"• Visualize SIP transaction flow",
|
||||
"• Decode SDP offers/answers",
|
||||
)
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title, content)
|
||||
}
|
||||
|
||||
func (m Model) viewNetworkMap() string {
|
||||
title := m.styles.Title.Render("🗺️ Network Map")
|
||||
|
||||
if len(m.networkMap.Nodes) == 0 {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title,
|
||||
"No network nodes configured.",
|
||||
"",
|
||||
m.styles.Help.Render("[a] Add node [l] Load from file"),
|
||||
)
|
||||
}
|
||||
|
||||
var nodes []string
|
||||
for _, node := range m.networkMap.Nodes {
|
||||
nodes = append(nodes, fmt.Sprintf("• %s (%s): %s", node.Name, node.Type, node.IP))
|
||||
}
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title, lipgloss.JoinVertical(lipgloss.Left, nodes...))
|
||||
}
|
||||
Reference in New Issue
Block a user