From 59505b443fa5644b61d7d423030dd9e284e2d465 Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Mon, 6 Jul 2026 11:45:44 -0400 Subject: [PATCH] fix(nfs): reconcile clients[i].advanced type mismatch (object vs string) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend: db.NFSClient.Advanced is db.NFSAdvanced (struct → JSON object) handler: nfsClientRequest.Advanced was string → now db.NFSAdvanced frontend: api.ts NFSClient.advanced string → NFSAdvanced (object) frontend: Nfs.tsx updated to work with object advanced per host Without this fix, editing an existing NFS export with per-host options (loaded from DB via migration 0005) would 400 on PUT because the backend expected a string but received a JSON object. --- internal/web/handlers_nfs.go | 23 ++++++------------ web/src/api.ts | 11 ++++++++- web/src/pages/Nfs.tsx | 47 ++++++++++++++++++------------------ 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/internal/web/handlers_nfs.go b/internal/web/handlers_nfs.go index 52d0c1f..d4e02ef 100644 --- a/internal/web/handlers_nfs.go +++ b/internal/web/handlers_nfs.go @@ -14,12 +14,12 @@ import ( ) type nfsClientRequest struct { - Host string `json:"host"` - ReadOnly bool `json:"read_only"` - Async bool `json:"async"` - RootSquash bool `json:"root_squash"` - SubtreeCheck bool `json:"subtree_check"` - Advanced string `json:"advanced"` + Host string `json:"host"` + ReadOnly bool `json:"read_only"` + Async bool `json:"async"` + RootSquash bool `json:"root_squash"` + SubtreeCheck bool `json:"subtree_check"` + Advanced db.NFSAdvanced `json:"advanced"` } type nfsExportRequest struct { @@ -54,7 +54,7 @@ func (req nfsExportRequest) toModel() db.NFSExport { Async: c.Async, RootSquash: c.RootSquash, SubtreeCheck: c.SubtreeCheck, - Advanced: parseNFSAdvanced(c.Advanced), + Advanced: c.Advanced, }) } return db.NFSExport{ @@ -68,15 +68,6 @@ func (req nfsExportRequest) toModel() db.NFSExport { } } -func parseNFSAdvanced(raw string) db.NFSAdvanced { - var adv db.NFSAdvanced - if raw == "" || raw == "{}" { - return adv - } - _ = json.Unmarshal([]byte(raw), &adv) - return adv -} - func (s *Server) handleListNFSExports(w http.ResponseWriter, r *http.Request) { exports, err := s.DB.ListNFSExports() if err != nil { diff --git a/web/src/api.ts b/web/src/api.ts index 6b1b19f..d0b1153 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -15,7 +15,16 @@ export interface NFSClient { async: boolean; root_squash: boolean; subtree_check: boolean; - advanced: string; + advanced: NFSAdvanced; +} + +export interface NFSAdvanced { + all_squash: boolean; + secure: boolean; + wdelay: boolean; + hide: boolean; + crossmnt: boolean; + [key: string]: boolean; } export interface NFSExport { diff --git a/web/src/pages/Nfs.tsx b/web/src/pages/Nfs.tsx index f2dad57..ba6ce1e 100644 --- a/web/src/pages/Nfs.tsx +++ b/web/src/pages/Nfs.tsx @@ -30,14 +30,18 @@ function serializeAdvanced(m: Record): string { return JSON.stringify(m); } -function clientDefaults(exp: Partial): Partial { +function emptyAdvanced() { + return { all_squash: false, secure: false, wdelay: false, hide: false, crossmnt: false }; +} + +function clientDefaults(exp: Partial): NFSClient { return { host: "", read_only: exp.read_only ?? false, async: exp.async ?? false, root_squash: exp.root_squash ?? true, subtree_check: exp.subtree_check ?? false, - advanced: exp.advanced ?? "{}", + advanced: emptyAdvanced(), }; } @@ -72,7 +76,10 @@ export default function Nfs() { setAdvanced(parseAdvanced(x.advanced ?? "{}")); setShowAdvanced(false); setEditing(x); - setHostDrafts(x.clients.map(c => ({ ...c }))); + setHostDrafts(x.clients.map(c => ({ + ...c, + advanced: c.advanced ?? emptyAdvanced(), + }))); } function openNew() { @@ -89,10 +96,7 @@ export default function Nfs() { const payload = { ...editing, - clients: hostDrafts.map(h => ({ - ...h, - advanced: h.advanced !== undefined ? h.advanced : serializeAdvanced(advanced), - })), + clients: hostDrafts.map(h => ({ ...h })), advanced: serializeAdvanced(advanced), }; @@ -138,9 +142,7 @@ export default function Nfs() { function toggleHostAdvanced(idx: number, key: string) { setHostDrafts(prev => prev.map((h, i) => { if (i !== idx) return h; - const adv = parseAdvanced(h.advanced || "{}"); - adv[key] = !adv[key]; - return { ...h, advanced: serializeAdvanced(adv) }; + return { ...h, advanced: { ...h.advanced, [key]: !h.advanced[key] } }; })); } @@ -262,20 +264,17 @@ export default function Nfs() { {showAdvanced ? "▾" : "▸"} Avanzado
- {ADVANCED_KEYS.map(({ key, label }) => { - const adv = parseAdvanced(c.advanced || "{}"); - return ( - - ); - })} + {ADVANCED_KEYS.map(({ key, label }) => ( + + ))}