Commit 7827549

Ansari <ping@ansari.wtf>
2026-02-28 07:58:21
temp change - trying out keybind
1 parent b022383
cmd/ufWall/main.go
@@ -10,6 +10,12 @@ import (
 )
 
 func main() {
+	f, err := tea.LogToFile("debug.log", "debug")
+	if err != nil {
+		panic(err)
+	}
+	defer f.Close()
+
 	p := tea.NewProgram(app.InitialModel(), tea.WithAltScreen())
 	if _, err := p.Run(); err != nil {
 		fmt.Printf("Error: %v\n", err)
internal/app/update.go
@@ -1,6 +1,8 @@
 package app
 
 import (
+	"log"
+	"ufWall/internal/sections"
 	"ufWall/internal/ufw"
 
 	"github.com/charmbracelet/bubbles/key"
@@ -13,23 +15,46 @@ func (m model) Init() tea.Cmd {
 
 func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	switch msg := msg.(type) {
+
 	case tea.KeyMsg:
+		switch m.activeSection {
+
+		case sections.StatsSection:
+			newStats, sectionCmd := m.statsSection.Update(msg)
+			m.statsSection = newStats
+			if m.statsSection.HasOpenMenu() {
+				return m, sectionCmd
+			}
+		}
+
 		switch {
 		case key.Matches(msg, Keys.NextSection):
+			m.blurAllSections()
 			m.activeSection = (m.activeSection + 1) % 3
+			m.focusActiveSection()
+			return m, nil
+
 		case key.Matches(msg, Keys.PrevSection):
+			m.blurAllSections()
 			m.activeSection = (m.activeSection - 1 + 3) % 3
+			m.focusActiveSection()
+			return m, nil
+
+		case key.Matches(msg, Keys.Refresh):
+			return m, m.refreshData()
+
 		case key.Matches(msg, Keys.Quit):
+			log.Println("EXITING")
 			return m, tea.Quit
-		case key.Matches(msg, Keys.Refresh):
-			data := ufw.GetUFWData()
-			m.rules = data.Rules
-			m.policy = data.Policy
-			m.stats = data.Stats
-			m.err = data.Error
-			return m, nil
 		}
 
+	case RefreshMsg:
+		m.rules = msg.Rules
+		m.policy = msg.Policy
+		m.stats = msg.Stats
+		m.err = msg.Error
+		return m, nil
+
 	case tea.WindowSizeMsg:
 		m.width = msg.Width
 		m.height = msg.Height
@@ -38,3 +63,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 	return m, nil
 }
+
+func (m *model) blurAllSections() {
+	m.statsSection.Blur()
+	m.policySection.Blur()
+	m.rulesSection.Blur()
+}
+
+func (m *model) focusActiveSection() {
+	switch m.activeSection {
+	case sections.StatsSection:
+		m.statsSection.Focus()
+	case sections.PolicySection:
+		m.policySection.Focus()
+	case sections.RulesSection:
+		m.rulesSection.Focus()
+	}
+}
+
+func (m model) refreshData() tea.Cmd {
+	return func() tea.Msg {
+		data := ufw.GetUFWData()
+		return RefreshMsg{
+			Rules:  data.Rules,
+			Policy: data.Policy,
+			Stats:  data.Stats,
+			Error:  data.Error,
+		}
+	}
+}
+
+type RefreshMsg struct {
+	Rules  []ufw.Rule
+	Policy ufw.Policy
+	Stats  ufw.Stats
+	Error  error
+}
internal/app/view.go
@@ -38,14 +38,14 @@ func (m model) View() string {
 
 	infoSection := lipgloss.JoinHorizontal(
 		lipgloss.Top,
-		m.statsSection.View(m.stats,m.activeSection == sections.StatsSection),
-		m.policySection.View(m.policy,m.activeSection == sections.PolicySection),
+		m.statsSection.View(m.stats),
+		m.policySection.View(m.policy),
 	)
 
 	content := lipgloss.JoinVertical(
 		lipgloss.Left,
 		infoSection,
-		m.rulesSection.View(m.rules,m.activeSection == sections.RulesSection),
+		m.rulesSection.View(m.rules),
 	)
 
 	layout := lipgloss.JoinVertical(
@@ -65,5 +65,27 @@ func (m model) View() string {
 		layout,
 	)
 
+	if m.activeSection == sections.StatsSection && m.statsSection.GetMenu() != nil {
+		// Dim the background
+		dimmed := lipgloss.NewStyle().
+			Foreground(lipgloss.Color("#6c7086")). // Dimmed text
+			Render(layout)
+
+		// Get menu
+		menuView := m.statsSection.GetMenu().View()
+
+		// Center menu over dimmed background
+		centeredMenu := lipgloss.Place(
+			m.width,
+			m.height,
+			lipgloss.Center,
+			lipgloss.Center,
+			menuView,
+		)
+
+		// Use ANSI codes to position menu absolutely
+		return dimmed + layout + centeredMenu // Position at row 10, col 30
+	}
+
 	return layout
 }
internal/sections/policy/model.go
@@ -6,10 +6,25 @@ import (
 
 type Model struct {
 	styles ui.Styles
+	active bool
 }
 
 func New(styles ui.Styles) Model {
 	return Model{
-		styles,
+		styles: styles,
+		active: false,
 	}
 }
+
+func (m *Model) Focus() {
+	// m.cursorLine = 0
+	m.active = true
+}
+
+func (m *Model) Blur() {
+	m.active = false
+	// m.showMenu = false
+	// m.cursorLine = 0
+}
+
+
internal/sections/policy/view.go
@@ -21,7 +21,7 @@ func (m Model) getPolicyStyle(policy string) lipgloss.Style {
 	}
 }
 
-func (m Model) View(policy ufw.Policy,active bool) string {
+func (m Model) View(policy ufw.Policy) string {
 
 	incomingStyle := m.getPolicyStyle(policy.DefaultIncoming)
 	outgoingStyle := m.getPolicyStyle(policy.DefaultOutgoing)
@@ -51,5 +51,5 @@ func (m Model) View(policy ufw.Policy,active bool) string {
 		outgoingLine,
 		routedLine,
 	)
-	return ui.TitledBox("Default Policies", content, m.styles, -1, active)
+	return ui.TitledBox("Default Policies", content, m.styles, -1, m.active)
 }
internal/sections/rules/model.go
@@ -6,10 +6,23 @@ import (
 
 type Model struct {
 	styles ui.Styles
+	active bool
 }
 
 func New(styles ui.Styles) Model {
 	return Model{
-		styles,
+		styles: styles,
+		active: false,
 	}
 }
+
+func (m *Model) Focus() {
+	// m.cursorLine = 0
+	m.active = true
+}
+
+func (m *Model) Blur() {
+	m.active = false
+	// m.showMenu = false
+	// m.cursorLine = 0
+}
internal/sections/rules/view.go
@@ -9,7 +9,7 @@ import (
 	"github.com/charmbracelet/lipgloss"
 )
 
-func (m Model) View(rules []ufw.Rule, active bool) string {
+func (m Model) View(rules []ufw.Rule) string {
 	var rows []string
 
 	header := fmt.Sprintf(
@@ -38,5 +38,5 @@ func (m Model) View(rules []ufw.Rule, active bool) string {
 		lipgloss.Left,
 		table,
 	)
-	return ui.TitledBox("Active Rules", content, m.styles, -1, active)
+	return ui.TitledBox("Active Rules", content, m.styles, -1, m.active)
 }
internal/sections/stats/model.go
@@ -4,12 +4,50 @@ import (
 	"ufWall/internal/ui"
 )
 
+type MenuType int
+
+const (
+	MenuNone     MenuType = iota
+	MenuFirewall          // Enable/Disable
+	MenuLogging           // off/on/low/medium/high/full
+)
+
 type Model struct {
-	styles ui.Styles
+	styles     ui.Styles
+	cursorLine int // 0=status, 1=logging
+	showMenu   bool
+	menu       *ui.Menu
+	menuType   MenuType
+	active     bool
 }
 
 func New(styles ui.Styles) Model {
 	return Model{
-		styles,
+		styles:     styles,
+		cursorLine: 0,
+		showMenu:   false,
+		menu:       nil,
+		menuType:   MenuNone,
+		active:     true,
 	}
 }
+
+func (m *Model) Focus() {
+	m.cursorLine = 0
+	m.active = true
+}
+
+func (m *Model) Blur() {
+	m.active = false
+	m.showMenu = false
+	m.cursorLine = 0
+}
+
+func (m Model) HasOpenMenu() bool {
+	return m.menu != nil
+}
+
+func (m Model) GetMenu() *ui.Menu {
+    return m.menu
+}
+
internal/sections/stats/update.go
@@ -0,0 +1,99 @@
+package stats
+
+import (
+	"ufWall/internal/ui"
+
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
+	if key, ok := msg.(tea.KeyMsg); ok {
+		if m.menu != nil {
+			return m.handleMenuInput(key)
+		}
+		return m.handleNavigation(key)
+	}
+	return m, nil
+}
+
+func (m Model) handleNavigation(key tea.KeyMsg) (Model, tea.Cmd) {
+	switch key.String() {
+	case "up", "k":
+		if m.cursorLine > 0 {
+			m.cursorLine--
+		}
+
+	case "down", "j":
+		if m.cursorLine < 1  {
+			m.cursorLine++
+		}
+
+	case "enter":
+		return m.openMenu()
+	}
+
+	return m, nil
+}
+
+func (m Model) openMenu() (Model, tea.Cmd) {
+	var options []string
+
+	switch m.cursorLine {
+	case 0: 
+		m.menuType = MenuFirewall
+		options = []string{"Enable Firewall", "Disable Firewall"}
+
+	case 1: 		
+		m.menuType = MenuLogging
+		options = []string{"off", "low", "medium", "high", "full"}
+	
+	}
+
+	menu := ui.NewMenu(options, m.styles)
+	m.menu = &menu
+	return m, nil
+}
+
+func (m Model) handleMenuInput(key tea.KeyMsg) (Model, tea.Cmd) {
+	switch key.String() {
+	case "up", "k":
+		m.menu.Up()
+
+	case "down", "j":
+		m.menu.Down()
+
+	case "enter":
+		return m.executeMenuAction()
+
+	case "esc":
+		m.menu = nil
+		m.menuType = MenuNone
+	}
+
+	return m, nil
+}
+
+func (m Model) executeMenuAction() (Model, tea.Cmd) {
+	if m.menu == nil {
+		return m, nil
+	}
+
+	// selectedOption := m.menu.SelectedOption()
+
+	switch m.menuType {
+	case MenuFirewall:
+		// Handle enable/disable
+		// TODO: Call UFW command based on selectedOption
+
+	case MenuLogging:
+		// Handle logging level change
+		// TODO: Call UFW command with selectedOption (off/on/low/etc)
+	}
+
+	// Close menu
+	m.menu = nil
+	m.menuType = MenuNone
+
+	return m, nil
+}
+
internal/sections/stats/view.go
@@ -9,7 +9,7 @@ import (
 	"github.com/charmbracelet/lipgloss"
 )
 
-func (m Model) View(stats ufw.Stats, active bool) string {
+func (m Model) View(stats ufw.Stats) string {
 	var statusText string
 	var statusStyle lipgloss.Style
 
@@ -33,18 +33,20 @@ func (m Model) View(stats ufw.Stats, active bool) string {
 		m.styles.Value.Render(strings.ToUpper(stats.Logging)),
 	)
 
-	totalRulesLine := lipgloss.JoinHorizontal(
+	rulesLine := lipgloss.JoinHorizontal(
 		lipgloss.Left,
 		m.styles.Label.Render("Total Rules:"),
 		m.styles.Value.Render(strconv.Itoa(stats.TotalRules)),
 	)
 
+	sectionActiveNoMenu := m.menu == nil && m.active
+
 	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		statusLine,
-		loggingLine,
-		totalRulesLine,
+		lipgloss.Top,
+		ui.InsertCursor(statusLine, m.cursorLine == 0 && sectionActiveNoMenu, m.styles),
+		ui.InsertCursor(loggingLine, m.cursorLine == 1 && sectionActiveNoMenu, m.styles),
+		ui.InsertCursor(rulesLine, false, m.styles),
 	)
 
-	return ui.TitledBox("Firewall Stats", content, m.styles, -1, active)
+	return ui.TitledBox("Firewall Stats", content, m.styles, -1, m.active)
 }
internal/ui/cursor.go
@@ -0,0 +1,12 @@
+package ui
+
+func InsertCursor(line string, selected bool, styles Styles) string {
+	prefix := "  "
+	style := styles.Value
+	if selected {
+		prefix = "▶ "
+		style = styles.ActiveStatus.Bold(true)
+	}
+	line = prefix + style.Render(line)
+	return line
+}
internal/ui/menu.go
@@ -0,0 +1,67 @@
+// internal/ui/menu.go
+package ui
+
+import (
+	"strings"
+
+	"github.com/charmbracelet/lipgloss"
+)
+
+type Menu struct {
+	Options  []string
+	Selected int
+	styles   Styles
+}
+
+func NewMenu(options []string, styles Styles) Menu {
+	return Menu{
+		Options:  options,
+		Selected: 0,
+		styles:   styles,
+	}
+}
+
+func (m *Menu) Up() {
+	if m.Selected > 0 {
+		m.Selected--
+	}
+}
+
+func (m *Menu) Down() {
+	if m.Selected < len(m.Options)-1 {
+		m.Selected++
+	}
+}
+
+func (m Menu) SelectedOption() string {
+	if m.Selected >= 0 && m.Selected < len(m.Options) {
+		return m.Options[m.Selected]
+	}
+	return ""
+}
+
+func (m Menu) View() string {
+	var lines []string
+
+	for i, option := range m.Options {
+		prefix := "  "
+		style := m.styles.Value
+
+		if i == m.Selected {
+			prefix = "▶ "
+			style = m.styles.ActiveStatus.Bold(true)
+		}
+
+		lines = append(lines, prefix+style.Render(option))
+	}
+
+	content := strings.Join(lines, "\n")
+
+	menuStyle := lipgloss.NewStyle().
+		Border(lipgloss.RoundedBorder()).
+		BorderForeground(lipgloss.Color("#89b4fa")).
+		Padding(1, 2).
+		Background(lipgloss.Color("#1e1e2e"))
+
+	return menuStyle.Render(content)
+}