18 KiB
Nodes Form Fields Fix — Listen Binding & UI Inconsistencies
TL;DR
Quick Summary: Fix 6 inconsistencies in the Nodes Register/Edit form (Servers.vue): missing Listen Address in edit modal, misleading "Listen Binding" column header, display column that doesn't show port, and inconsistent input types for script hooks. Frontend-only changes, no backend modification needed.
Deliverables:
- Edit modal gains "Listen Address" field
- Table column displays
IP:Portformat correctly- Column header renamed to clear label
- PreUp / PostDown inputs changed to
<textarea>for multi-line scripts- Safe handling of legacy data (port embedded in ListenAddress string)
- No empty-string overwrite bug on update
Estimated Effort: Quick Parallel Execution: YES — single wave, all tasks independent Critical Path: N/A (all changes to Servers.vue)
Context
Original Request
Fix masalah labeling di Nodes Register/Edit form: "Listen Binding" vs "Listen Address" tidak konsisten, Edit modal hilang field Listen Address, dan tipe input script hooks tidak seragam.
Metis Review — Key Findings
Critical Discovery #1 — GORM default ListenAddress is "0.0.0.0:51820" (IP:Port), but frontend always sends IP-only. Legacy records may have port embedded in the string.
Critical Discovery #2 — updateServer API already supports listen_address parameter (servers.ts:45). Only Servers.vue needs the field wired up.
Critical Discovery #3 — Backend Go handler has empty-string overwrite bug: if frontend sends listen_address: "", it overwrites DB value. Must omit field from payload if unchanged.
Critical Discovery #4 — No validation on listen_address format. Backend accepts any string. Out of scope for this fix but worth noting.
Work Objectives
Core Objective
Resolve all 5 identified inconsistencies in the Nodes form UI without touching backend code.
Concrete Deliverables
apps/dashboard-ui/src/views/Servers.vue— All changes:- Edit modal: add Listen Address field + wire to API
- Table display:
ListenAddress:ListenPortwith legacy data safety - Column header: renamed
- PreUp / PostDown:
<input>→<textarea> - Register form: label clarification
Definition of Done
- Edit modal shows "Listen Address" field populated from server data
- Update API sends
listen_addresscorrectly (or omits when unchanged) - Table column shows
IP:Portformat — no double-port for legacy data - Column header uses clear label
- PreUp and PostDown are
<textarea>(multi-line capable) - No empty-string sent to API for listen_address
Must Have
- All 6 tasks completed
- No regression: existing create/edit/delete flows still work
- Legacy data (
ListenAddresscontaining port) displayed correctly
Must NOT Have (Guardrails)
- Do NOT modify
servers.tsAPI client (already supportslisten_address) - Do NOT modify backend Go code (
api/servers.go,internal/models/) - Do NOT touch other views (DeviceDetail.vue, Dashboard.vue, etc.)
- Do NOT add IP validation or IPv6 handling (out of scope)
- Do NOT send
listen_address: ""to update API
Verification Strategy
ZERO HUMAN INTERVENTION — ALL verification is agent-executed.
Test Decision
- Infrastructure exists: YES (Vue 3 + TypeScript)
- Automated tests: None (no frontend test suite exists)
- Primary verification: Agent-executed QA via Playwright (browser automation)
QA Policy
Every task MUST include agent-executed QA scenarios using Playwright:
- Navigate to Nodes page
- Open Register / Edit modal
- Fill fields, submit, verify results
- Capture screenshots as evidence
Execution Strategy
Parallel Execution Waves
Wave 1 (ALL tasks in parallel — single file edits):
├── Task 1: Add listenAddress to editForm reactive state + openEdit()
├── Task 2: Wire listen_address to handleEditSave() payload
├── Task 3: Fix table display column — ListenAddress:ListenPort with legacy safety
├── Task 4: Rename column header from "Listen Binding"
├── Task 5: Fix PreUp and PostDown — <input> → <textarea>
└── Task 6: Clarify Register form "Listen Address" label
Wave FINAL (verification):
├── Task F1: Verify all changes via Playwright QA scenarios
TODOs
-
1. Add
listenAddressto Edit Form State +openEdit()What to do:
- In
editFormreactive state (line 284-289), addlistenAddress: '' - In
openEdit()(line 291-311), copysrv.ListenAddresstoeditForm.value.listenAddress - Critical: If
srv.ListenAddresscontains a port (e.g.,"0.0.0.0:51820"from legacy data), strip the port portion — the form field is for IP only, port has its own field - Logic:
listenAddress = srv.ListenAddress.includes(':') ? srv.ListenAddress.split(':')[0] : srv.ListenAddress - Add the input field in the edit modal template, after "Public Endpoint" (line 168):
<div> <label class="block text-xs text-gray-500 mb-1">Listen Address</label> <input v-model="editForm.listenAddress" placeholder="0.0.0.0" class="w-full bg-black/50 border border-white/10 rounded p-2 text-white focus:border-cyan-500 focus:outline-none" /> </div>
Must NOT do:
- Do NOT modify
servers.tsAPI client - Do NOT send empty string if field is cleared
Recommended Agent Profile:
- Category:
quick- Reason: Simple reactive state + template addition
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 2-6)
- Blocks: Task 2 (needs the state variable)
- Blocked By: None
References:
apps/dashboard-ui/src/views/Servers.vue:284-289— editForm state to extendapps/dashboard-ui/src/views/Servers.vue:291-311— openEdit() to updateapps/dashboard-ui/src/views/Servers.vue:168-172— After "Public Endpoint" field (insert point)
Acceptance Criteria:
- editForm has
listenAddressfield - openEdit() populates listenAddress from srv.ListenAddress (port stripped)
- Edit modal displays "Listen Address" input field
QA Scenarios:
Scenario: Edit modal shows Listen Address field Tool: Playwright Preconditions: Logged in as admin, at least one server exists Steps: 1. Navigate to Nodes page (/servers) 2. Click "Edit" on any server row 3. Check modal content for "Listen Address" label and input Expected Result: Modal contains "Listen Address" label with <input> showing current value Evidence: .sisyphus/evidence/task-1-edit-listen-address.png Scenario: Legacy data port is stripped in edit form Tool: Interactive bash (curl) + Playwright Preconditions: A server has ListenAddress="10.0.0.1:51820" (legacy data) Steps: 1. Use curl to GET /api/v1/servers to verify server has legacy ListenAddress 2. Open edit modal in Playwright 3. Check listenAddress input value Expected Result: Input shows "10.0.0.1", not "10.0.0.1:51820" Evidence: .sisyphus/evidence/task-1-legacy-port-stripped.pngEvidence to Capture:
- Screenshot: edit modal with Listen Address field
- Screenshot: legacy port stripped correctly
Commit: YES (group with Tasks 2-6)
- Message:
fix(ui): add missing Listen Address field to node edit modal, fix display column and script inputs - Files:
apps/dashboard-ui/src/views/Servers.vue
- In
-
2. Wire
listen_addresstohandleEditSave()PayloadWhat to do:
- In
handleEditSave()(line 318-343), addlisten_addressto the update payload - Implementation:
listen_address: editForm.value.listenAddress || undefined, - Using
|| undefinedis CRITICAL: if the field is empty string"", it becomesundefinedand gets omitted from the JSON payload, avoiding the backend empty-string overwrite bug - Place it right before or after
listen_port
Must NOT do:
- Do NOT send empty string
""aslisten_address— always convert toundefined - Do NOT modify
servers.ts
Recommended Agent Profile:
- Category:
quick- Reason: Single line addition in existing function
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES (but must be after Task 1 for state variable)
- Parallel Group: Wave 1
- Blocked By: Task 1
References:
Servers.vue:318-343— handleEditSave functionservers.ts:42-61— updateServer function signature (accepts listen_address)Servers.vue:328— existinglisten_port: editForm.value.listenPortline
Acceptance Criteria:
- handleEditSave sends
listen_addressin payload - Empty listen_address field results in
undefined(omitted from payload) - Backend does NOT receive
listen_address: ""
QA Scenarios:
Scenario: Update listen address via edit modal Tool: Playwright Preconditions: Logged in as admin, server exists Steps: 1. Open edit modal, change Listen Address to "0.0.0.1" 2. Click Save 3. Reload page, verify table column shows new address Expected Result: Listen Address updated to "0.0.0.1" Evidence: .sisyphus/evidence/task-2-update-listen-address.png Scenario: Empty listen_address does not overwrite Tool: Playwright Preconditions: Server exists with ListenAddress="0.0.0.0" Steps: 1. Open edit modal, clear Listen Address field 2. Click Save 3. Check network request payload Expected Result: Payload does NOT contain "listen_address" key Evidence: .sisyphus/evidence/task-2-omit-empty.txtEvidence to Capture:
- Screenshot: after updating listen address
- Network request log: payload verification
Commit: YES (group with Task 1)
- In
-
3. Fix Table Display Column —
ListenAddress:ListenPortwith Legacy SafetyWhat to do:
- Change the table cell (line 147) from:
To:
<td class="py-4 font-mono text-gray-400 text-sm">{{ srv.ListenAddress }}</td><td class="py-4 font-mono text-gray-400 text-sm">{{ displayListenBinding(srv) }}</td> - Add a helper function in
<script setup>:const displayListenBinding = (srv: WgServer): string => { // Handle legacy data: if ListenAddress already contains port, use it as-is if (srv.ListenAddress.includes(':')) { return srv.ListenAddress } return `${srv.ListenAddress}:${srv.ListenPort}` }
Must NOT do:
- Do NOT produce double-port (e.g.,
0.0.0.0:51820:51820) - Do NOT crash if
ListenPortis 0
Recommended Agent Profile:
- Category:
quick- Reason: Template change + 5-line helper function
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1
- Blocked By: None
References:
Servers.vue:147— Current display to changeServers.vue:252—import { type WgServer }already exists
Acceptance Criteria:
- Table column shows
0.0.0.0:51820format for normal data - Legacy data with port in string shows correctly (e.g.,
192.168.1.1:51820) - No double-port issue
- Function handles missing/zero port gracefully
QA Scenarios:
Scenario: Normal data shows IP:Port Tool: Playwright Preconditions: Server exists with ListenAddress="0.0.0.0" and ListenPort=51820 Steps: 1. Navigate to Nodes page 2. Check the "Listen Binding" column Expected Result: Cell shows "0.0.0.0:51820" Evidence: .sisyphus/evidence/task-3-display-normal.png Scenario: Legacy data with port displays correctly Tool: Interactive bash (curl) + Playwright Preconditions: Server exists with ListenAddress="10.0.0.1:51820" (legacy) Steps: 1. Navigate to Nodes page 2. Check the column for that server Expected Result: Cell shows "10.0.0.1:51820" (no double port) Evidence: .sisyphus/evidence/task-3-display-legacy.pngEvidence to Capture:
- Screenshot: normal IP:Port display
- Screenshot: legacy data display
Commit: YES (group with Task 1)
- Change the table cell (line 147) from:
-
4. Rename Column Header from "Listen Binding"
What to do:
- Change line 134:
→ choose one of:
<th class="pb-3">Listen Binding</th>"Bind Address"(clear, standard)"Listen Address"(matches form label)"Listen Port"(if showing port only)
- Since we're now displaying
IP:Portin the cell,"Bind Address"is the most descriptive
Must NOT do:
- Do NOT use "Listen Binding" — it's ambiguous
Recommended Agent Profile:
- Category:
quick- Reason: One-line text change
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1
- Blocked By: None
References:
Servers.vue:134— Current header text
Acceptance Criteria:
- Column header changed to clear label
- No broken layout
QA Scenarios:
Scenario: Column header displays new label Tool: Playwright Steps: Navigate to Nodes page, capture screenshot of table header Expected Result: Header shows new label (e.g., "Bind Address") Evidence: .sisyphus/evidence/task-4-column-header.pngEvidence to Capture:
- Screenshot: table header
Commit: YES (group with Task 1)
- Change line 134:
-
5. Fix PreUp and PostDown —
<input>→<textarea>What to do:
- In Register form (line 75-76): Change PreUp from
<input>to<textarea rows="2"> - In Register form (line 87-88): Change PostDown from
<input>to<textarea rows="2"> - In Edit form (line 201-203): Change PreUp from
<input>to<textarea rows="2"> - In Edit form (line 213-215): Change PostDown from
<input>to<textarea rows="2"> - Keep the same TailwindCSS styling classes
- Match the existing
<textarea>pattern from PostUp/PreDown (lines 79-84, 206-211)
Must NOT do:
- Do NOT change PostUp or PreDown (already are
<textarea>) - Do NOT change any other field types
Recommended Agent Profile:
- Category:
quick- Reason: 4 HTML element tag changes, identical pattern
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1
- Blocked By: None
References:
Servers.vue:75-76— Register form PreUp (input → textarea)Servers.vue:79-84— Register form PostUp (textarea — existing pattern)Servers.vue:87-88— Register form PostDown (input → textarea)Servers.vue:201-203— Edit form PreUpServers.vue:213-215— Edit form PostDown
Acceptance Criteria:
- PreUp and PostDown are
<textarea>in both Register and Edit forms - Multi-line text can be entered
- No visual regression (same styling as PostUp/PreDown)
QA Scenarios:
Scenario: All 4 script hook fields are textareas Tool: Playwright Steps: 1. Navigate to Nodes page 2. Click "Register Node" 3. Check PreUp, PostUp, PreDown, PostDown are all textareas 4. Cancel, click Edit on a server 5. Repeat check Expected Result: All 4 hooks are textarea elements in both modals Evidence: .sisyphus/evidence/task-5-script-textareas.pngEvidence to Capture:
- Screenshot: register modal with all 4 textareas
- Screenshot: edit modal with all 4 textareas
Commit: YES (group with Task 1)
- In Register form (line 75-76): Change PreUp from
-
6. Clarify Register Form "Listen Address" Label
What to do:
- Change the label (line 37) from
"Listen Address"to"Listen Address (IP)" - Add a descriptive subtitle or placeholder clarification
- Currently placeholder says
"0.0.0.0"— keep this, it already hints IP-only
Must NOT do:
- Do NOT remove the separate "Listen Port" field
- Do NOT change the placeholder text (already clear)
Recommended Agent Profile:
- Category:
quick- Reason: Single label text change
- Skills:
[]
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1
- Blocked By: None
References:
Servers.vue:37— Current label "Listen Address"
Acceptance Criteria:
- Label updated to clarify it's IP-only
Evidence to Capture:
- Screenshot: register form showing updated label
Commit: YES (group with Task 1)
- Change the label (line 37) from
Final Verification Wave
-
F1. Build Verification —
npm run buildPASSEDWhat to do: Run Playwright against the dashboard to verify ALL changes:
- Open Register New Node modal — verify all script hooks are
<textarea>, verify "Listen Address (IP)" label - Fill dummy data, submit, verify node appears in table with correct
IP:Portin Bind Address column - Click Edit on the new node — verify Listen Address field is populated correctly (IP only)
- Change Listen Address, save — verify table updates
- Clear Listen Address, save — verify it doesn't break
- Open edit on legacy node (if exists) — verify port is stripped from Listen Address in form
- Verify all 4 script hooks are textareas in edit modal too
- Take screenshots of each verification step
Expected Result: All 8 steps pass, screenshots captured to
.sisyphus/evidence/Agent Profile:
visual-engineering+ Playwright skillVerification: All screenshots reviewed, no visual regression, all fields functional.
- Open Register New Node modal — verify all script hooks are
Commit Strategy
| Commit # | Tasks | Message |
|---|---|---|
| 1 | 1-6 | fix(ui): add missing Listen Address field to node edit modal, fix display column and script inputs |
Files: apps/dashboard-ui/src/views/Servers.vue
Success Criteria
Verification Commands
cd apps/dashboard-ui
npx vue-tsc --noEmit # Expected: PASS (no type errors)
npm run build # Expected: PASS (build succeeds)
Final Checklist
- All 6 tasks complete
- Edit modal has "Listen Address" field
- Table column displays
IP:Portcorrectly - Column header "Bind Address"
- All 4 script hooks are
<textarea> - Register form label clarified
- No empty-string sent to API
npm run buildpassesvue-tsc --noEmitpasses