initial commit
This commit is contained in:
185
internal/tui/keys.go
Normal file
185
internal/tui/keys.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"snitch/internal/collector"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
// search mode captures all input
|
||||
if m.searchActive {
|
||||
return m.handleSearchKey(msg)
|
||||
}
|
||||
|
||||
// detail view only allows closing
|
||||
if m.showDetail {
|
||||
return m.handleDetailKey(msg)
|
||||
}
|
||||
|
||||
// help view only allows closing
|
||||
if m.showHelp {
|
||||
return m.handleHelpKey(msg)
|
||||
}
|
||||
|
||||
return m.handleNormalKey(msg)
|
||||
}
|
||||
|
||||
func (m model) handleSearchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.searchActive = false
|
||||
m.searchQuery = ""
|
||||
case "enter":
|
||||
m.searchActive = false
|
||||
m.cursor = 0
|
||||
case "backspace":
|
||||
if len(m.searchQuery) > 0 {
|
||||
m.searchQuery = m.searchQuery[:len(m.searchQuery)-1]
|
||||
}
|
||||
default:
|
||||
if len(msg.String()) == 1 {
|
||||
m.searchQuery += msg.String()
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) handleDetailKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc", "enter", "q":
|
||||
m.showDetail = false
|
||||
m.selected = nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) handleHelpKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "esc", "enter", "q", "?":
|
||||
m.showHelp = false
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) handleNormalKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
return m, tea.Sequence(tea.ShowCursor, tea.Quit)
|
||||
|
||||
// navigation
|
||||
case "j", "down":
|
||||
m.moveCursor(1)
|
||||
case "k", "up":
|
||||
m.moveCursor(-1)
|
||||
case "g":
|
||||
m.cursor = 0
|
||||
case "G":
|
||||
visible := m.visibleConnections()
|
||||
if len(visible) > 0 {
|
||||
m.cursor = len(visible) - 1
|
||||
}
|
||||
case "ctrl+d":
|
||||
m.moveCursor(m.pageSize() / 2)
|
||||
case "ctrl+u":
|
||||
m.moveCursor(-m.pageSize() / 2)
|
||||
case "ctrl+f", "pgdown":
|
||||
m.moveCursor(m.pageSize())
|
||||
case "ctrl+b", "pgup":
|
||||
m.moveCursor(-m.pageSize())
|
||||
|
||||
// filter toggles
|
||||
case "t":
|
||||
m.showTCP = !m.showTCP
|
||||
m.clampCursor()
|
||||
case "u":
|
||||
m.showUDP = !m.showUDP
|
||||
m.clampCursor()
|
||||
case "l":
|
||||
m.showListening = !m.showListening
|
||||
m.clampCursor()
|
||||
case "e":
|
||||
m.showEstablished = !m.showEstablished
|
||||
m.clampCursor()
|
||||
case "o":
|
||||
m.showOther = !m.showOther
|
||||
m.clampCursor()
|
||||
case "a":
|
||||
m.showTCP = true
|
||||
m.showUDP = true
|
||||
m.showListening = true
|
||||
m.showEstablished = true
|
||||
m.showOther = true
|
||||
|
||||
// sorting
|
||||
case "s":
|
||||
m.cycleSort()
|
||||
case "S":
|
||||
m.sortReverse = !m.sortReverse
|
||||
m.applySorting()
|
||||
|
||||
// search
|
||||
case "/":
|
||||
m.searchActive = true
|
||||
m.searchQuery = ""
|
||||
|
||||
// actions
|
||||
case "enter", " ":
|
||||
visible := m.visibleConnections()
|
||||
if m.cursor < len(visible) {
|
||||
conn := visible[m.cursor]
|
||||
m.selected = &conn
|
||||
m.showDetail = true
|
||||
}
|
||||
case "r":
|
||||
return m, m.fetchData()
|
||||
case "?":
|
||||
m.showHelp = true
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *model) moveCursor(delta int) {
|
||||
visible := m.visibleConnections()
|
||||
m.cursor += delta
|
||||
if m.cursor < 0 {
|
||||
m.cursor = 0
|
||||
}
|
||||
if m.cursor >= len(visible) {
|
||||
m.cursor = len(visible) - 1
|
||||
}
|
||||
if m.cursor < 0 {
|
||||
m.cursor = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) pageSize() int {
|
||||
size := m.height - 6
|
||||
if size < 1 {
|
||||
return 10
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (m *model) cycleSort() {
|
||||
fields := []collector.SortField{
|
||||
collector.SortByLport,
|
||||
collector.SortByProcess,
|
||||
collector.SortByPID,
|
||||
collector.SortByState,
|
||||
collector.SortByProto,
|
||||
}
|
||||
|
||||
for i, f := range fields {
|
||||
if f == m.sortField {
|
||||
m.sortField = fields[(i+1)%len(fields)]
|
||||
m.applySorting()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m.sortField = collector.SortByLport
|
||||
m.applySorting()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user