diff --git a/tui/models/bisturi_model.go b/tui/models/bisturi_model.go index 6131aaa..0250dd7 100644 --- a/tui/models/bisturi_model.go +++ b/tui/models/bisturi_model.go @@ -29,6 +29,8 @@ type errMsg error type packetMsg sockets.NetworkPacket type bisturiModel struct { + terminalHeight int + terminalWidth int step step spinner spinner.Model startMenu startMenuModel @@ -43,20 +45,23 @@ type bisturiModel struct { err error } -func NewBisturiModel() *bisturiModel { - s := spinner.New(spinner.WithSpinner(spinner.Meter)) - s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) - +func newRowsInput(terminalWidth int) textinput.Model { ti := textinput.New() ti.Placeholder = "Enter the max number of rows to display" ti.Focus() ti.CharLimit = 4 - ti.Width = 50 + ti.Width = terminalWidth / 2 + + return ti +} + +func NewBisturiModel() *bisturiModel { + s := spinner.New(spinner.WithSpinner(spinner.Meter)) + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) return &bisturiModel{ - step: retrieveIfaces, - spinner: s, - rowsInput: ti, + step: retrieveIfaces, + spinner: s, } } @@ -93,7 +98,7 @@ func (m bisturiModel) View() string { switch m.step { case retrieveIfaces: - sb.WriteString(fmt.Sprintf("\n\nWelcome!\n\n %s Retrieving network interfaces...\n\n", m.spinner.View())) + sb.WriteString(fmt.Sprintf("\n\nWelcome!\n\nRetrieving network interfaces \n\n%s", m.spinner.View())) case selectIface, selectProtocol: sb.WriteString(m.startMenu.View()) case selectRows: @@ -113,8 +118,14 @@ func (m *bisturiModel) updateLoading(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = msg return m, tea.Quit + case tea.WindowSizeMsg: + m.terminalHeight = msg.Height + m.terminalWidth = msg.Width + + return m, nil + case networkInterfacesMsg: - m.startMenu = newStartMenuModel(msg) + m.startMenu = newStartMenuModel(msg, m.terminalHeight, m.terminalWidth) m.step = selectIface return m, nil @@ -136,6 +147,17 @@ func (m *bisturiModel) updateStartMenuSelection(msg tea.Msg) (tea.Model, tea.Cmd m.startMenu, cmd = m.startMenu.Update(msg) switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.terminalHeight = msg.Height + m.terminalWidth = msg.Height + if m.step == selectIface { + m.startMenu.ifaceList.resize(m.terminalHeight, m.terminalWidth) + } else if m.step == selectProtocol { + m.startMenu.protoList.resize(m.terminalHeight, m.terminalWidth) + } + + return m, nil + case selectedIfaceItemMsg: iface, err := net.InterfaceByName(msg.name) if err != nil { @@ -165,6 +187,7 @@ func (m *bisturiModel) updateStartMenuSelection(msg tea.Msg) (tea.Model, tea.Cmd m.rawSocket = rs m.step = selectRows + m.rowsInput = newRowsInput(m.terminalWidth) return m, nil } return m, cmd @@ -175,6 +198,12 @@ func (m *bisturiModel) updateRowsInput(msg tea.Msg) (tea.Model, tea.Cmd) { m.rowsInput, cmd = m.rowsInput.Update(msg) switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.terminalHeight = msg.Height + m.terminalWidth = msg.Width + + return m, nil + case tea.KeyMsg: switch msg.String() { case "q", "esc", "ctrl+c": @@ -183,7 +212,7 @@ func (m *bisturiModel) updateRowsInput(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": maxRows, err := strconv.Atoi(m.rowsInput.Value()) if err == nil && maxRows > 0 { - m.packetsTable = newPacketsTable(maxRows) + m.packetsTable = newPacketsTable(maxRows, m.terminalWidth) m.packetsChan = make(chan sockets.NetworkPacket) m.errChan = make(chan error) @@ -206,7 +235,14 @@ func (m *bisturiModel) updateReceivingPacket(msg tea.Msg) (tea.Model, tea.Cmd) { m.packetsTable, cmd = m.packetsTable.Update(msg) - switch msg.(type) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.terminalHeight = msg.Height + m.terminalWidth = msg.Width + m.packetsTable.resizeTable(m.terminalWidth) + + return m, nil + case sockets.NetworkPacket: return m, m.waitForPacket } diff --git a/tui/models/interfaces_list.go b/tui/models/interfaces_list.go index 6a77ec4..4264278 100644 --- a/tui/models/interfaces_list.go +++ b/tui/models/interfaces_list.go @@ -29,6 +29,40 @@ type interfacesListModel struct { l list.Model } +func newInterfacesListModel(interfaces []net.Interface, terminalHeight, terminalWidth int) interfacesListModel { + listHeight := (75 * terminalHeight) / 100 + listWidth := (75 * terminalWidth) / 100 + + items := make([]list.Item, len(interfaces)) + for i, iface := range interfaces { + items[i] = ifaceItem{ + name: iface.Name, + flags: iface.Flags.String(), + } + } + + titlesStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")).Blink(true).Bold(true) + ifaceDelegate := list.NewDefaultDelegate() + ifaceDelegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) + + ifaceList := list.New(items, ifaceDelegate, listWidth, listHeight) + ifaceList.Title = "Select a Network Interface" + ifaceList.Styles.Title = titlesStyle + ifaceList.SetShowStatusBar(true) + ifaceList.SetFilteringEnabled(false) + ifaceList.SetShowHelp(true) + + return interfacesListModel{l: ifaceList} +} + +func (m *interfacesListModel) resize(terminalHeight, terminalWidth int) { + listHeight := (75 * terminalHeight) / 100 + listWidth := (75 * terminalWidth) / 100 + + m.l.SetHeight(listHeight) + m.l.SetWidth(listWidth) +} + func (m interfacesListModel) Init() tea.Cmd { return nil } @@ -57,29 +91,6 @@ func (m interfacesListModel) View() string { return m.l.View() } -func newInterfacesListModel(width, height int, interfaces []net.Interface) interfacesListModel { - items := make([]list.Item, len(interfaces)) - for i, iface := range interfaces { - items[i] = ifaceItem{ - name: iface.Name, - flags: iface.Flags.String(), - } - } - - titlesStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")).Blink(true).Bold(true) - ifaceDelegate := list.NewDefaultDelegate() - ifaceDelegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) - - ifaceList := list.New(items, ifaceDelegate, width, height) - ifaceList.Title = "Select a Network Interface" - ifaceList.Styles.Title = titlesStyle - ifaceList.SetShowStatusBar(true) - ifaceList.SetFilteringEnabled(false) - ifaceList.SetShowHelp(true) - - return interfacesListModel{l: ifaceList} -} - func fetchInterfaces() tea.Cmd { return func() tea.Msg { ifaces, err := net.Interfaces() diff --git a/tui/models/packets_table.go b/tui/models/packets_table.go index c3d5eca..1a59e2d 100644 --- a/tui/models/packets_table.go +++ b/tui/models/packets_table.go @@ -25,28 +25,36 @@ type packetsTablemodel struct { counter uint64 } -func newPacketsTable(max int) packetsTablemodel { +func buildTable(rows []table.Row, terminalWidth int) table.Model { + return table.New([]table.Column{ + table.NewColumn(columnKeyID, "#", (2*terminalWidth)/100), + table.NewColumn(columnKeyDate, "Date", (8*terminalWidth)/100), + table.NewColumn(columnKeySource, "Source", (20*terminalWidth)/100), + table.NewColumn(columnKeyDestination, "Destination", (20*terminalWidth)/100), + table.NewColumn(columnKeyInfo, "Info", (46*terminalWidth)/100), + }). + WithRows(rows). + WithBaseStyle(lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("#00cc99")). + Foreground(lipgloss.Color("#00cc99")). + Align(lipgloss.Center), + ) +} + +func newPacketsTable(max int, terminalWidth int) packetsTablemodel { rows := make([]table.Row, 0, max) return packetsTablemodel{ maxRows: max, cachedRows: rows, - table: table.New([]table.Column{ - table.NewColumn(columnKeyID, "#", 5), - table.NewColumn(columnKeyDate, "Date", 18), - table.NewColumn(columnKeySource, "Source", 50), - table.NewColumn(columnKeyDestination, "Destination", 50), - table.NewColumn(columnKeyInfo, "Info", 100), - }). - WithRows(rows). - WithBaseStyle(lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("#00cc99")). - Foreground(lipgloss.Color("#00cc99")). - Align(lipgloss.Center), - ), + table: buildTable(rows, terminalWidth), } } +func (m *packetsTablemodel) resizeTable(terminalWidth int) { + m.table = buildTable(m.cachedRows, terminalWidth) +} + func (m packetsTablemodel) Init() tea.Cmd { return nil } diff --git a/tui/models/protocols_list.go b/tui/models/protocols_list.go index 20427b0..8290c71 100644 --- a/tui/models/protocols_list.go +++ b/tui/models/protocols_list.go @@ -27,6 +27,42 @@ type protocolsListModel struct { l list.Model } +func newProtocolsListModel(terminalHeight, terminalWidth int) protocolsListModel { + listHeight := (75 * terminalHeight) / 100 + listWidth := (75 * terminalWidth) / 100 + + protoDelegate := list.NewDefaultDelegate() + protoDelegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) + + items := []list.Item{ + protoItem{name: "all", ethType: syscall.ETH_P_ALL}, + protoItem{name: "arp", ethType: syscall.ETH_P_ARP}, + protoItem{name: "ip", ethType: syscall.ETH_P_IP}, + protoItem{name: "ipv6", ethType: syscall.ETH_P_IPV6}, + // UDP and TCP are part of IP, need special handling if filtered specifically + protoItem{name: "udp", ethType: syscall.ETH_P_IP}, + protoItem{name: "udp6", ethType: syscall.ETH_P_IPV6}, + protoItem{name: "tcp", ethType: syscall.ETH_P_IP}, + protoItem{name: "tcp6", ethType: syscall.ETH_P_IPV6}, + } + protoList := list.New(items, protoDelegate, listWidth, listHeight) + protoList.Title = "Select a Network Protocol" + protoList.Styles.Title = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")).Blink(true).Bold(true) + protoList.SetShowStatusBar(false) + protoList.SetFilteringEnabled(false) + protoList.SetShowHelp(true) + + return protocolsListModel{l: protoList} +} + +func (m *protocolsListModel) resize(terminalHeight, terminalWidth int) { + listHeight := (75 * terminalHeight) / 100 + listWidth := (75 * terminalWidth) / 100 + + m.l.SetHeight(listHeight) + m.l.SetWidth(listWidth) +} + func (m protocolsListModel) Init() tea.Cmd { return nil } @@ -54,28 +90,3 @@ func (m protocolsListModel) Update(msg tea.Msg) (protocolsListModel, tea.Cmd) { func (m protocolsListModel) View() string { return m.l.View() } - -func newProtocolsListModel(width, height int) protocolsListModel { - protoDelegate := list.NewDefaultDelegate() - protoDelegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) - - items := []list.Item{ - protoItem{name: "all", ethType: syscall.ETH_P_ALL}, - protoItem{name: "arp", ethType: syscall.ETH_P_ARP}, - protoItem{name: "ip", ethType: syscall.ETH_P_IP}, - protoItem{name: "ipv6", ethType: syscall.ETH_P_IPV6}, - // UDP and TCP are part of IP, need special handling if filtered specifically - protoItem{name: "udp", ethType: syscall.ETH_P_IP}, - protoItem{name: "udp6", ethType: syscall.ETH_P_IPV6}, - protoItem{name: "tcp", ethType: syscall.ETH_P_IP}, - protoItem{name: "tcp6", ethType: syscall.ETH_P_IPV6}, - } - protoList := list.New(items, protoDelegate, width, height) - protoList.Title = "Select a Network Protocol" - protoList.Styles.Title = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")).Blink(true).Bold(true) - protoList.SetShowStatusBar(false) - protoList.SetFilteringEnabled(false) - protoList.SetShowHelp(true) - - return protocolsListModel{l: protoList} -} diff --git a/tui/models/start_menu.go b/tui/models/start_menu.go index 8731092..b923d8b 100644 --- a/tui/models/start_menu.go +++ b/tui/models/start_menu.go @@ -21,12 +21,9 @@ type selectedProtocolMsg struct { ethTytpe uint16 } -func newStartMenuModel(interfaces []net.Interface) startMenuModel { - const listHeight = 50 - const listWidth = 50 - - il := newInterfacesListModel(listWidth, listHeight, interfaces) - plm := newProtocolsListModel(listWidth, listHeight) +func newStartMenuModel(interfaces []net.Interface, terminalHeight, terminalWidth int) startMenuModel { + il := newInterfacesListModel(interfaces, terminalHeight, terminalWidth) + plm := newProtocolsListModel(terminalHeight, terminalWidth) return startMenuModel{ step: selectIface,