diff --git a/src/webui/internal/handlers/yang_tree.go b/src/webui/internal/handlers/yang_tree.go index 1f01ea8e..e5fe380f 100644 --- a/src/webui/internal/handlers/yang_tree.go +++ b/src/webui/internal/handlers/yang_tree.go @@ -1227,17 +1227,17 @@ func (h *TreeHandler) SaveLeaf(w http.ResponseWriter, r *http.Request) { } body := map[string]any{qualName: coerceLeafValue(rawValue, node)} - data := &nodeDetailData{Node: node} + // Emit cfgSaved/cfgError like SaveGroup and let the client re-fetch the + // whole current page from the fresh candidate (so confd inference and + // normalisation surface) — don't echo the raw input back, which hid it. if putErr := h.RC.Put(r.Context(), candidateDS+path, body); putErr != nil { - data.Error = putErr.Error() - data.CurrentValue = rawValue - } else { - data.SavedOK = true - data.CurrentValue = rawValue + w.Header().Set("HX-Trigger", `{"cfgError":"`+node.Name+": "+putErr.Error()+`"}`) + w.WriteHeader(http.StatusUnprocessableEntity) + return } - - h.FragTmpl.ExecuteTemplate(w, "yang-node-detail", data) + w.Header().Set("HX-Trigger", `{"cfgSaved":"Saved `+node.Name+` to candidate"}`) + w.WriteHeader(http.StatusNoContent) } // DeleteLeaf serves DELETE /configure/tree/node?path=... diff --git a/src/webui/static/js/app.js b/src/webui/static/js/app.js index f740d419..19d9425e 100644 --- a/src/webui/static/js/app.js +++ b/src/webui/static/js/app.js @@ -2121,6 +2121,22 @@ function renderCfgLog() { span.classList.remove('saved'); }, 3000); } + + // Re-render the whole current tree page from the (now fresh) candidate. + // The save handlers only WRITE — they don't echo back what confd + // inferred from the change (DHCP option lists, related leaves, + // normalised values), and inference can land outside the edited node. + // Re-fetching the page the user is on surfaces all of it. Guarded on + // #yang-detail so curated pages that also emit cfgSaved are unaffected. + var detail = document.getElementById('yang-detail'); + if (detail && window.htmx) { + var node = (history.state && history.state.yangDetailPath) || + new URLSearchParams(window.location.search).get('node'); + if (node) { + htmx.ajax('GET', '/configure/tree/node?path=' + encodeURIComponent(node), + { target: '#yang-detail', swap: 'innerHTML' }); + } + } }); })(); diff --git a/src/webui/templates/fragments/yang-node-detail.html b/src/webui/templates/fragments/yang-node-detail.html index a5170538..3238f917 100644 --- a/src/webui/templates/fragments/yang-node-detail.html +++ b/src/webui/templates/fragments/yang-node-detail.html @@ -21,10 +21,12 @@ {{/* ── Edit form — only for writable leaves in non-ReadOnly mode ── */}} {{if and (not .ReadOnly) .Config (or (eq .Kind "leaf") (eq .Kind "leaf-list"))}} + {{/* hx-swap="none": SaveLeaf returns 204 + a cfgSaved trigger, and the + cfgSaved handler re-fetches the whole current tree page from the + fresh candidate so confd-inferred values surface. */}}