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:
@@ -19,7 +19,7 @@ type nfsClientRequest struct {
|
|||||||
Async bool `json:"async"`
|
Async bool `json:"async"`
|
||||||
RootSquash bool `json:"root_squash"`
|
RootSquash bool `json:"root_squash"`
|
||||||
SubtreeCheck bool `json:"subtree_check"`
|
SubtreeCheck bool `json:"subtree_check"`
|
||||||
Advanced string `json:"advanced"`
|
Advanced db.NFSAdvanced `json:"advanced"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type nfsExportRequest struct {
|
type nfsExportRequest struct {
|
||||||
@@ -54,7 +54,7 @@ func (req nfsExportRequest) toModel() db.NFSExport {
|
|||||||
Async: c.Async,
|
Async: c.Async,
|
||||||
RootSquash: c.RootSquash,
|
RootSquash: c.RootSquash,
|
||||||
SubtreeCheck: c.SubtreeCheck,
|
SubtreeCheck: c.SubtreeCheck,
|
||||||
Advanced: parseNFSAdvanced(c.Advanced),
|
Advanced: c.Advanced,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return db.NFSExport{
|
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) {
|
func (s *Server) handleListNFSExports(w http.ResponseWriter, r *http.Request) {
|
||||||
exports, err := s.DB.ListNFSExports()
|
exports, err := s.DB.ListNFSExports()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+10
-1
@@ -15,7 +15,16 @@ export interface NFSClient {
|
|||||||
async: boolean;
|
async: boolean;
|
||||||
root_squash: boolean;
|
root_squash: boolean;
|
||||||
subtree_check: 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 {
|
export interface NFSExport {
|
||||||
|
|||||||
+15
-16
@@ -30,14 +30,18 @@ function serializeAdvanced(m: Record<string, boolean>): string {
|
|||||||
return JSON.stringify(m);
|
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 {
|
return {
|
||||||
host: "",
|
host: "",
|
||||||
read_only: exp.read_only ?? false,
|
read_only: exp.read_only ?? false,
|
||||||
async: exp.async ?? false,
|
async: exp.async ?? false,
|
||||||
root_squash: exp.root_squash ?? true,
|
root_squash: exp.root_squash ?? true,
|
||||||
subtree_check: exp.subtree_check ?? false,
|
subtree_check: exp.subtree_check ?? false,
|
||||||
advanced: exp.advanced ?? "{}",
|
advanced: emptyAdvanced(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +76,10 @@ export default function Nfs() {
|
|||||||
setAdvanced(parseAdvanced(x.advanced ?? "{}"));
|
setAdvanced(parseAdvanced(x.advanced ?? "{}"));
|
||||||
setShowAdvanced(false);
|
setShowAdvanced(false);
|
||||||
setEditing(x);
|
setEditing(x);
|
||||||
setHostDrafts(x.clients.map(c => ({ ...c })));
|
setHostDrafts(x.clients.map(c => ({
|
||||||
|
...c,
|
||||||
|
advanced: c.advanced ?? emptyAdvanced(),
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
function openNew() {
|
function openNew() {
|
||||||
@@ -89,10 +96,7 @@ export default function Nfs() {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...editing,
|
...editing,
|
||||||
clients: hostDrafts.map(h => ({
|
clients: hostDrafts.map(h => ({ ...h })),
|
||||||
...h,
|
|
||||||
advanced: h.advanced !== undefined ? h.advanced : serializeAdvanced(advanced),
|
|
||||||
})),
|
|
||||||
advanced: serializeAdvanced(advanced),
|
advanced: serializeAdvanced(advanced),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,9 +142,7 @@ export default function Nfs() {
|
|||||||
function toggleHostAdvanced(idx: number, key: string) {
|
function toggleHostAdvanced(idx: number, key: string) {
|
||||||
setHostDrafts(prev => prev.map((h, i) => {
|
setHostDrafts(prev => prev.map((h, i) => {
|
||||||
if (i !== idx) return h;
|
if (i !== idx) return h;
|
||||||
const adv = parseAdvanced(h.advanced || "{}");
|
return { ...h, advanced: { ...h.advanced, [key]: !h.advanced[key] } };
|
||||||
adv[key] = !adv[key];
|
|
||||||
return { ...h, advanced: serializeAdvanced(adv) };
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,20 +264,17 @@ export default function Nfs() {
|
|||||||
{showAdvanced ? "▾" : "▸"} Avanzado
|
{showAdvanced ? "▾" : "▸"} Avanzado
|
||||||
</summary>
|
</summary>
|
||||||
<div className="mt-1 grid grid-cols-2 gap-y-1">
|
<div className="mt-1 grid grid-cols-2 gap-y-1">
|
||||||
{ADVANCED_KEYS.map(({ key, label }) => {
|
{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">
|
<label key={key} className="flex items-center gap-1.5 text-xs text-slate-300">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="checkbox"
|
className="checkbox"
|
||||||
checked={!!adv[key]}
|
checked={!!c.advanced?.[key]}
|
||||||
onChange={() => toggleHostAdvanced(idx, key)}
|
onChange={() => toggleHostAdvanced(idx, key)}
|
||||||
/>
|
/>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user