webui: dedupe restconf 404 checks via restconf.IsNotFound

The errors.As(err, &rcErr) + StatusCode == http.StatusNotFound boilerplate
was repeated across six handlers. Replace with the IsNotFound helper
introduced in restconf/errors.go, dropping the now-unused errors import
from each affected file along the way.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-06-15 19:45:16 +02:00
parent f8432f59fb
commit f00511d235
9 changed files with 34 additions and 92 deletions
@@ -4,7 +4,6 @@ package handlers
import (
"context"
"errors"
"fmt"
"html/template"
"log"
@@ -716,17 +715,15 @@ func (h *ConfigureFirewallHandler) fetchFirewall(ctx context.Context) (*firewall
if err == nil {
return wrap.Firewall, wrap.Firewall != nil, nil
}
var rcErr *restconf.Error
if errors.As(err, &rcErr) && rcErr.StatusCode == http.StatusNotFound {
runErr := h.RC.Get(ctx, "/data/infix-firewall:firewall", &wrap)
if runErr == nil {
return wrap.Firewall, wrap.Firewall != nil, nil
}
var rcRun *restconf.Error
if errors.As(runErr, &rcRun) && rcRun.StatusCode == http.StatusNotFound {
return nil, false, nil
}
return nil, false, runErr
if !restconf.IsNotFound(err) {
return nil, false, err
}
return nil, false, err
runErr := h.RC.Get(ctx, "/data/infix-firewall:firewall", &wrap)
if runErr == nil {
return wrap.Firewall, wrap.Firewall != nil, nil
}
if restconf.IsNotFound(runErr) {
return nil, false, nil
}
return nil, false, runErr
}
@@ -8,7 +8,6 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"html/template"
"log"
@@ -69,18 +68,12 @@ func (h *ConfigureKeystoreHandler) Overview(w http.ResponseWriter, r *http.Reque
var ks keystoreWrapper
if err := h.RC.Get(r.Context(), keystorePath, &ks); err != nil {
var rcErr *restconf.Error
if errors.As(err, &rcErr) && rcErr.StatusCode == http.StatusNotFound {
if fallErr := h.RC.Get(r.Context(), "/data/ietf-keystore:keystore", &ks); fallErr != nil {
var rcFall *restconf.Error
if !errors.As(fallErr, &rcFall) || rcFall.StatusCode != http.StatusNotFound {
log.Printf("configure keystore (running fallback): %v", fallErr)
data.Error = "Could not read keystore"
}
}
} else {
if !restconf.IsNotFound(err) {
log.Printf("configure keystore: %v", err)
data.Error = "Could not read keystore"
} else if fallErr := h.RC.Get(r.Context(), "/data/ietf-keystore:keystore", &ks); fallErr != nil && !restconf.IsNotFound(fallErr) {
log.Printf("configure keystore (running fallback): %v", fallErr)
data.Error = "Could not read keystore"
}
}
@@ -4,7 +4,6 @@ package handlers
import (
"context"
"errors"
"fmt"
"html/template"
"log"
@@ -298,17 +297,13 @@ func (h *ConfigureRoutesHandler) fetchStaticCPP(ctx context.Context) (staticCPPW
if err == nil {
return cpp, nil
}
var rcErr *restconf.Error
if errors.As(err, &rcErr) && rcErr.StatusCode == http.StatusNotFound {
runErr := h.RC.Get(ctx, "/data"+staticCPPSuffix, &cpp)
if runErr == nil {
return cpp, nil
}
var rcRun *restconf.Error
if errors.As(runErr, &rcRun) && rcRun.StatusCode == http.StatusNotFound {
return cpp, nil // no static routes configured — not an error
}
return cpp, runErr
if !restconf.IsNotFound(err) {
return cpp, err
}
return cpp, err
runErr := h.RC.Get(ctx, "/data"+staticCPPSuffix, &cpp)
if runErr == nil || restconf.IsNotFound(runErr) {
// 404 on fallback = no static routes configured, not an error
return cpp, nil
}
return cpp, runErr
}
@@ -4,7 +4,6 @@ package handlers
import (
"encoding/json"
"errors"
"html/template"
"log"
"net/http"
@@ -108,19 +107,13 @@ func (h *ConfigureSystemHandler) Overview(w http.ResponseWriter, r *http.Request
var raw cfgSystemWrapper
if err := h.RC.Get(r.Context(), candidatePath+"/ietf-system:system", &raw); err != nil {
var rcErr *restconf.Error
if errors.As(err, &rcErr) && rcErr.StatusCode == http.StatusNotFound {
// Candidate not initialised — read from running as fallback.
if fallErr := h.RC.Get(r.Context(), "/data/ietf-system:system", &raw); fallErr != nil {
var rcFall *restconf.Error
if !errors.As(fallErr, &rcFall) || rcFall.StatusCode != http.StatusNotFound {
log.Printf("configure system (running fallback): %v", fallErr)
data.Error = "Could not read system configuration"
}
}
} else {
if !restconf.IsNotFound(err) {
log.Printf("configure system: %v", err)
data.Error = "Could not read candidate configuration"
} else if fallErr := h.RC.Get(r.Context(), "/data/ietf-system:system", &raw); fallErr != nil && !restconf.IsNotFound(fallErr) {
// Candidate not initialised — fall back to running; only real errors surface.
log.Printf("configure system (running fallback): %v", fallErr)
data.Error = "Could not read system configuration"
}
}
if data.Error == "" {
+1 -3
View File
@@ -3,7 +3,6 @@
package handlers
import (
"errors"
"html/template"
"log"
"net/http"
@@ -145,8 +144,7 @@ func (h *FirewallHandler) Overview(w http.ResponseWriter, r *http.Request) {
var fw firewallWrapper
err := h.RC.Get(r.Context(), "/data/infix-firewall:firewall", &fw)
if err != nil {
var rcErr *restconf.Error
if errors.As(err, &rcErr) && rcErr.StatusCode == http.StatusNotFound {
if restconf.IsNotFound(err) {
// Firewall module not active — show disabled state, not an error.
data.EnabledText = "Inactive"
} else {
@@ -206,12 +206,7 @@
hx-target="closest tr"
hx-swap="delete"
hx-confirm="Delete {{$row.InstanceName}}?">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
</td>
{{end}}
@@ -33,12 +33,7 @@
hx-target="#yang-detail"
hx-swap="innerHTML"
hx-confirm="Delete {{$row.InstanceName}}?">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
</td>
{{end}}
@@ -135,13 +135,7 @@
hx-confirm="Delete zone {{.Name}}?"
hx-swap="none"
title="Delete zone">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
{{end}}
</td>
@@ -375,13 +369,7 @@
hx-confirm="Delete policy {{.Name}}?"
hx-swap="none"
title="Delete policy">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
{{end}}
</td>
@@ -51,13 +51,7 @@
hx-confirm="Delete route {{.Prefix}}?"
hx-swap="none"
title="Delete route">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
</td>
</tr>
@@ -152,13 +146,7 @@
hx-confirm="Delete route {{.Prefix}}?"
hx-swap="none"
title="Delete route">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
<path d="M10 11v6M14 11v6"></path>
</svg>
{{template "icon-trash"}}
</button>
</td>
</tr>