fix(nfs): reconcile clients[i].advanced type mismatch (object vs string)

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.
This commit is contained in:
2026-07-06 11:45:44 -04:00
parent 512feaffd7
commit 59505b443f
3 changed files with 40 additions and 41 deletions
+10 -1
View File
@@ -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 {
+23 -24
View File
@@ -30,14 +30,18 @@ function serializeAdvanced(m: Record<string, boolean>): string {
return JSON.stringify(m);
}
function clientDefaults(exp: Partial<NFSExport>): Partial<NFSClient> {
function emptyAdvanced() {
return { all_squash: false, secure: false, wdelay: false, hide: false, crossmnt: false };
}
function clientDefaults(exp: Partial<NFSExport>): 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
</summary>
<div className="mt-1 grid grid-cols-2 gap-y-1">
{ADVANCED_KEYS.map(({ key, label }) => {
const adv = parseAdvanced(c.advanced || "{}");
return (
<label key={key} className="flex items-center gap-1.5 text-xs text-slate-300">
<input
type="checkbox"
className="checkbox"
checked={!!adv[key]}
onChange={() => toggleHostAdvanced(idx, key)}
/>
{label}
</label>
);
})}
{ADVANCED_KEYS.map(({ key, label }) => (
<label key={key} className="flex items-center gap-1.5 text-xs text-slate-300">
<input
type="checkbox"
className="checkbox"
checked={!!c.advanced?.[key]}
onChange={() => toggleHostAdvanced(idx, key)}
/>
{label}
</label>
))}
</div>
</details>
</div>