Skip to content

Commit

Permalink
Starting implementation of a "responsive" TUI
Browse files Browse the repository at this point in the history
  • Loading branch information
NamelessOne91 committed Jul 20, 2024
1 parent 06a0940 commit 8e48c26
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 80 deletions.
60 changes: 48 additions & 12 deletions tui/models/bisturi_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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":
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
57 changes: 34 additions & 23 deletions tui/models/interfaces_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down
36 changes: 22 additions & 14 deletions tui/models/packets_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
61 changes: 36 additions & 25 deletions tui/models/protocols_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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}
}
9 changes: 3 additions & 6 deletions tui/models/start_menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 8e48c26

Please sign in to comment.