Commit af6e956

Ansari <ping@ansari.wtf>
2026-03-02 20:27:49
Refactor policy and rules views for improved styling and functionality
1 parent 694234d
Changed files (6)
internal/sections/policy/view.go
@@ -1,31 +1,17 @@
 package policy
 
 import (
-	"strings"
 	"ufWall/internal/ufw"
 	"ufWall/internal/ui"
 
 	"github.com/charmbracelet/lipgloss"
 )
 
-func (m Model) getPolicyStyle(policy string) lipgloss.Style {
-	switch strings.ToUpper(policy) {
-	case "ALLOW":
-		return m.styles.AllowPolicy
-	case "DENY":
-		return m.styles.DenyPolicy
-	case "REJECT":
-		return m.styles.RejectPolicy
-	default:
-		return m.styles.Value
-	}
-}
-
 func (m Model) View(policy ufw.Policy) string {
 
-	incomingStyle := m.getPolicyStyle(policy.DefaultIncoming)
-	outgoingStyle := m.getPolicyStyle(policy.DefaultOutgoing)
-	routedStyle := m.getPolicyStyle(policy.DefaultRouted)
+	incomingStyle := ui.GetPolicyStyle(m.styles, policy.DefaultIncoming)
+	outgoingStyle := ui.GetPolicyStyle(m.styles, policy.DefaultOutgoing)
+	routedStyle := ui.GetPolicyStyle(m.styles, policy.DefaultRouted)
 
 	incomingLine := lipgloss.JoinHorizontal(
 		lipgloss.Left,
internal/sections/rules/view.go
@@ -2,6 +2,7 @@ package rules
 
 import (
 	"fmt"
+	"regexp"
 	"strings"
 	"ufWall/internal/ufw"
 	"ufWall/internal/ui"
@@ -20,20 +21,21 @@ func (m Model) View(rules []ufw.Rule) string {
 	line := strings.Repeat("─", lipgloss.Width(headerContent))
 	rows = append(rows, "  "+headerContent, "  "+m.styles.Label.UnsetWidth().Render(line))
 
-	sectionActiveNoMenu := m.menu == nil && m.active
+	sectionActiveNoMenu := m.menu == nil && m.active && !m.showDetails && !m.showDeleteConfirm
 
 	for i, r := range rules {
+		action := fmt.Sprintf("%-6s", r.Action)
 		row := fmt.Sprintf(
-			"%-3d │ %-6s │ %-5s │ %-16s │ %-5s │ %-16s │ %-5s",
+			"%-3d │ %6s │ %-5s │ %-16s │ %-5s │ %-16s │ %-5s",
 			r.Num,
-			r.Action,
+			action,
 			r.ToProtocol,
 			r.FromSource,
 			r.FromPort,
 			r.ToDest,
 			r.ToPort,
 		)
-		rows = append(rows, ui.InsertCursor(row, m.cursorLine == i && sectionActiveNoMenu, m.styles))
+		rows = append(rows, ui.InsertCursorRulesSection(row, m.cursorLine == i && sectionActiveNoMenu, m.styles, r.Action))
 	}
 	table := strings.Join(rows, "\n")
 	content := lipgloss.JoinVertical(
@@ -42,3 +44,95 @@ func (m Model) View(rules []ufw.Rule) string {
 	)
 	return ui.TitledBox("Active Rules", content, m.styles, -1, m.active)
 }
+
+// DetailView renders the rule detail overlay
+func (m Model) DetailView() string {
+	if m.detailRule == nil {
+		return ""
+	}
+
+	r := m.detailRule
+
+	// Build detail content
+	var lines []string
+	lines = append(lines, "")
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Rule #:"), m.styles.Value.Render(fmt.Sprintf("%d", r.Num))))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Action:"), ui.GetPolicyStyle(m.styles, r.Action).Render(r.Action)))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Protocol:"), m.styles.Value.Render(r.ToProtocol)))
+	lines = append(lines, "")
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Source:"), m.styles.Value.Render(r.FromSource)))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Source Port:"), m.styles.Value.Render(r.FromPort)))
+	lines = append(lines, "")
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Destination:"), m.styles.Value.Render(r.ToDest)))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Dest Port:"), m.styles.Value.Render(r.ToPort)))
+	lines = append(lines, "")
+
+	if r.Comment != "" {
+		lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Comment:"), m.styles.Value.Render(r.Comment)))
+		lines = append(lines, "")
+	}
+
+	re := regexp.MustCompile(`\s+`)
+	rawText := re.ReplaceAllString(r.Raw, " ")
+	lines = append(lines, fmt.Sprintf("  %s", m.styles.Label.Render("Raw:")))
+	lines = append(lines, fmt.Sprintf("  %s", m.styles.Value.Render(rawText)))
+	lines = append(lines, "")
+	text := m.styles.Label.
+		PaddingTop(1).
+		UnsetWidth().
+		Render("[Press any key to close]")
+
+	centered := lipgloss.Place(
+		50,
+		1,
+		lipgloss.Center,
+		lipgloss.Center,
+		text,
+	)
+
+	lines = append(lines, centered)
+
+	lines = append(lines, "")
+
+	content := strings.Join(lines, "\n")
+
+	title := fmt.Sprintf("Rule #%d Details", r.Num)
+	return ui.TitledBox(title, content, m.styles, -1, true)
+}
+
+func (m Model) DeleteConfirmView() string {
+	if m.deleteRule == nil {
+		return ""
+	}
+
+	r := m.deleteRule
+
+	var lines []string
+	lines = append(lines, "")
+	lines = append(lines, m.styles.Error.Render("  Are you sure you want to delete this rule?"))
+	lines = append(lines, "")
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("Rule #:"), m.styles.Value.Render(fmt.Sprintf("%d", r.Num))))
+	lines = append(lines, fmt.Sprintf("  %s  %s %s", m.styles.Label.Render("Action:"), ui.GetPolicyStyle(m.styles, r.Action).Render(r.Action), m.styles.Value.Render(r.ToPort)))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("From:"), m.styles.Value.Render(r.FromSource)))
+	lines = append(lines, fmt.Sprintf("  %s  %s", m.styles.Label.Render("To:"), m.styles.Value.Render(r.ToDest)))
+	lines = append(lines, "")
+	text := m.styles.Label.
+		PaddingTop(1).
+		UnsetWidth().
+		Render("[y] Yes, delete  [n/Esc] Cancel")
+
+	centered := lipgloss.Place(
+		50,
+		1,
+		lipgloss.Center,
+		lipgloss.Center,
+		text,
+	)
+
+	lines = append(lines, centered)
+	lines = append(lines, "")
+
+	content := strings.Join(lines, "\n")
+
+	return ui.TitledBox("Confirm Delete", content, m.styles, -1, true)
+}
internal/ufw/rules.go
@@ -100,7 +100,7 @@ func parseRuleLine(num int, line string) Rule {
 func findActionIndex(parts []string) int {
 	for i, part := range parts {
 		upper := strings.ToUpper(strings.Fields(part)[0])
-		if upper == "ALLOW" || upper == "DENY" || upper == "REJECT" || upper == "LIMIT" {
+		if upper == ActionAllow || upper == ActionDeny || upper == ActionReject || upper == ActionLimit {
 			return i
 		}
 	}
internal/ui/cursor.go
@@ -1,12 +1,34 @@
 package ui
 
+import (
+	"fmt"
+	"strings"
+)
+
 func InsertCursor(line string, selected bool, styles Styles) string {
 	prefix := "  "
 	style := styles.Value
 	if selected {
 		prefix = "▶ "
-		style = styles.ActiveStatus.Bold(true)
+		style = styles.ActiveCursorPointer
 	}
 	line = prefix + style.Render(line)
 	return line
 }
+
+func InsertCursorRulesSection(line string, selected bool, styles Styles, action string) string {
+	prefix := "  "
+
+	if selected {
+		return "▶ " + styles.ActiveCursorPointer.Render(line)
+	}
+
+	policyStyle := GetPolicyStyle(styles, action)
+
+	actionFormatted := fmt.Sprintf("%-6s", action)
+	styledAction := policyStyle.Render(actionFormatted)
+
+	line = strings.Replace(line, actionFormatted, styledAction, 1)
+
+	return prefix + styles.Value.Render(line)
+}
internal/ui/footer.go
@@ -15,7 +15,7 @@ func Footer(styles Styles, activeSection int, width int) string {
 		keys = []string{"↑↓: navigate", "space: toggle allow/deny"}
 
 	case sections.RulesSection:
-		keys = []string{"↑↓: navigate", "enter: details", "d: delete"}
+		keys = []string{"↑↓: navigate", "enter: actions", "i: info", "d: delete"}
 	default:
 		keys = []string{"tab: next section", "r: refresh"}
 	}
internal/ui/styles.go
@@ -1,6 +1,8 @@
 package ui
 
 import (
+	"strings"
+
 	"github.com/charmbracelet/lipgloss"
 )
 
@@ -16,6 +18,8 @@ type Styles struct {
 	SectionTitle        lipgloss.Style
 	SectionBorderActive lipgloss.Style
 
+	ActiveCursorPointer lipgloss.Style
+
 	Menu lipgloss.Style
 
 	AllowPolicy  lipgloss.Style
@@ -33,16 +37,16 @@ type Styles struct {
 
 func NewStyles() Styles {
 	var (
-		mauve  = lipgloss.Color("#cba6f7")
-		red    = lipgloss.Color("#f38ba8")
-		maroon = lipgloss.Color("#eba0ac")
-		peach  = lipgloss.Color("#fab387")
-		yellow    = lipgloss.Color("#f9e2af")
-		green = lipgloss.Color("#a6e3a1")
-		sky = lipgloss.Color("#89dceb")
+		mauve    = lipgloss.Color("#cba6f7")
+		red      = lipgloss.Color("#f38ba8")
+		maroon   = lipgloss.Color("#eba0ac")
+		peach    = lipgloss.Color("#fab387")
+		yellow   = lipgloss.Color("#f9e2af")
+		green    = lipgloss.Color("#a6e3a1")
+		sky      = lipgloss.Color("#89dceb")
 		lavender = lipgloss.Color("#b4befe")
 
-		text = lipgloss.Color("#cdd6f4")
+		text     = lipgloss.Color("#cdd6f4")
 		subtext0 = lipgloss.Color("#a6adc8")
 		overlay2 = lipgloss.Color("#9399b2")
 		overlay1 = lipgloss.Color("#7f849c")
@@ -68,6 +72,10 @@ func NewStyles() Styles {
 			Bold(true).
 			Foreground(red),
 
+		ActiveCursorPointer: lipgloss.NewStyle().
+			Foreground(yellow).
+			Bold(true),
+
 		StatusLabel: lipgloss.NewStyle().
 			Foreground(text).
 			Bold(true),
@@ -127,3 +135,16 @@ func NewStyles() Styles {
 			Padding(1, 2),
 	}
 }
+
+func GetPolicyStyle(styles Styles, policy string) lipgloss.Style {
+	switch strings.ToUpper(policy) {
+	case "ALLOW":
+		return styles.AllowPolicy
+	case "DENY":
+		return styles.DenyPolicy
+	case "REJECT":
+		return styles.RejectPolicy
+	default:
+		return styles.Value
+	}
+}