Files
Nexus-Guard-Suite/.omo/plans/archive/firewall-and-legacy-cleanup.md
datadunia cbacfea7f2
NexusGuard CI / server-core-test (push) Failing after 3m6s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 4s
NexusGuard CI / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI / dashboard-test (push) Failing after 4s
NexusGuard CI / dashboard-dist (push) Has been skipped
chore: update submodule refs, clean up plans/evidence, update .gitignore
2026-06-07 23:53:15 +07:00

56 KiB

Firewall System Fix + Legacy Styling Cleanup

TL;DR

Quick Summary: Fix broken nftables firewall rule system (rules from UI never actually filter traffic), remove dead LinkedDevices component, and modernize ALL remaining legacy-styled views/components to use the Ng* design system with proper @theme tokens.

Deliverables:

  • Working nftables firewall rules (UI rules actually filter kernel traffic)
  • fwd_estab duplicate bug fixed
  • RemoveForwardRule multi-CIDR fix
  • @theme design tokens defined in main.css
  • 11 files restyled with Ng* components + design tokens
  • All 19 alert() → useToast(), all 6 confirm() → useConfirm()
  • LinkedDevices.vue deleted
  • Backend go tests for firewall rule CRUD

Estimated Effort: Large Parallel Execution: YES - 3 waves Critical Path: Task 1 (@theme) → Wave 2 frontend tasks → Final verification


Context

Original Request

User identified LinkedDevices.vue ("Perangkat Tertaut") as broken/redundant and asked:

  1. What is it for? → Answered: legacy component showing device's own data, not actual linked devices
  2. Firewall needs fixing — conflicts with AllowedIPs and nftables → Investigated: found critical backend bugs
  3. Fix with recommendations + check all legacy styling across all menus

Interview Summary

Key Discussions:

  • LinkedDevices.vue: DELETE — broken, redundant, uses emoji/alert/confirm
  • FirewallEditor.vue: Redesign with Ng* design system
  • Backend bugs: fwd_estab 3x duplicate, syncRuleToFirewall completely broken (rules never actually filter), RemoveForwardRule single-handle bug
  • AllowedIPs vs nftables: Separate concerns (routing vs filtering) — UI already separates them correctly
  • Styling: ALL 7 views + 4 components need cleanup (19 alert, 6 confirm, hardcoded colors, emoji)

Research Findings:

  • Ng* components use @theme tokens (bg-bg-surface, text-text-primary, etc.) but main.css has NO @theme block — tokens undefined
  • syncRuleToFirewall() calls AddRangeRule() which creates orphaned nftables SETs with no chain RULE referencing them — UI firewall rules are 100% non-functional
  • AddRangeRule interface inadequate: no protocol, no action, CIDR parsing wrong
  • 3 callers of AddRangeRule: rules.go:133, devices.go:144, peers.go:128
  • useToast() and useConfirm() composables already exist and ready to use
  • All 16 Ng* components created during redesign, ready to use

Metis Review

Identified Gaps (addressed):

  • @theme tokens missing — added as Wave 0 prerequisite task
  • NetManager interface needs new method for proper firewall rules — planned
  • devices.go:144 and peers.go:128 SSH auto-provisioning also use broken AddRangeRule — included in scope
  • RemoveForwardRule multi-CIDR awk bug — included in scope
  • Existing orphaned nftables sets in production — noted, InitNetwork will clean
  • alert/confirm count correction: 21/7 raw → 19/6 after LinkedDevices deletion — confirmed

Work Objectives

Core Objective

Fix the completely non-functional firewall rule system so UI-created rules actually filter traffic in the Linux kernel, and modernize all remaining legacy UI to the Ng* design system.

Concrete Deliverables

  • apps/server-core/internal/firewall/manager.go — new AddFirewallRule/RemoveFirewallRule methods
  • apps/server-core/internal/firewall/nftables_linux.go — fwd_estab fix, new methods, RemoveForwardRule fix
  • apps/server-core/internal/firewall/nftables_stub.go — matching stub methods
  • apps/server-core/api/rules.go — rewritten syncRuleToFirewall + delete cleanup
  • apps/server-core/api/devices.go + peers.go — updated SSH auto-provisioning callers
  • apps/dashboard-ui/src/assets/main.css — @theme design tokens
  • apps/dashboard-ui/src/components/LinkedDevices.vue — DELETED
  • apps/dashboard-ui/src/components/FirewallEditor.vue — redesigned with Ng*
  • 9 more .vue files restyled (see TODOs)

Definition of Done

  • cd apps/server-core && go build ./... passes
  • cd apps/server-core && go test ./... -tags dev -count=1 all pass
  • cd apps/dashboard-ui && npm run build passes with 0 errors
  • grep -r "alert(" --include="*.vue" src/ returns 0 matches
  • grep -r "LinkedDevices" --include="*.vue" --include="*.ts" src/ returns 0 matches
  • grep -r "confirm(" --include="*.vue" src/ | grep -v useConfirm | grep -v handleConfirm | grep -v ConfirmModal | grep -v "\/\/" returns 0 matches

Must Have

  • Firewall rules created via UI must produce actual nft rules in FORWARD chain
  • All alert()/confirm() replaced with useToast()/useConfirm()
  • LinkedDevices.vue deleted with all references
  • @theme tokens defined so Ng* components render correctly
  • Port range support (e.g., 80-443)
  • Protocol selection (tcp/udp/both) honored in nft rules
  • Action (accept/drop) honored in nft rules

Must NOT Have (Guardrails)

  • NEVER use nft flush table — destroys all peer isolation
  • NEVER touch AddForwardRule, AddInputRule, RemoveInputRule, AddUserIsolation — they work correctly
  • NEVER introduce new Ng* components — use existing 16 only
  • NEVER touch Login.vue — already redesigned
  • NEVER touch style.css — dead scaffold code
  • NEVER add logging/audit trails to firewall backend
  • NEVER refactor admin-only checks (known debt, out of scope)
  • NEVER add sorting/filtering/pagination to FirewallEditor rule table
  • Port format: single port or dash-range ONLY (e.g., 80 or 8000-9000). No comma-separated.

Verification Strategy

ZERO HUMAN INTERVENTION - ALL verification is agent-executed. No exceptions.

Test Decision

  • Infrastructure exists: YES (Go: go test, Frontend: npm run build / vue-tsc)
  • Automated tests: YES (backend tests after implementation)
  • Framework: Go testing package for backend; no frontend unit tests
  • Test approach: Tests-after for backend; agent QA for frontend

QA Policy

Every task MUST include agent-executed QA scenarios. Evidence saved to .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}.

  • Backend: Use Bash (go test, go build) — compile, run tests, verify output
  • Frontend/UI: Use Bash (npm run build, grep) — type-check, verify no legacy patterns remain

Execution Strategy

Parallel Execution Waves

Wave 0 (Prerequisite — must complete first):
└── Task 1: Define @theme design tokens in main.css [quick]

Wave 1 (Backend firewall — all parallel):
├── Task 2: Fix InitNetwork fwd_estab duplicates [quick]
├── Task 3: Add AddFirewallRule/RemoveFirewallRule to NetManager interface [quick]
├── Task 4: Rewrite syncRuleToFirewall + fix rule delete cleanup [quick]
├── Task 5: Fix RemoveForwardRule multi-CIDR handling [quick]
├── Task 6: Update SSH auto-provisioning callers [quick]
└── Task 7: Add backend tests for firewall rule CRUD [quick]

Wave 2 (Frontend — all parallel, depends on Task 1):
├── Task 8: Delete LinkedDevices.vue + clean references [quick]
├── Task 9: Redesign FirewallEditor.vue with Ng* design system [quick]
├── Task 10: Restyle DeviceDetail.vue [quick]
├── Task 11: Restyle Devices.vue [quick]
├── Task 12: Restyle Dashboard.vue [quick]
├── Task 13: Restyle Servers.vue modals/forms [quick]
├── Task 14: Restyle Users.vue [quick]
├── Task 15: Restyle AddPeerModal.vue + PeerConfigModal.vue [quick]
├── Task 16: Restyle ShareConfig.vue [quick]
└── Task 17: Restyle TrafficChart.vue SVG colors [quick]

Wave FINAL (After ALL tasks — 4 parallel reviews):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
-> Present results -> Get explicit user okay

Dependency Matrix

Task Depends On Blocks Wave
1 - 8-17 0
2 - 7 1
3 - 4, 5, 6, 7 1
4 3 7 1
5 - 7 1
6 3 7 1
7 2, 3, 4, 5, 6 FINAL 1
8 1 FINAL 2
9 1 FINAL 2
10 1, 8 FINAL 2
11-17 1 FINAL 2
F1-F4 ALL - FINAL

Agent Dispatch Summary

  • Wave 0: 1 — T1 → quick
  • Wave 1: 6 — T2-T6 → quick, T7 → quick
  • Wave 2: 10 — T8-T17 → quick
  • FINAL: 4 — F1 → oracle, F2 → unspecified-high, F3 → unspecified-high, F4 → deep

TODOs

  • 1. Define @theme design tokens in main.css

    What to do:

    • Add @theme block to apps/dashboard-ui/src/assets/main.css after @import "tailwindcss";
    • Define ALL color tokens used by existing Ng* components (found by grepping src/components/ui/*.vue):
      • Background: --color-bg-base, --color-bg-surface, --color-bg-elevated, --color-bg-overlay
      • Text: --color-text-primary, --color-text-secondary, --color-text-muted
      • Border: --color-border-subtle, --color-border-default, --color-border-strong
      • Accent: --color-accent, --color-accent-hover
      • Semantic: --color-danger, --color-success, --color-warning
      • Shadow: --shadow-glow
    • Color palette: dark glassmorphism theme — bg-base ~#0a0a14, bg-surface ~#12121e, bg-elevated ~#1a1a2e, accent = cyan (#06b6d4), danger = red (#ef4444)
    • Verify existing Ng* components render correctly after tokens are defined

    Must NOT do:

    • Do NOT modify any Ng* component files
    • Do NOT touch style.css (dead code)
    • Do NOT add custom utility classes beyond what @theme provides

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO (prerequisite for all frontend tasks)
    • Parallel Group: Wave 0
    • Blocks: Tasks 8-17
    • Blocked By: None

    References:

    • apps/dashboard-ui/src/assets/main.css — Current file (17 lines: @import + fadeInUp animation only)
    • apps/dashboard-ui/src/components/ui/NgCard.vue:14-17 — Uses bg-bg-surface, bg-bg-elevated, border-border-subtle, border-border-default, shadow-glow
    • apps/dashboard-ui/src/components/ui/NgButton.vue:18-22 — Uses bg-accent, bg-accent-hover, bg-bg-elevated, bg-bg-surface, text-text-primary, text-text-secondary, bg-danger, ring-accent, ring-offset-bg-base
    • apps/dashboard-ui/src/components/ui/NgInput.vue:49-51 — Uses bg-bg-base, text-text-primary, placeholder-text-muted, border-border-default, border-border-strong, ring-accent, border-danger, ring-danger
    • apps/dashboard-ui/src/components/ui/NgModal.vue:46,63,70,77 — Uses bg-bg-overlay, from-bg-surface, to-bg-elevated, border-border-subtle, text-text-primary, text-text-muted, text-text-secondary
    • apps/dashboard-ui/vite.config.ts — Vite 8 + @tailwindcss/vite plugin (TailwindCSS v4 syntax, @theme directive)
    • TailwindCSS v4 docs: @theme defines custom design tokens as CSS custom properties that generate utility classes

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: @theme tokens compile and generate utilities
      Tool: Bash
      Preconditions: apps/dashboard-ui has node_modules installed
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
        2. Check exit code is 0
        3. Run: grep -c "@theme" src/assets/main.css
      Expected Result: Build succeeds. grep returns 1 (one @theme block exists)
      Evidence: .sisyphus/evidence/task-1-theme-build.txt
    
    Scenario: All required token names defined
      Tool: Bash
      Steps:
        1. Run: grep -E "bg-base|bg-surface|bg-elevated|bg-overlay|text-primary|text-secondary|text-muted|border-subtle|border-default|border-strong|accent|accent-hover|danger|success|warning|shadow-glow" apps/dashboard-ui/src/assets/main.css | wc -l
      Expected Result: At least 15 matches (all tokens present)
      Evidence: .sisyphus/evidence/task-1-token-audit.txt
    

    Commit: YES (Commit A)

    • Message: feat(dashboard): add @theme design tokens to main.css
    • Files: src/assets/main.css
    • Pre-commit: npm run build
  • 2. Fix InitNetwork fwd_estab duplicates in nftables_linux.go

    What to do:

    • DELETE lines 67, 75, 83 in nftables_linux.go — these are copy-paste duplicates that insert fwd_estab into FORWARD chain from inside the INPUT chain section
    • Keep ONLY line 43 (the correct one inside the FORWARD wg_isolation guard)
    • Verify the idempotency guard: line 43 is inside if exec.Command("sh", "-c", checkWgDrop).Run() != nil — this means fwd_estab is only added when wg_isolation doesn't exist yet. This is correct behavior.

    Must NOT do:

    • Do NOT touch the INPUT chain rules (lines 48-84 excluding the fwd_estab duplicates)
    • Do NOT change wg_isolation rule
    • Do NOT use nft flush table
    • Do NOT modify AddForwardRule, AddInputRule, or any method outside InitNetwork

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 3, 4, 5, 6)
    • Blocks: Task 7
    • Blocked By: None

    References:

    • apps/server-core/internal/firewall/nftables_linux.go:25-87 — Full InitNetwork() function
    • Line 43: Correct fwd_estab insert (inside FORWARD wg_isolation guard)
    • Line 67: DUPLICATE — inside INPUT section, inserts into FORWARD
    • Line 75: DUPLICATE — inside INPUT section, inserts into FORWARD
    • Line 83: DUPLICATE — inside INPUT section, inserts into FORWARD
    • All 3 duplicates lack idempotency guards (no grep check), so every InitNetwork() call creates new handles

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: fwd_estab appears exactly once in InitNetwork
      Tool: Bash
      Steps:
        1. Run: grep -c "fwd_estab" apps/server-core/internal/firewall/nftables_linux.go
      Expected Result: Exactly 1 match (line 43 area only)
      Evidence: .sisyphus/evidence/task-2-fwd-estab-count.txt
    
    Scenario: Build compiles successfully
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go build ./...
      Expected Result: Exit code 0, no errors
      Evidence: .sisyphus/evidence/task-2-go-build.txt
    

    Commit: NO (groups with Commit B)

  • 3. Add AddFirewallRule/RemoveFirewallRule to NetManager interface

    What to do:

    • Add new method to manager.go interface:
      AddFirewallRule(ruleName string, sourceIP net.IP, destCIDR string, portRange string, protocol string, action string) error
      RemoveFirewallRule(ruleName string) error
      
    • Implement in nftables_linux.go:
      • AddFirewallRule: Generate proper nft insert rule ip nexusguard forward ip saddr {sourceIP} ip daddr {destCIDR} {protocol} dport {portRange} {action} comment "fwrule_{ruleName}"
      • Handle protocol: tcp, udp, or omit for both
      • Handle portRange: empty = all ports (no dport match), single port 80, range 80-443
      • Handle action: accept or drop
      • Handle destCIDR: single IP auto-appended /32, CIDR passed as-is
      • RemoveFirewallRule: Find and delete ALL handles matching comment "fwrule_{ruleName}" (loop over awk output)
    • Implement stubs in nftables_stub.go (return nil)
    • Do NOT remove old AddRangeRule/RemoveRangeRule yet — keep for backward compatibility until callers are updated

    Must NOT do:

    • Do NOT remove AddRangeRule/RemoveRangeRule from interface (callers still reference them until Task 4/6)
    • Do NOT touch AddForwardRule, AddInputRule, or any other existing methods
    • Do NOT use nft flush table

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 2, 5)
    • Blocks: Tasks 4, 6, 7
    • Blocked By: None

    References:

    • apps/server-core/internal/firewall/manager.go — Full interface (17 lines). New methods follow existing pattern.
    • apps/server-core/internal/firewall/nftables_linux.go:181-200AddForwardRule as PATTERN to follow: uses nft insert rule, handles comma-separated CIDRs, uses comment for identification, uses exec.Command
    • apps/server-core/internal/firewall/nftables_linux.go:202-211RemoveForwardRule as PATTERN but with KNOWN BUG (single handle). New RemoveFirewallRule must loop ALL handles.
    • apps/server-core/internal/firewall/nftables_stub.go — All methods return nil, one-liner pattern

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: Interface compiles with new methods
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go build ./...
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-3-go-build.txt
    
    Scenario: New methods exist in all 3 files
      Tool: Bash
      Steps:
        1. Run: grep -c "AddFirewallRule" apps/server-core/internal/firewall/manager.go apps/server-core/internal/firewall/nftables_linux.go apps/server-core/internal/firewall/nftables_stub.go
      Expected Result: 1 match per file (3 total)
      Evidence: .sisyphus/evidence/task-3-method-check.txt
    

    Commit: NO (groups with Commit B)

  • 4. Rewrite syncRuleToFirewall + fix rule delete in rules.go

    What to do:

    • Rewrite syncRuleToFirewall() in rules.go to use new AddFirewallRule():
      func (h *RulesHandler) syncRuleToFirewall(rule models.FirewallRule, deviceName string) {
          // Get device's internal IP for source matching
          var device models.Device
          if err := h.db.Where("id = ?", rule.DeviceID).First(&device).Error; err != nil {
              return
          }
          sourceIP := net.ParseIP(device.InternalIP)
          if sourceIP == nil {
              return
          }
      
          destCIDR := rule.DestIPRange
          // Single IP → append /32
          if !strings.Contains(destCIDR, "/") {
              destCIDR += "/32"
          }
      
          h.fw.AddFirewallRule(
              rule.ID.String(),
              sourceIP,
              destCIDR,
              rule.DestPortRange,  // "" = all ports, "80" = single, "80-443" = range
              rule.Protocol,       // "tcp", "udp", "both"
              rule.Action,         // "accept", "drop"
          )
      }
      
    • Update Delete() handler to use RemoveFirewallRule() instead of RemoveRangeRule():
      h.fw.RemoveFirewallRule(rule.ID.String())
      
    • Add input validation in Create(): validate DestPortRange format (empty, single number, or N-N range)
    • Add input validation: validate Action is accept or drop only
    • Add input validation: validate Protocol is tcp, udp, or both only

    Must NOT do:

    • Do NOT change the API request/response shape (keep CreateRuleRequest struct)
    • Do NOT add new fields to the model
    • Do NOT add logging or audit trails

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES (after Task 3)
    • Parallel Group: Wave 1
    • Blocks: Task 7
    • Blocked By: Task 3

    References:

    • apps/server-core/api/rules.go — Full file (135 lines). syncRuleToFirewall at line 114-135, Delete at line 83-112, Create at line 49-81
    • apps/server-core/api/helpers.go — Has validateAllowedIPs() pattern to follow for validation
    • apps/server-core/internal/models/models.go:95-112FirewallRule model: ID, DeviceID, DestIPRange, DestPortRange, Protocol, Action
    • apps/server-core/api/rules.go:109 — Current delete uses RemoveRangeRule("rule_" + rule.ID.String()) → change to RemoveFirewallRule(rule.ID.String())

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: syncRuleToFirewall uses new AddFirewallRule
      Tool: Bash
      Steps:
        1. Run: grep "AddFirewallRule" apps/server-core/api/rules.go
        2. Run: grep "AddRangeRule" apps/server-core/api/rules.go
      Expected Result: AddFirewallRule found, AddRangeRule NOT found
      Evidence: .sisyphus/evidence/task-4-sync-rewrite.txt
    
    Scenario: Delete uses RemoveFirewallRule
      Tool: Bash
      Steps:
        1. Run: grep "RemoveFirewallRule" apps/server-core/api/rules.go
        2. Run: grep "RemoveRangeRule" apps/server-core/api/rules.go
      Expected Result: RemoveFirewallRule found, RemoveRangeRule NOT found
      Evidence: .sisyphus/evidence/task-4-delete-fix.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go build ./...
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-4-build.txt
    

    Commit: NO (groups with Commit B)

  • 5. Fix RemoveForwardRule multi-CIDR handling

    What to do:

    • Fix RemoveForwardRule() in nftables_linux.go to handle multiple handles (multi-CIDR peers)
    • Current bug: awk '/comment \\"peer_xxx\\"/ {print $NF}' returns only the LAST match when piped to a single string. Only one handle is deleted.
    • Fix: Loop over ALL matching handles. Use approach:
      func (m *LinuxManager) RemoveForwardRule(peerName string) error {
          // Match both exact "peer_xxx" and suffixed "peer_xxx_0", "peer_xxx_1" patterns
          cmdStr := fmt.Sprintf(`nft -a list chain ip nexusguard forward | grep -E 'comment "peer_%s(_[0-9]+)?"' | awk '{print $NF}'`, peerName)
          out, err := exec.Command("sh", "-c", cmdStr).Output()
          if err != nil || len(out) == 0 {
              return nil
          }
          for _, handle := range strings.Split(strings.TrimSpace(string(out)), "\n") {
              handle = strings.TrimSpace(handle)
              if handle == "" {
                  continue
              }
              exec.Command("sh", "-c", fmt.Sprintf("nft delete rule ip nexusguard forward handle %s", handle)).Run()
          }
          return nil
      }
      
    • Note: AddForwardRule creates rules with comments peer_xxx (single CIDR) or peer_xxx_0, peer_xxx_1 (multi-CIDR). The grep pattern must match all variants.

    Must NOT do:

    • Do NOT modify AddForwardRule — it works correctly
    • Do NOT use nft flush table

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 2, 3)
    • Blocks: Task 7
    • Blocked By: None

    References:

    • apps/server-core/internal/firewall/nftables_linux.go:202-211 — Current buggy RemoveForwardRule (single handle)
    • apps/server-core/internal/firewall/nftables_linux.go:181-200AddForwardRule creates comments peer_{name} and peer_{name}_{i} for multi-CIDR
    • Line 190-192: Suffix logic — suffix = fmt.Sprintf("_%d", i) for multi-CIDR, empty string for single CIDR

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: RemoveForwardRule loops over all handles
      Tool: Bash
      Steps:
        1. Run: grep -A 15 "func.*RemoveForwardRule" apps/server-core/internal/firewall/nftables_linux.go
        2. Verify the output contains a loop (for/range or while) over handles
        3. Verify grep pattern matches both "peer_xxx" and "peer_xxx_N" suffixes
      Expected Result: Function contains loop + regex pattern for suffixed comments
      Evidence: .sisyphus/evidence/task-5-remove-forward.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go build ./...
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-5-build.txt
    

    Commit: NO (groups with Commit B)

  • 6. Update SSH auto-provisioning callers (devices.go + peers.go)

    What to do:

    • Update apps/server-core/api/devices.go:144 — replace AddRangeRule call with AddFirewallRule:
      // Before: _ = h.fw.AddRangeRule("rule_"+rule.ID.String(), ip, ip, 22, 22)
      // After:
      _ = h.fw.AddFirewallRule(rule.ID.String(), sourceIP, rule.DestIPRange, "22", "tcp", "accept")
      
      Where sourceIP is the device's InternalIP (already available in the handler context)
    • Update apps/server-core/api/peers.go:128 — same pattern as devices.go
    • Read surrounding code context to get the correct sourceIP variable name (the device's internal IP is available in the handler)
    • After updating both callers, AddRangeRule and RemoveRangeRule can be removed from the interface + all implementations (cleanup)

    Must NOT do:

    • Do NOT change the SSH auto-provisioning logic (still creates port 22 TCP accept rule)
    • Do NOT change the FirewallRule model or DB operations
    • Do NOT remove AddRangeRule/RemoveRangeRule until AFTER both callers are updated

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES (after Task 3)
    • Parallel Group: Wave 1
    • Blocks: Task 7
    • Blocked By: Task 3

    References:

    • apps/server-core/api/devices.go:144 — Current: _ = h.fw.AddRangeRule("rule_"+rule.ID.String(), ip, ip, 22, 22). Read lines 130-150 for full context to find the sourceIP variable.
    • apps/server-core/api/peers.go:128 — Current: _ = h.fw.AddRangeRule("rule_"+rule.ID.String(), ip, ip, 22, 22). Read lines 115-135 for full context.
    • apps/server-core/internal/firewall/manager.go — After cleanup, remove AddRangeRule/RemoveRangeRule from interface
    • apps/server-core/internal/firewall/nftables_linux.go:154-177 — Remove AddRangeRule/RemoveRangeRule implementations
    • apps/server-core/internal/firewall/nftables_stub.go:22-23 — Remove stub methods
    • apps/server-core/internal/firewall/nftables_test.go:40-52 — Remove or update TestRangeRule

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No more AddRangeRule callers in API
      Tool: Bash
      Steps:
        1. Run: grep -r "AddRangeRule" apps/server-core/
      Expected Result: 0 matches (removed from interface, impls, callers, and tests)
      Evidence: .sisyphus/evidence/task-6-no-rangerule.txt
    
    Scenario: AddFirewallRule used in devices.go and peers.go
      Tool: Bash
      Steps:
        1. Run: grep "AddFirewallRule" apps/server-core/api/devices.go apps/server-core/api/peers.go
      Expected Result: 1 match per file (2 total)
      Evidence: .sisyphus/evidence/task-6-new-callers.txt
    
    Scenario: Build succeeds after cleanup
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go build ./...
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-6-build.txt
    

    Commit: NO (groups with Commit B)

  • 7. Add backend tests for firewall rule CRUD

    What to do:

    • Add/update tests in apps/server-core/api/rules_test.go (if exists) or create it:
      • Test Create handler: valid rule with IP/CIDR, port range, protocol, action → 201 Created
      • Test Create handler: invalid port format → 400
      • Test Create handler: invalid action → 400
      • Test Create handler: invalid protocol → 400
      • Test Delete handler: existing rule → 200, verify rule removed from DB
      • Test List handler: returns rules for a device
    • Update apps/server-core/internal/firewall/nftables_test.go:
      • Replace TestRangeRule with TestFirewallRule testing new AddFirewallRule/RemoveFirewallRule via stub
    • Run all tests: go test ./... -tags dev -count=1

    Must NOT do:

    • Do NOT write tests that require a running Linux nftables kernel — use stub manager
    • Do NOT mock the database — use GORM in-memory SQLite (pattern from existing tests)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO (depends on all Wave 1 tasks)
    • Parallel Group: Wave 1 (sequential after Tasks 2-6)
    • Blocks: Final verification
    • Blocked By: Tasks 2, 3, 4, 5, 6

    References:

    • apps/server-core/internal/firewall/nftables_test.go — Existing test file (52 lines), has TestRangeRule to replace
    • apps/server-core/internal/models/models_test.go — Pattern for GORM in-memory SQLite test setup (lines 1-30)
    • apps/server-core/api/rules.go — Handler code to test
    • Check if apps/server-core/api/rules_test.go exists — if so, read it for existing patterns

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: All tests pass
      Tool: Bash
      Steps:
        1. Run: cd apps/server-core && go test ./... -tags dev -count=1 -v 2>&1 | tail -30
      Expected Result: All tests PASS, exit code 0
      Evidence: .sisyphus/evidence/task-7-test-results.txt
    
    Scenario: New firewall tests exist
      Tool: Bash
      Steps:
        1. Run: grep -c "TestFirewallRule\|TestCreate.*Rule\|TestDelete.*Rule\|TestList.*Rule" apps/server-core/internal/firewall/nftables_test.go apps/server-core/api/rules_test.go 2>/dev/null || echo "no test files"
      Expected Result: At least 3 test functions found
      Evidence: .sisyphus/evidence/task-7-test-count.txt
    

    Commit: YES (Commit B)

    • Message: fix(firewall): rewrite nftables rule system + fix fwd_estab duplicates
    • Files: internal/firewall/manager.go, internal/firewall/nftables_linux.go, internal/firewall/nftables_stub.go, internal/firewall/nftables_test.go, api/rules.go, api/rules_test.go, api/devices.go, api/peers.go
    • Pre-commit: go test ./... -tags dev -count=1
  • 8. Delete LinkedDevices.vue + clean all references

    What to do:

    • Delete apps/dashboard-ui/src/components/LinkedDevices.vue
    • Remove import and template usage from DeviceDetail.vue:
      • Line 175: <LinkedDevices v-if="device.InternalIP" :device-id="device.ID" /> → DELETE
      • Line 220: import LinkedDevices from '../components/LinkedDevices.vue' → DELETE
    • Remove src/components/LinkedDevices.vue from any tsconfig references if applicable
    • Verify no other files reference LinkedDevices

    Must NOT do:

    • Do NOT modify any other component files
    • Do NOT remove the FirewallEditor section from DeviceDetail.vue (it stays)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 9-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/components/LinkedDevices.vue — 84 lines, DELETE entirely
    • apps/dashboard-ui/src/views/DeviceDetail.vue:175 — Template reference: <LinkedDevices v-if="device.InternalIP" :device-id="device.ID" />
    • apps/dashboard-ui/src/views/DeviceDetail.vue:220 — Import: import LinkedDevices from '../components/LinkedDevices.vue'

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: LinkedDevices.vue deleted
      Tool: Bash
      Steps:
        1. Run: test -f apps/dashboard-ui/src/components/LinkedDevices.vue && echo "EXISTS" || echo "DELETED"
      Expected Result: DELETED
      Evidence: .sisyphus/evidence/task-8-deleted.txt
    
    Scenario: No references to LinkedDevices remain
      Tool: Bash
      Steps:
        1. Run: grep -r "LinkedDevices" --include="*.vue" --include="*.ts" apps/dashboard-ui/src/
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-8-no-refs.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-8-build.txt
    

    Commit: YES (Commit C — group with Task 9)

    • Message: refactor(dashboard): delete LinkedDevices + redesign FirewallEditor
    • Files: src/components/LinkedDevices.vue (delete), src/views/DeviceDetail.vue
  • 9. Redesign FirewallEditor.vue with Ng* design system

    What to do:

    • Complete rewrite of apps/dashboard-ui/src/components/FirewallEditor.vue:
      • Replace raw <table> with NgTable or keep structured table but use NgCard wrapper
      • Replace all hardcoded text-cyan-400, bg-gray-900/50, border-white/10 etc. with design tokens
      • Replace alert() (if any) with useToast()
      • Use NgButton for Add Rule button
      • Use NgInput for form fields
      • Add NgBadge for Action (accept=success, drop=danger)
      • Keep the component functional — same CRUD operations, same API calls
      • Add success toast after rule creation/deletion
      • Add proper error display (NgBadge or inline error, not alert())
      • Validate port format: empty, single number, or N-N range

    Must NOT do:

    • Do NOT change the API contract (same fetchRules/createRule/deleteRule calls)
    • Do NOT add sorting/filtering/pagination to the rule table
    • Do NOT introduce new Ng* components beyond what already exists

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8, 10-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/components/FirewallEditor.vue — Current file (127 lines). Has raw table, hardcoded colors, no error toasts.
    • apps/dashboard-ui/src/components/ui/NgCard.vue — Use as wrapper
    • apps/dashboard-ui/src/components/ui/NgInput.vue — Use for IP, port inputs
    • apps/dashboard-ui/src/components/ui/NgSelect.vue — Use for protocol, action dropdowns
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for Add Rule button
    • apps/dashboard-ui/src/components/ui/NgBadge.vue — Use for Action badges (accept/drop)
    • apps/dashboard-ui/src/components/ui/NgEmptyState.vue — Use for empty rule list
    • apps/dashboard-ui/src/composables/useToast.ts — For success/error toasts
    • apps/dashboard-ui/src/api/rules.ts — API contract (unchanged): fetchRules, createRule, deleteRule

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: FirewallEditor uses Ng* components
      Tool: Bash
      Steps:
        1. Run: grep -c "NgCard\|NgInput\|NgSelect\|NgButton\|NgBadge\|NgEmptyState\|useToast" apps/dashboard-ui/src/components/FirewallEditor.vue
      Expected Result: At least 3 matches (NgCard + useToast + at least one other)
      Evidence: .sisyphus/evidence/task-9-ng-components.txt
    
    Scenario: No hardcoded colors remain in FirewallEditor
      Tool: Bash
      Steps:
        1. Run: grep -E "text-cyan-|text-green-|text-red-|bg-gray-|bg-black/|border-white/" apps/dashboard-ui/src/components/FirewallEditor.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-9-no-hardcoded.txt
    
    Scenario: No alert() in FirewallEditor
      Tool: Bash
      Steps:
        1. Run: grep "alert(" apps/dashboard-ui/src/components/FirewallEditor.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-9-no-alert.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-9-build.txt
    

    Commit: YES (Commit C — group with Task 8)

    • Message: refactor(dashboard): delete LinkedDevices + redesign FirewallEditor
    • Files: src/components/FirewallEditor.vue
    • Pre-commit: npm run build
  • 10. Restyle DeviceDetail.vue

    What to do:

    • Replace ALL alert() calls with useToast():
      • alert('Failed to regenerate token')toast.error('Failed to regenerate token')
      • alert('Failed to save advanced settings')toast.error('Failed to save advanced settings')
      • alert('Failed to regenerate keys')toast.error('Failed to regenerate keys')
      • alert('Failed to delete device')toast.error('Failed to delete device')
      • alert('Failed to toggle suspension')toast.error('Failed to toggle suspension')
      • alert('Token copied to clipboard!')toast.success('Token copied to clipboard!')
    • Replace ALL confirm() with useConfirm():
      • confirm('This will invalidate...')await confirm('This will invalidate...')
      • confirm('Are you absolutely sure...')await confirm('Are you absolutely sure...')
      • confirm('This will regenerate...')await confirm('This will regenerate...')
    • Replace emoji ⚙️ (line 66) with @iconify/vue icon (e.g., heroicons:cog-6-tooth)
    • Replace all hardcoded Tailwind colors with design tokens (text-cyan-400text-accent, bg-gray-900/90bg-surface, etc.)
    • Use NgCard for sections (keys display, advanced settings, connection status)
    • Use NgInput for advanced settings form fields
    • Use NgToggle for Allow Internet, Suspend, Disable PSK toggles (if NgToggle exists, else keep custom toggle with token colors)

    Must NOT do:

    • Do NOT change the API calls or data flow
    • Do NOT change the component's functionality
    • Do NOT remove the FirewallEditor or PeerConfigModal sections

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8, 9, 11-17)
    • Blocks: Final verification
    • Blocked By: Task 1, Task 8 (must delete LinkedDevices first since it was in DeviceDetail)

    References:

    • apps/dashboard-ui/src/views/DeviceDetail.vue — Full file (392 lines). 6 alert(), 3 confirm(), emoji ⚙️, extensive hardcoded colors.
    • apps/dashboard-ui/src/composables/useToast.tstoast.success(msg), toast.error(msg)
    • apps/dashboard-ui/src/composables/useConfirm.tsconst confirmed = await confirm('message') returns boolean
    • apps/dashboard-ui/src/components/ui/NgCard.vue — Use for sections
    • apps/dashboard-ui/src/components/ui/NgInput.vue — Use for form fields
    • apps/dashboard-ui/src/components/ui/NgToggle.vue — Use for toggle switches
    • @iconify/vue — Use <Icon icon="heroicons:cog-6-tooth" /> instead of ⚙️ emoji

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert() in DeviceDetail
      Tool: Bash
      Steps:
        1. Run: grep "alert(" apps/dashboard-ui/src/views/DeviceDetail.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-10-no-alert.txt
    
    Scenario: No confirm() in DeviceDetail
      Tool: Bash
      Steps:
        1. Run: grep "confirm(" apps/dashboard-ui/src/views/DeviceDetail.vue
      Expected Result: 0 matches (excluding useConfirm import)
      Evidence: .sisyphus/evidence/task-10-no-confirm.txt
    
    Scenario: No emoji in DeviceDetail
      Tool: Bash
      Steps:
        1. Run: grep -P "[\x{1F300}-\x{1F9FF}]|⚙️" apps/dashboard-ui/src/views/DeviceDetail.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-10-no-emoji.txt
    
    Scenario: No hardcoded colors
      Tool: Bash
      Steps:
        1. Run: grep -cE "text-cyan-|text-green-|text-red-|bg-gray-|bg-black/|border-white/|text-amber-" apps/dashboard-ui/src/views/DeviceDetail.vue
      Expected Result: 0 or minimal (allow some if unavoidable)
      Evidence: .sisyphus/evidence/task-10-hardcoded-count.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-10-build.txt
    

    Commit: YES (Commit D — batch with Tasks 11-17)

    • Message: refactor(dashboard): replace alert/confirm + restyle all views
    • Files: src/views/DeviceDetail.vue
  • 11. Restyle Devices.vue

    What to do:

    • Replace confirm() (line 111) with useConfirm():
      const confirmed = await confirm(`Delete device "${name}"? This will destroy its tunnel and all firewall rules.`)
      if (!confirmed) return
      
    • Replace alert() (line 116) with toast.error(err.response?.data?.error || 'Failed to delete device')
    • Replace hardcoded colors in table rows with design tokens
    • Replace raw firewall modal with NgModal:
      • Current: manual <div class="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center...">
      • Replace with: <NgModal :open="showFirewallModal" @close="showFirewallModal = false" title="Firewall Rules" size="lg">
    • Use NgButton for "+ Add Peer" button
    • Use NgBadge for Online/Offline status

    Must NOT do:

    • Do NOT change the API calls or data flow
    • Do NOT add sorting/filtering to the device table

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-10, 12-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/views/Devices.vue — Full file (138 lines). 1 confirm, 1 alert, raw table, raw modal.
    • apps/dashboard-ui/src/components/ui/NgModal.vue — Use for firewall modal wrapper
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for action buttons
    • apps/dashboard-ui/src/components/ui/NgBadge.vue — Use for status badges
    • apps/dashboard-ui/src/composables/useToast.ts — For error toasts
    • apps/dashboard-ui/src/composables/useConfirm.ts — For delete confirmation

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert()/confirm() in Devices
      Tool: Bash
      Steps:
        1. Run: grep -E "alert\(|confirm\(" apps/dashboard-ui/src/views/Devices.vue
      Expected Result: 0 matches (excluding useConfirm import)
      Evidence: .sisyphus/evidence/task-11-no-dialogs.txt
    
    Scenario: Firewall modal uses NgModal
      Tool: Bash
      Steps:
        1. Run: grep "NgModal" apps/dashboard-ui/src/views/Devices.vue
      Expected Result: 1+ match
      Evidence: .sisyphus/evidence/task-11-ngmodal.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-11-build.txt
    

    Commit: YES (Commit D)

    • Files: src/views/Devices.vue
  • 12. Restyle Dashboard.vue

    What to do:

    • Replace stat cards with NgCard (variant="stat"):
      <NgCard variant="stat">
        <p class="text-text-muted text-sm font-semibold uppercase tracking-wider mb-2">Total Devices</p>
        <p class="text-4xl font-bold">{{ store.devices.length }}</p>
      </NgCard>
      
    • Replace hardcoded text-green-400/text-red-400 with text-success/text-danger
    • Replace hardcoded bg-gray-900/50 with bg-surface or use NgCard
    • Replace hardcoded text-cyan-400 with text-accent
    • Replace hardcoded border-white/10 with border-border-subtle
    • Keep the device grid but use design tokens for card backgrounds

    Must NOT do:

    • Do NOT change the API calls or polling logic
    • Do NOT add new dashboard features

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-11, 13-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/views/Dashboard.vue — Full file (99 lines). Hardcoded stat cards, no NgCard usage.
    • apps/dashboard-ui/src/components/ui/NgCard.vue — Use variant="stat" for stat cards
    • apps/dashboard-ui/src/assets/main.css — @theme tokens (from Task 1)

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: Dashboard uses NgCard
      Tool: Bash
      Steps:
        1. Run: grep "NgCard" apps/dashboard-ui/src/views/Dashboard.vue
      Expected Result: 1+ match (stat cards)
      Evidence: .sisyphus/evidence/task-12-ngcard.txt
    
    Scenario: No hardcoded stat card colors
      Tool: Bash
      Steps:
        1. Run: grep -c "bg-gradient-to-br from-gray-900" apps/dashboard-ui/src/views/Dashboard.vue
      Expected Result: 0 matches (all replaced with NgCard)
      Evidence: .sisyphus/evidence/task-12-no-hardcoded.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-12-build.txt
    

    Commit: YES (Commit D)

    • Files: src/views/Dashboard.vue
  • 13. Restyle Servers.vue modals/forms

    What to do:

    • Replace confirm() (line 576) with useConfirm()
    • Replace alert('Delete failed') (line 582) with toast.error('Delete failed')
    • Replace hardcoded colors in add/edit modals with design tokens
    • Use NgInput for form fields in Register Node and Edit Node modals
    • Use NgButton for Register/Save/Cancel buttons
    • Use NgSelect for Table dropdown (auto/off)
    • Replace hardcoded text-cyan-400 with text-accent, bg-gray-900/90 with bg-surface
    • Servers list already uses NgCard — no change needed there

    Must NOT do:

    • Do NOT change the IP parsing logic or form data structure
    • Do NOT change the API calls

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-12, 14-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/views/Servers.vue — Full file (599 lines). 1 confirm, 1 alert, modals with hardcoded colors.
    • apps/dashboard-ui/src/components/ui/NgInput.vue — Use for form fields
    • apps/dashboard-ui/src/components/ui/NgSelect.vue — Use for dropdowns
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for action buttons
    • apps/dashboard-ui/src/composables/useConfirm.ts — For delete confirmation
    • apps/dashboard-ui/src/composables/useToast.ts — For error toasts

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert()/confirm() in Servers
      Tool: Bash
      Steps:
        1. Run: grep -E "alert\(|confirm\(" apps/dashboard-ui/src/views/Servers.vue
      Expected Result: 0 matches (excluding useConfirm import)
      Evidence: .sisyphus/evidence/task-13-no-dialogs.txt
    
    Scenario: Modals use NgInput/NgButton
      Tool: Bash
      Steps:
        1. Run: grep -c "NgInput\|NgButton\|NgSelect" apps/dashboard-ui/src/views/Servers.vue
      Expected Result: 3+ matches
      Evidence: .sisyphus/evidence/task-13-ng-components.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-13-build.txt
    

    Commit: YES (Commit D)

    • Files: src/views/Servers.vue
  • 14. Restyle Users.vue

    What to do:

    • Replace confirm() (line 110) with useConfirm()
    • Replace alert('Delete failed') (line 117) with toast.error('Delete failed')
    • Replace hardcoded colors with design tokens
    • Use NgInput for username/password fields
    • Use NgButton for Add User button
    • Use NgBadge for username display
    • Keep raw table but use design tokens for styling

    Must NOT do:

    • Do NOT change the API calls or user management logic

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-13, 15-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/views/Users.vue — Full file (124 lines). 1 confirm, 1 alert, hardcoded colors.
    • apps/dashboard-ui/src/components/ui/NgInput.vue — Use for form fields
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for action buttons
    • apps/dashboard-ui/src/composables/useConfirm.ts — For delete confirmation
    • apps/dashboard-ui/src/composables/useToast.ts — For error toasts

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert()/confirm() in Users
      Tool: Bash
      Steps:
        1. Run: grep -E "alert\(|confirm\(" apps/dashboard-ui/src/views/Users.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-14-no-dialogs.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-14-build.txt
    

    Commit: YES (Commit D)

    • Files: src/views/Users.vue
  • 15. Restyle AddPeerModal.vue + PeerConfigModal.vue

    What to do:

    • AddPeerModal.vue:
      • Replace 3x alert() with toast.success()/toast.error()
      • Replace hardcoded colors with design tokens
      • Use NgModal for the modal wrapper (currently manual <div class="fixed inset-0...">)
      • Use NgInput for peer name field
      • Use NgSelect for target node dropdown
      • Use NgToggle for "Allow Internet" and "Disable PSK" toggles
      • Use NgButton for Create/Download/Copy/Cancel buttons
    • PeerConfigModal.vue:
      • Replace 5x alert() with toast.success()/toast.error()
      • Replace hardcoded colors with design tokens
      • Use NgModal for the modal wrapper
      • Use NgButton for Save/Cancel/Copy/Download buttons
      • Keep QR code and config textarea (textarea is specific, no Ng equivalent)

    Must NOT do:

    • Do NOT change the API calls or provisioning logic
    • Do NOT change the QR code generation

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-14, 16-17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/components/AddPeerModal.vue — Full file (171 lines). 3 alert(), hardcoded colors, manual modal.
    • apps/dashboard-ui/src/components/PeerConfigModal.vue — Full file (186 lines). 5 alert(), hardcoded colors, manual modal.
    • apps/dashboard-ui/src/components/ui/NgModal.vue — Use for modal wrapper
    • apps/dashboard-ui/src/components/ui/NgInput.vue — Use for form fields
    • apps/dashboard-ui/src/components/ui/NgSelect.vue — Use for dropdowns
    • apps/dashboard-ui/src/components/ui/NgToggle.vue — Use for toggles
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for action buttons
    • apps/dashboard-ui/src/composables/useToast.ts — For success/error toasts

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert() in AddPeerModal
      Tool: Bash
      Steps:
        1. Run: grep "alert(" apps/dashboard-ui/src/components/AddPeerModal.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-15-addpeer-no-alert.txt
    
    Scenario: No alert() in PeerConfigModal
      Tool: Bash
      Steps:
        1. Run: grep "alert(" apps/dashboard-ui/src/components/PeerConfigModal.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-15-peerconfig-no-alert.txt
    
    Scenario: Both use NgModal
      Tool: Bash
      Steps:
        1. Run: grep -c "NgModal" apps/dashboard-ui/src/components/AddPeerModal.vue apps/dashboard-ui/src/components/PeerConfigModal.vue
      Expected Result: 1 match per file
      Evidence: .sisyphus/evidence/task-15-ngmodal.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-15-build.txt
    

    Commit: YES (Commit D)

    • Files: src/components/AddPeerModal.vue, src/components/PeerConfigModal.vue
  • 16. Restyle ShareConfig.vue

    What to do:

    • Replace 2x alert() with toast.success()/toast.error()
    • Replace hardcoded colors with design tokens (text-cyan-400text-accent, bg-gradient-to-br from-gray-900 to-gray-800bg-gradient-to-br from-bg-surface to-bg-elevated)
    • Use NgButton for Download/Copy buttons
    • Use NgCard for the config container

    Must NOT do:

    • Do NOT change the fetch logic or config display
    • Do NOT add new features

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-15, 17)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/views/ShareConfig.vue — Full file (80 lines). 2 alert(), hardcoded colors.
    • apps/dashboard-ui/src/components/ui/NgButton.vue — Use for action buttons
    • apps/dashboard-ui/src/components/ui/NgCard.vue — Use for config container
    • apps/dashboard-ui/src/composables/useToast.ts — For success/error toasts

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No alert() in ShareConfig
      Tool: Bash
      Steps:
        1. Run: grep "alert(" apps/dashboard-ui/src/views/ShareConfig.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-16-no-alert.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-16-build.txt
    

    Commit: YES (Commit D)

    • Files: src/views/ShareConfig.vue
  • 17. Restyle TrafficChart.vue SVG colors

    What to do:

    • Replace hardcoded SVG hex colors with CSS custom properties:
      • stroke="#00ffff"stroke="var(--color-accent)" or use currentColor where appropriate
      • fill="#00ffff"fill="var(--color-accent)"
      • fill="#3b82f6"fill="var(--color-info)" (define --color-info in @theme if needed, or use existing blue token)
    • Replace hardcoded gradient stop colors with CSS variables
    • Replace inline SVG spinner with NgSkeleton or keep as-is (spinner is functional)
    • Replace hardcoded text-gray-400, text-gray-500 with text-text-secondary, text-text-muted

    Must NOT do:

    • Do NOT change the chart rendering logic
    • Do NOT change the data transformation or formatting functions

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-16)
    • Blocks: Final verification
    • Blocked By: Task 1

    References:

    • apps/dashboard-ui/src/components/TrafficChart.vue — Full file (299 lines). Hardcoded SVG colors (#00ffff, #3b82f6), inline spinner.
    • apps/dashboard-ui/src/assets/main.css — @theme tokens (from Task 1)

    Acceptance Criteria:

    QA Scenarios (MANDATORY):

    Scenario: No hardcoded hex colors in TrafficChart
      Tool: Bash
      Steps:
        1. Run: grep -E "#[0-9a-fA-F]{3,8}" apps/dashboard-ui/src/components/TrafficChart.vue
      Expected Result: 0 matches
      Evidence: .sisyphus/evidence/task-17-no-hex.txt
    
    Scenario: Build succeeds
      Tool: Bash
      Steps:
        1. Run: cd apps/dashboard-ui && npm run build
      Expected Result: Exit code 0
      Evidence: .sisyphus/evidence/task-17-build.txt
    

    Commit: YES (Commit D)

    • Files: src/components/TrafficChart.vue
    • Pre-commit: npm run build

Final Verification Wave

  • F1. Plan Compliance Auditoracle
  • F2. Code Quality Reviewunspecified-high
  • F3. Real Manual QAunspecified-high
  • F4. Scope Fidelity Checkdeep For each task: read "What to do", read actual diff. Verify 1:1 — everything in spec was built, nothing beyond spec was built. Check "Must NOT do" compliance. Detect cross-task contamination. Flag unaccounted changes. Specifically verify: Login.vue untouched, style.css untouched, AddForwardRule untouched, no nft flush table. Output: Tasks [N/N compliant] | Contamination [CLEAN/N issues] | VERDICT

Commit Strategy

  • Commit A (Wave 0): feat(dashboard): add @theme design tokens to main.css — main.css
  • Commit B (Wave 1): fix(firewall): rewrite nftables rule system + fix fwd_estab duplicates — all backend firewall files
  • Commit C (Wave 2 batch 1): refactor(dashboard): delete LinkedDevices + redesign FirewallEditor — LinkedDevices.vue (delete), FirewallEditor.vue, DeviceDetail.vue
  • Commit D (Wave 2 batch 2): refactor(dashboard): replace alert/confirm + restyle all views — remaining 8 .vue files

Success Criteria

Verification Commands

# Backend
cd apps/server-core && go build ./...          # Expected: no errors
cd apps/server-core && go test ./... -tags dev  # Expected: all PASS

# Frontend
cd apps/dashboard-ui && npm run build           # Expected: no errors

# Legacy pattern audit
grep -r "alert(" --include="*.vue" apps/dashboard-ui/src/     # Expected: 0 matches
grep -r "LinkedDevices" --include="*.vue" --include="*.ts" apps/dashboard-ui/src/  # Expected: 0 matches

Final Checklist

  • All "Must Have" present
  • All "Must NOT Have" absent
  • All go tests pass
  • Frontend builds without errors
  • Zero alert()/confirm() in .vue files
  • Zero LinkedDevices references