webui: fix more wrappers decoding keyed GETs as bare nodes

Three more RESTCONF wrappers assume the response to a keyed GET is the
bare node, when the server nests it under the full parent path: the
container resource-usage stats always read zero, the WireGuard listen
port never populated, and resetting advertised link modes silently
re-enabled auto-negotiation on links where it was forced off.

Same class of bug as the firewall zone reset fix: the decode matches
nothing and the zero value is used as if valid.  Model the full nesting
in all three wrappers, reusing the existing container list wrapper.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-07-04 09:32:02 +02:00
parent 7291b812a6
commit 4c6aa115a3
3 changed files with 26 additions and 15 deletions
@@ -1369,15 +1369,22 @@ func (h *ConfigureInterfacesHandler) ResetEthernetAdvertised(w http.ResponseWrit
name := r.PathValue("name")
base := ifacePath(name) + "/ieee802-ethernet-interface:ethernet"
// Keyed GETs nest the requested node under its full parent path.
var resp struct {
Interfaces struct {
Interface []struct {
Ethernet struct {
AN struct {
Enable *bool `json:"enable"`
} `json:"ieee802-ethernet-interface:auto-negotiation"`
} `json:"auto-negotiation"`
} `json:"ieee802-ethernet-interface:ethernet"`
} `json:"interface"`
} `json:"ietf-interfaces:interfaces"`
}
enable := true // YANG default
if err := h.RC.Get(r.Context(), base+"/auto-negotiation", &resp); err == nil {
if resp.AN.Enable != nil {
enable = *resp.AN.Enable
if ifs := resp.Interfaces.Interface; len(ifs) > 0 && ifs[0].Ethernet.AN.Enable != nil {
enable = *ifs[0].Ethernet.AN.Enable
}
} else if !restconf.IsNotFound(err) {
log.Printf("configure interfaces %s reset advertised get: %v", name, err)
+5 -7
View File
@@ -46,11 +46,6 @@ type containerListWrapper struct {
} `json:"infix-containers:containers"`
}
// containerResourceUsageWrapper wraps the RESTCONF resource-usage response.
type containerResourceUsageWrapper struct {
ResourceUsage containerResourceUsageJSON `json:"infix-containers:resource-usage"`
}
// ContainerEntry holds display-ready data for a single container row.
type ContainerEntry struct {
Name string
@@ -106,13 +101,16 @@ func (h *ContainersHandler) Overview(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
path := fmt.Sprintf("/data/infix-containers:containers/container=%s/resource-usage",
url.PathEscape(name))
var w containerResourceUsageWrapper
var w containerListWrapper // keyed GETs nest under the full parent path
if err := h.RC.Get(ctx, path, &w); err != nil {
log.Printf("restconf resource-usage %s: %v", name, err)
return
}
if len(w.Containers.Container) == 0 {
return
}
mu.Lock()
usages[idx] = w.ResourceUsage
usages[idx] = w.Containers.Container[0].ResourceUsage
mu.Unlock()
}(i, c.Name)
}
+8 -2
View File
@@ -21,8 +21,13 @@ type wgConfigJSON struct {
}
// wgIfaceConfigWrapper is used to fetch per-interface WireGuard config.
// Keyed GETs nest the requested node under its full parent path.
type wgIfaceConfigWrapper struct {
Interfaces struct {
Interface []struct {
WireGuard *wgConfigJSON `json:"infix-interfaces:wireguard"`
} `json:"interface"`
} `json:"ietf-interfaces:interfaces"`
}
// WGPeer holds display-ready data for a single WireGuard peer.
@@ -128,8 +133,9 @@ func buildWGTunnel(ctx context.Context, rc *restconf.Client, iface ifaceJSON) WG
// Fetch ListenPort from config endpoint (separate from oper-state).
var cfgWrap wgIfaceConfigWrapper
path := fmt.Sprintf("/data/ietf-interfaces:interfaces/interface=%s/infix-interfaces:wireguard", iface.Name)
if err := rc.Get(ctx, path, &cfgWrap); err == nil && cfgWrap.WireGuard != nil {
tunnel.ListenPort = cfgWrap.WireGuard.ListenPort
if err := rc.Get(ctx, path, &cfgWrap); err == nil &&
len(cfgWrap.Interfaces.Interface) > 0 && cfgWrap.Interfaces.Interface[0].WireGuard != nil {
tunnel.ListenPort = cfgWrap.Interfaces.Interface[0].WireGuard.ListenPort
}
// Build peers from embedded peer-status.