# Plan: WGDashboard Config Parity (Phase 5.5) ## 1. Goal Achieve feature parity with WGDashboard for WireGuard configuration editing and peer settings, adapting WGDashboard's local-file management approach to NexusGuard's multi-node database-driven SD-WAN architecture. --- ## 2. Current Status (May 22, 2026) ### ✅ COMPLETED #### Phase 1: Database Model Updates (Backend) - [x] **Update `WgServer` Model**: Added MTU (default 1420), DNS (default "1.1.1.1"). - [x] **Update `Device` Model**: Added EndpointAllowedIPs, DNS, MTU, PersistentKeepalive (default 25), Notes, IsSuspended, RxBytes, TxBytes. - [x] **Database Migration**: Auto-migrate via GORM AutoMigrate. #### Phase 2: API Updates (Backend) - [x] **Server API (`servers.go`)**: `PUT /api/v1/servers/:id` — pointer fields for partial updates, firewall sync on port change. - [x] **Device API (`devices.go`)**: `PUT /api/v1/devices/:id` — pointer fields for EndpointAllowedIPs, DNS, MTU, PersistentKeepalive, Notes. - [x] **Config Generation (`peers.go`)**: `getDeviceConfig` — DNS override (device > server > "1.1.1.1"), MTU override, PersistentKeepalive, EndpointAllowedIPs. - [x] **Peer Suspension API**: `POST /api/v1/devices/:id/suspend` and `/unsuspend` with `IsSuspended` toggle + firewall rule add/remove. #### Phase 3: Firewall Enforcement Sync (nftables) — MOSTLY DONE - [x] **Firewall Manager Interface**: Extended with `AddForwardRule`, `RemoveForwardRule`, `AddInputRule`, `RemoveInputRule`. - [x] **Linux Implementation**: `nftables_linux.go` via `nft` CLI (AddForwardRule, RemoveForwardRule, AddInputRule, RemoveInputRule). - [x] **Stub Implementation**: `nftables_stub.go` for non-Linux builds. - [x] **Server Create**: `AddInputRule(server.Name, server.ListenPort)` in `servers.go`. - [x] **Server Update**: Firewall re-sync when ListenPort changes in `servers.go`. - [x] **Server Delete**: `RemoveInputRule(server.Name)` in `servers.go`. - [x] **Peer Suspend**: `RemoveForwardRule` / `AddForwardRule` in `devices.go` toggleSuspension. - [x] **Device Delete (partial)**: `RemoveRangeRule` called but **missing `RemoveForwardRule`**. ### ❌ REMAINING (to be completed now) #### Phase 3: Backend Firewall Fixes - [x] **Fix duplicate `"net"` import** in `devices.go` (lines 4-5, two imports of same package). - [x] **Add `AddForwardRule` in `peers.go` CreatePeer** — after device creation, add FORWARD rule for device AllowedIPs. - [x] **Add firewall sync in `devices.go` Update** — when EndpointAllowedIPs or AllowInternet changes, re-sync FORWARD rule. - [x] **Add `RemoveForwardRule` in `devices.go` Delete** — clean up FORWARD rule when device is deleted. #### Phase 4: Dashboard UI (Frontend) - [x] **Node Edit Modal** in `Servers.vue` — Edit Node button + modal to configure MTU, DNS, ListenPort via `PUT /api/v1/servers/:id`. - [x] **Advanced Peer Settings** in `DeviceDetail.vue` — Accordion section with fields for AllowedIPs, DNS, MTU, Keepalive, Notes, Suspend toggle, using new API fields. - [x] **Update API client** (`servers.ts`, `devices.ts`) — Add `updateServer()`, `suspendDevice()`, `unsuspendDevice()` and extend `updateDevice()` with new fields. --- ## 3. Implementation Tasks ### Task A: Backend Fixes (4 edits across 2 files) #### A.1 devices.go — Duplicate import - Remove second `"net"` import on line 5. - File: `apps/server-core/api/devices.go` - Verify: `go build ./...` passes. #### A.2 peers.go — Add ForwardRule on CreatePeer - After line ~131 (configText generation), add: ```go // Sync firewall FORWARD rule for this peer h.fw.AddForwardRule(device.Name, ip, allowedIPs) ``` - Also handle case where EndpointAllowedIPs is empty — derive from AllowInternet. - File: `apps/server-core/api/peers.go` #### A.3 devices.go — Firewall sync on Update - In `Update()` handler, after device is updated, re-sync FORWARD rule if: - `EndpointAllowedIPs` changed (req.EndpointAllowedIPs != nil) - `AllowInternet` changed (req.AllowInternet != nil) - Calculate destCIDR from device's EndpointAllowedIPs > AllowInternet > InternalIP - Call `h.fw.RemoveForwardRule(device.Name)` then `h.fw.AddForwardRule(...)` with new CIDR. #### A.4 devices.go — ForwardRule cleanup on Delete - In `Delete()` handler, add `h.fw.RemoveForwardRule(device.Name)` before or after the existing `h.db.Delete(&device)`. - File: `apps/server-core/api/devices.go` ### Task B: Frontend API Client Updates #### B.1 servers.ts — Add updateServer() ```typescript export const updateServer = async (id: string, data: { name?: string; public_endpoint?: string; listen_address?: string; listen_port?: number; mtu?: number; dns?: string; }) => { await api.put(`/servers/${id}`, data) } ``` #### B.2 devices.ts — Extend updateDevice() + add suspend/unsuspend ```typescript export const updateDevice = async (id: string, data: { name?: string; allow_internet?: boolean; endpoint_allowed_ips?: string; dns?: string; mtu?: number; persistent_keepalive?: number; notes?: string; }) => { await api.put(`/devices/${id}`, data) } export const suspendDevice = async (id: string) => { const { data } = await api.post(`/devices/${id}/suspend`) return data } export const unsuspendDevice = async (id: string) => { const { data } = await api.post(`/devices/${id}/unsuspend`) return data } ``` #### B.3 Device interface — Add new fields ```typescript export interface Device { // ... existing fields ... EndpointAllowedIPs?: string DNS?: string MTU?: number PersistentKeepalive?: number Notes?: string IsSuspended?: boolean } ``` ### Task C: Frontend UI Components #### C.1 Servers.vue — Edit Node Modal Add to the template: - An "Edit" button next to "Delete" in each server row - A modal dialog (glassmorphism style matching current design) with fields: - ListenPort (number input) - MTU (number input) - DNS (text input) - Form submits to `updateServer(id, payload)` API - Modal uses `v-model="showEditModal"` to open/close - Refresh list after update Implementation approach: - Add `editingServer` ref to track which server is being edited - Add modal template with glassmorphism card (bg-gray-900/90 rounded-2xl border border-white/10 shadow-lg backdrop-blur-xl) - Pre-populate form with current server values - On submit: call API, close modal, reload list #### C.2 DeviceDetail.vue — Advanced Peer Settings Add after the AllowInternet toggle section: - A collapsible "Advanced Settings" accordion - Inside: form fields for: - AllowedIPs (text input - comma-separated CIDRs) - DNS (text input) - MTU (number input) - PersistentKeepalive (number input) - Notes (textarea) - Suspend (toggle switch with API call) - "Save" button calls `updateDevice()` with all fields - Suspend toggle calls `suspendDevice()` / `unsuspendDevice()` separately - Show "Suspended" badge when device is suspended --- ## 4. Execution Strategy ``` Wave 1 (parallel — backend fixes + frontend API): ├── Task A: Backend fixes (devices.go, peers.go) ├── Task B: Frontend API client updates (servers.ts, devices.ts, peers.ts) Wave 2 (parallel — frontend UI): ├── Task C.1: Servers.vue Edit Node modal ├── Task C.2: DeviceDetail.vue Advanced Peer Settings accordion Wave 3 (final — verification): ├── Verify go build ./... passes ├── Verify npm build passes (or at least no TS errors) ├── Update plan file with final status ├── Commit all changes ``` --- ## 5. Verification Strategy ### Backend Verification ```bash cd apps/server-core go build ./... # Expected: PASS (no errors) go vet ./... # Expected: PASS (no warnings) ``` ### Frontend Verification ```bash cd apps/dashboard-ui npx vue-tsc --noEmit # Expected: PASS (no type errors) npm run build # Expected: PASS (build succeeds) ``` ### Manual QA (after deploy to wglab) - Add Node → Edit Node (change MTU, DNS, ListenPort) → Verify - Create Peer → Edit Advanced Settings (AllowedIPs, DNS, MTU, Keepalive, Notes) → Verify - Toggle Suspend → Verify peer status changes → Verify firewall rules update - Verify nftables rules: `nft list chain ip nexusguard forward`, `nft list chain ip nexusguard input`