Files
Nexus-Guard-Suite/.sisyphus/plans/archived/nodes-form-fields-fix.md
T
datadunia 075f915a66
NexusGuard CI / server-core-test (push) Failing after 47s
NexusGuard CI / device-agent-test (push) Failing after 35s
NexusGuard CI / dashboard-ui-build (push) Failing after 31s
chore: archive old plans, add new plan docs, update submodules
2026-05-28 02:50:43 +07:00

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:Port format 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 #2updateServer 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:ListenPort with 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_address correctly (or omits when unchanged)
  • Table column shows IP:Port format — 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 (ListenAddress containing port) displayed correctly

Must NOT Have (Guardrails)

  • Do NOT modify servers.ts API client (already supports listen_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 listenAddress to Edit Form State + openEdit()

    What to do:

    • In editForm reactive state (line 284-289), add listenAddress: ''
    • In openEdit() (line 291-311), copy srv.ListenAddress to editForm.value.listenAddress
    • Critical: If srv.ListenAddress contains 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.ts API 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 extend
    • apps/dashboard-ui/src/views/Servers.vue:291-311 — openEdit() to update
    • apps/dashboard-ui/src/views/Servers.vue:168-172 — After "Public Endpoint" field (insert point)

    Acceptance Criteria:

    • editForm has listenAddress field
    • 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.png
    

    Evidence 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

  • 2. Wire listen_address to handleEditSave() Payload

    What to do:

    • In handleEditSave() (line 318-343), add listen_address to the update payload
    • Implementation:
      listen_address: editForm.value.listenAddress || undefined,
      
    • Using || undefined is CRITICAL: if the field is empty string "", it becomes undefined and 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 "" as listen_address — always convert to undefined
    • 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 function
    • servers.ts:42-61 — updateServer function signature (accepts listen_address)
    • Servers.vue:328 — existing listen_port: editForm.value.listenPort line

    Acceptance Criteria:

    • handleEditSave sends listen_address in 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.txt
    

    Evidence to Capture:

    • Screenshot: after updating listen address
    • Network request log: payload verification

    Commit: YES (group with Task 1)


  • 3. Fix Table Display Column — ListenAddress:ListenPort with Legacy Safety

    What to do:

    • Change the table cell (line 147) from:
      <td class="py-4 font-mono text-gray-400 text-sm">{{ srv.ListenAddress }}</td>
      
      To:
      <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 ListenPort is 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 change
    • Servers.vue:252import { type WgServer } already exists

    Acceptance Criteria:

    • Table column shows 0.0.0.0:51820 format 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.png
    

    Evidence to Capture:

    • Screenshot: normal IP:Port display
    • Screenshot: legacy data display

    Commit: YES (group with Task 1)


  • 4. Rename Column Header from "Listen Binding"

    What to do:

    • Change line 134:
      <th class="pb-3">Listen Binding</th>
      
      → choose one of:
      • "Bind Address" (clear, standard)
      • "Listen Address" (matches form label)
      • "Listen Port" (if showing port only)
    • Since we're now displaying IP:Port in 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.png
    

    Evidence to Capture:

    • Screenshot: table header

    Commit: YES (group with Task 1)


  • 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 PreUp
    • Servers.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.png
    

    Evidence to Capture:

    • Screenshot: register modal with all 4 textareas
    • Screenshot: edit modal with all 4 textareas

    Commit: YES (group with Task 1)


  • 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)


Final Verification Wave

  • F1. Build Verification — npm run build PASSED

    What to do: Run Playwright against the dashboard to verify ALL changes:

    1. Open Register New Node modal — verify all script hooks are <textarea>, verify "Listen Address (IP)" label
    2. Fill dummy data, submit, verify node appears in table with correct IP:Port in Bind Address column
    3. Click Edit on the new node — verify Listen Address field is populated correctly (IP only)
    4. Change Listen Address, save — verify table updates
    5. Clear Listen Address, save — verify it doesn't break
    6. Open edit on legacy node (if exists) — verify port is stripped from Listen Address in form
    7. Verify all 4 script hooks are textareas in edit modal too
    8. Take screenshots of each verification step

    Expected Result: All 8 steps pass, screenshots captured to .sisyphus/evidence/

    Agent Profile: visual-engineering + Playwright skill

    Verification: All screenshots reviewed, no visual regression, all fields functional.


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:Port correctly
  • Column header "Bind Address"
  • All 4 script hooks are <textarea>
  • Register form label clarified
  • No empty-string sent to API
  • npm run build passes
  • vue-tsc --noEmit passes