281ac48d28
NexusGuard CI / server-core-test (push) Failing after 3m30s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 3s
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 3s
NexusGuard CI / dashboard-dist (push) Has been skipped
1361 lines
56 KiB
Markdown
1361 lines
56 KiB
Markdown
# 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
|
|
- [x] `cd apps/server-core && go build ./...` passes
|
|
- [x] `cd apps/server-core && go test ./... -tags dev -count=1` all pass
|
|
- [x] `cd apps/dashboard-ui && npm run build` passes with 0 errors
|
|
- [x] `grep -r "alert(" --include="*.vue" src/` returns 0 matches
|
|
- [x] `grep -r "LinkedDevices" --include="*.vue" --include="*.ts" src/` returns 0 matches
|
|
- [x] `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
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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)
|
|
|
|
- [x] 3. Add AddFirewallRule/RemoveFirewallRule to NetManager interface
|
|
|
|
**What to do**:
|
|
- Add new method to `manager.go` interface:
|
|
```go
|
|
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-200` — `AddForwardRule` 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-211` — `RemoveForwardRule` 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)
|
|
|
|
- [x] 4. Rewrite syncRuleToFirewall + fix rule delete in rules.go
|
|
|
|
**What to do**:
|
|
- Rewrite `syncRuleToFirewall()` in `rules.go` to use new `AddFirewallRule()`:
|
|
```go
|
|
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()`:
|
|
```go
|
|
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-112` — `FirewallRule` 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)
|
|
|
|
- [x] 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:
|
|
```go
|
|
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-200` — `AddForwardRule` 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)
|
|
|
|
- [x] 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`:
|
|
```go
|
|
// 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)
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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-400` → `text-accent`, `bg-gray-900/90` → `bg-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.ts` — `toast.success(msg)`, `toast.error(msg)`
|
|
- `apps/dashboard-ui/src/composables/useConfirm.ts` — `const 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`
|
|
|
|
- [x] 11. Restyle Devices.vue
|
|
|
|
**What to do**:
|
|
- Replace `confirm()` (line 111) with `useConfirm()`:
|
|
```ts
|
|
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`
|
|
|
|
- [x] 12. Restyle Dashboard.vue
|
|
|
|
**What to do**:
|
|
- Replace stat cards with NgCard (variant="stat"):
|
|
```vue
|
|
<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`
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 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`
|
|
|
|
- [x] 16. Restyle ShareConfig.vue
|
|
|
|
**What to do**:
|
|
- Replace 2x `alert()` with `toast.success()`/`toast.error()`
|
|
- Replace hardcoded colors with design tokens (`text-cyan-400` → `text-accent`, `bg-gradient-to-br from-gray-900 to-gray-800` → `bg-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`
|
|
|
|
- [x] 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
|
|
|
|
- [x] F1. **Plan Compliance Audit** — `oracle`
|
|
- [x] F2. **Code Quality Review** — `unspecified-high`
|
|
- [x] F3. **Real Manual QA** — `unspecified-high`
|
|
- [x] F4. **Scope Fidelity Check** — `deep`
|
|
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
|
|
```bash
|
|
# 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
|
|
- [x] All "Must Have" present
|
|
- [x] All "Must NOT Have" absent
|
|
- [x] All go tests pass
|
|
- [x] Frontend builds without errors
|
|
- [x] Zero alert()/confirm() in .vue files
|
|
- [x] Zero LinkedDevices references
|