Commit 6182b6f

Ansari <ping@ansari.wtf>
2026-02-28 23:17:09
policy section added
1 parent 6ca36a7
internal/app/update.go
@@ -37,6 +37,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 			case key.Matches(msg, keys.Bindings.Quit):
 				return m, tea.Quit
+
+			case key.Matches(msg, keys.Bindings.Execute):
+				if !m.stats.Active {
+					ufw.Enable()
+					return m, keys.Refresh()
+				}
 			}
 		}
 
@@ -45,6 +51,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			newStats, sectionCmd := m.statsSection.Update(msg)
 			m.statsSection = newStats
 			return m, sectionCmd
+
+		case sections.PolicySection:
+			newPolicy, sectionCmd := m.policySection.Update(msg, m.policy)
+			m.policySection = newPolicy
+			return m, sectionCmd
 		}
 
 	case keys.RefreshMsg:
internal/app/view.go
@@ -32,6 +32,23 @@ func (m model) View() string {
 		)
 	}
 
+	if !m.stats.Active {
+		prompt := lipgloss.JoinVertical(
+			lipgloss.Center,
+			m.styles.Error.Render("Firewall is disabled"),
+			"",
+			lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("Press Space to activate"),
+			lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("Press Esc or Q to quit"),
+		)
+		return lipgloss.Place(
+			m.width,
+			m.height,
+			lipgloss.Center,
+			lipgloss.Center,
+			prompt,
+		)
+	}
+
 	title := m.styles.Title.
 		Width(containerWidth).
 		Render("Firewall Manager")
internal/keys/keys.go
@@ -35,16 +35,14 @@ var Bindings = KeyMap{
 
 	Refresh: key.NewBinding(
 		key.WithKeys("r"),
-		key.WithHelp("r", "refresh"),
 	),
 
 	Quit: key.NewBinding(
 		key.WithKeys("q", "esc", "ctrl+c"),
-		key.WithHelp("q/esc", "quit"),
 	),
 
 	Execute: key.NewBinding(
-		key.WithKeys("enter"),
+		key.WithKeys(" ", "enter"),
 	),
 }
 
internal/sections/policy/model.go
@@ -5,26 +5,40 @@ import (
 )
 
 type Model struct {
-	styles ui.Styles
-	active bool
+	styles     ui.Styles
+	totalOpts  int
+	cursorLine int 
+	showMenu   bool
+	menu       *ui.Menu
+	active     bool
 }
 
 func New(styles ui.Styles) Model {
 	return Model{
-		styles: styles,
-		active: false,
+		styles:     styles,
+		cursorLine: 0,
+		showMenu:   false,
+		menu:       nil,
+		active:     false,
+		totalOpts:  2,
 	}
 }
 
 func (m *Model) Focus() {
-	// m.cursorLine = 0
+	m.cursorLine = 0
 	m.active = true
 }
 
 func (m *Model) Blur() {
 	m.active = false
-	// m.showMenu = false
-	// m.cursorLine = 0
+	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/policy/update.go
@@ -0,0 +1,35 @@
+package policy
+
+import (
+	"ufWall/internal/keys"
+	"ufWall/internal/ufw"
+
+	"github.com/charmbracelet/bubbles/key"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+func (m Model) Update(msg tea.Msg, policy ufw.Policy) (Model, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch {
+		case key.Matches(msg, keys.Bindings.CursorUp):
+			if m.cursorLine > 0 {
+				m.cursorLine--
+			}
+		case key.Matches(msg, keys.Bindings.CursorDown):
+			if m.cursorLine < m.totalOpts {
+				m.cursorLine++
+			}
+		case key.Matches(msg, keys.Bindings.Execute):
+			switch m.cursorLine {
+			case 0:
+				ufw.DefaultIncoming(!(policy.DefaultIncoming == "ALLOW"))
+			case 1:
+				ufw.DefaultOutgoing(!(policy.DefaultOutgoing == "ALLOW"))
+			case 2:
+				ufw.DefaultRouted(!(policy.DefaultRouted == "ALLOW"))
+			}
+		}
+	}
+	return m, keys.Refresh()
+}
internal/sections/policy/view.go
@@ -45,11 +45,14 @@ func (m Model) View(policy ufw.Policy) string {
 		routedStyle.Render(policy.DefaultRouted),
 	)
 
+	sectionActiveNoMenu := m.menu == nil && m.active
+
 	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		incomingLine,
-		outgoingLine,
-		routedLine,
+		lipgloss.Top,
+		ui.InsertCursor(incomingLine, m.cursorLine == 0 && sectionActiveNoMenu, m.styles),
+		ui.InsertCursor(outgoingLine, m.cursorLine == 1 && sectionActiveNoMenu, m.styles),
+		ui.InsertCursor(routedLine, m.cursorLine == 2 && sectionActiveNoMenu, m.styles),
 	)
+
 	return ui.TitledBox("Default Policies", content, m.styles, -1, m.active)
 }
internal/ufw/main.go
@@ -9,8 +9,8 @@ import (
 )
 
 type Stats struct {
-	Active  bool
-	Logging string
+	Active     bool
+	Logging    string
 	TotalRules int
 }
 
@@ -70,12 +70,12 @@ func extractPolicy(s string) string {
 	return "unknown"
 }
 
-func GetUFWData() (ufwData) {
+func GetUFWData() ufwData {
 	var data ufwData
 
 	numOut, _, errNum := RunSudo("status", "numbered")
 	verbOut, _, errVerb := RunSudo("status", "verbose")
-	
+
 	if errNum != nil && errVerb != nil {
 		data.Error = fmt.Errorf("ufw status: %w", errNum)
 		return data
@@ -139,6 +139,14 @@ func DefaultOutgoing(allow bool) (stdout, stderr string, err error) {
 	return RunSudo("default", pol, "outgoing")
 }
 
+func DefaultRouted(allow bool) (stdout, stderr string, err error) {
+	pol := "deny"
+	if allow {
+		pol = "allow"
+	}
+	return RunSudo("default", pol, "routed")
+}
+
 func SetLogging(level string) (stdout, stderr string, err error) {
 	return RunSudo("logging", level)
 }
internal/ui/footer.go
@@ -9,6 +9,11 @@ func Footer(styles Styles, activeSection int, width int) string {
 	var keys []string
 
 	switch activeSection {
+	case sections.StatsSection:
+		keys = []string{"↑↓: navigate", "space: toggle menu"}
+	case sections.PolicySection:
+		keys = []string{"↑↓: navigate", "space: toggle allow/deny"}
+
 	case sections.RulesSection:
 		keys = []string{"↑↓: navigate", "enter: details", "d: delete"}
 	default: