feat: ipfs-webui v4.9.0 with retrieval diagnostics (#10969)

* fix(webui): show helpful errors for incompatible configurations

- show error when Gateway.NoFetch=true and WebUI is not available locally
- show error when Gateway.DeserializedResponses=false (incompatible)
- add tests for both error scenarios

* chore(webui): update to v4.9.0

https://github.com/ipfs/ipfs-webui/releases/tag/v4.9.0

* docs: add WebUI v4.9.0 update to v0.38 changelog

- highlight new diagnostics screen for troubleshooting
- include screenshots of key features in table format
- add local access URL for WebUI
- update TOC with new sections
This commit is contained in:
Marcin Rataj 2025-09-19 04:16:21 +02:00 committed by GitHub
parent fa17b69c7d
commit 90740dca21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 217 additions and 15 deletions

View File

@ -1,19 +1,31 @@
package corehttp
import (
"fmt"
"net"
"net/http"
"strings"
"github.com/ipfs/go-cid"
"github.com/ipfs/kubo/config"
core "github.com/ipfs/kubo/core"
)
// WebUI version confirmed to work with this Kubo version
const WebUIPath = "/ipfs/bafybeifplj2s3yegn7ko7tdnwpoxa4c5uaqnk2ajnw5geqm34slcj6b6mu" // v4.8.0
const WebUIPath = "/ipfs/bafybeietkqxghs3hm56e3w64s4papqlvvzqzjigs4eyuy24plkpz652fee" // v4.9.0
// WebUIPaths is a list of all past webUI paths.
var WebUIPaths = []string{
WebUIPath,
"/ipfs/bafybeifplj2s3yegn7ko7tdnwpoxa4c5uaqnk2ajnw5geqm34slcj6b6mu", // v4.8.0
"/ipfs/bafybeibfd5kbebqqruouji6ct5qku3tay273g7mt24mmrfzrsfeewaal5y", // v4.7.0
"/ipfs/bafybeibpaa5kqrj4gkemiswbwndjqiryl65cks64ypwtyerxixu56gnvvm", // v4.6.0
"/ipfs/bafybeiata4qg7xjtwgor6r5dw63jjxyouenyromrrb4lrewxrlvav7gzgi", // v4.5.0
"/ipfs/bafybeigp3zm7cqoiciqk5anlheenqjsgovp7j7zq6hah4nu6iugdgb4nby", // v4.4.2
"/ipfs/bafybeiatztgdllxnp5p6zu7bdwhjmozsmd7jprff4bdjqjljxtylitvss4", // v4.4.1
"/ipfs/bafybeibgic2ex3fvzkinhy6k6aqyv3zy2o7bkbsmrzvzka24xetv7eeadm",
"/ipfs/bafybeid4uxz7klxcu3ffsnmn64r7ihvysamlj4ohl5h2orjsffuegcpaeq",
"/ipfs/bafybeif6abowqcavbkz243biyh7pde7ick5kkwwytrh7pd2hkbtuqysjxy",
"/ipfs/bafybeibgic2ex3fvzkinhy6k6aqyv3zy2o7bkbsmrzvzka24xetv7eeadm", // v4.4.0
"/ipfs/bafybeid4uxz7klxcu3ffsnmn64r7ihvysamlj4ohl5h2orjsffuegcpaeq", // v4.3.3
"/ipfs/bafybeif6abowqcavbkz243biyh7pde7ick5kkwwytrh7pd2hkbtuqysjxy", // v4.3.2
"/ipfs/bafybeihatzsgposbr3hrngo42yckdyqcc56yean2rynnwpzxstvdlphxf4",
"/ipfs/bafybeigggyffcf6yfhx5irtwzx3cgnk6n3dwylkvcpckzhqqrigsxowjwe",
"/ipfs/bafybeidf7cpkwsjkq6xs3r6fbbxghbugilx3jtezbza7gua3k5wjixpmba",
@ -22,18 +34,18 @@ var WebUIPaths = []string{
"/ipfs/bafybeicyp7ssbnj3hdzehcibmapmpuc3atrsc4ch3q6acldfh4ojjdbcxe",
"/ipfs/bafybeigs6d53gpgu34553mbi5bbkb26e4ikruoaaar75jpfdywpup2r3my",
"/ipfs/bafybeic4gops3d3lyrisqku37uio33nvt6fqxvkxihrwlqsuvf76yln4fm",
"/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte",
"/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte", // v2.22.0
"/ipfs/bafybeiequgo72mrvuml56j4gk7crewig5bavumrrzhkqbim6b3s2yqi7ty",
"/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy",
"/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa",
"/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y",
"/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm",
"/ipfs/bafybeibozpulxtpv5nhfa2ue3dcjx23ndh3gwr5vwllk7ptoyfwnfjjr4q",
"/ipfs/bafybeiednzu62vskme5wpoj4bjjikeg3xovfpp4t7vxk5ty2jxdi4mv4bu",
"/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva",
"/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy", // v2.20.0
"/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa", // v2.19.0
"/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y", // v2.18.1
"/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm", // v2.18.0
"/ipfs/bafybeibozpulxtpv5nhfa2ue3dcjx23ndh3gwr5vwllk7ptoyfwnfjjr4q", // v2.15.1
"/ipfs/bafybeiednzu62vskme5wpoj4bjjikeg3xovfpp4t7vxk5ty2jxdi4mv4bu", // v2.15.0
"/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva", // v2.13.0
"/ipfs/bafybeiflkjt66aetfgcrgvv75izymd5kc47g6luepqmfq6zsf5w6ueth6y",
"/ipfs/bafybeid26vjplsejg7t3nrh7mxmiaaxriebbm4xxrxxdunlk7o337m5sqq",
"/ipfs/bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i",
"/ipfs/bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i", // v2.11.4
"/ipfs/bafybeianwe4vy7sprht5sm3hshvxjeqhwcmvbzq73u55sdhqngmohkjgs4",
"/ipfs/bafybeicitin4p7ggmyjaubqpi3xwnagrwarsy6hiihraafk5rcrxqxju6m",
"/ipfs/bafybeihpetclqvwb4qnmumvcn7nh4pxrtugrlpw4jgjpqicdxsv7opdm6e",
@ -72,4 +84,85 @@ var WebUIPaths = []string{
"/ipfs/Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip",
}
var WebUIOption = RedirectOption("webui", WebUIPath)
// WebUIOption provides the WebUI handler for the RPC API.
func WebUIOption(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
cfg, err := n.Repo.Config()
if err != nil {
return nil, err
}
handler := &webUIHandler{
headers: cfg.API.HTTPHeaders,
node: n,
noFetch: cfg.Gateway.NoFetch,
deserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses),
}
mux.Handle("/webui/", handler)
return mux, nil
}
type webUIHandler struct {
headers map[string][]string
node *core.IpfsNode
noFetch bool
deserializedResponses bool
}
func (h *webUIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for k, v := range h.headers {
w.Header()[http.CanonicalHeaderKey(k)] = v
}
// Check if WebUI is incompatible with current configuration
if !h.deserializedResponses {
h.writeIncompatibleError(w)
return
}
// Check if WebUI is available locally when Gateway.NoFetch is true
if h.noFetch {
cidStr := strings.TrimPrefix(WebUIPath, "/ipfs/")
webUICID, err := cid.Parse(cidStr)
if err != nil {
// This should never happen with hardcoded constant
log.Errorf("failed to parse WebUI CID: %v", err)
} else {
has, err := h.node.Blockstore.Has(r.Context(), webUICID)
if err != nil {
log.Debugf("error checking WebUI availability: %v", err)
} else if !has {
h.writeNotAvailableError(w)
return
}
}
}
// Default behavior: redirect to the WebUI path
http.Redirect(w, r, WebUIPath, http.StatusFound)
}
func (h *webUIHandler) writeIncompatibleError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, `IPFS WebUI Incompatible
WebUI is not compatible with Gateway.DeserializedResponses=false.
The WebUI requires deserializing IPFS responses to render the interface.
To use the WebUI, set Gateway.DeserializedResponses=true in your config.
`)
}
func (h *webUIHandler) writeNotAvailableError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, `IPFS WebUI Not Available
WebUI at %s is not in your local node due to Gateway.NoFetch=true.
To use the WebUI, either:
1. Run: ipfs pin add --progress --name ipfs-webui %s
2. Download from https://github.com/ipfs/ipfs-webui/releases and import with: ipfs dag import ipfs-webui.car
`, WebUIPath, WebUIPath)
}

View File

@ -14,7 +14,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
- [🧹 Experimental Sweeping DHT Provider](#-experimental-sweeping-dht-provider)
- [📊 Exposed DHT metrics](#-exposed-dht-metrics)
- [🚨 Improved gateway error pages with diagnostic tools](#-improved-gateway-error-pages-with-diagnostic-tools)
- [🛠️ Identity CID size enforcement and `ipfs files write` fixes](#-identity-cid-size-enforcement-and-ipfs-files-write-fixes)
- [🎨 Updated WebUI](#-updated-webui)
- [🛠️ Identity CID size enforcement and `ipfs files write` fixes](#-identity-cid-size-enforcement-and-ipfs-files-write-fixes)
- [📦️ Important dependency updates](#-important-dependency-updates)
- [📝 Changelog](#-changelog)
- [👨‍👩‍👧‍👦 Contributors](#-contributors)
@ -64,6 +65,22 @@ Gateway error pages now provide more actionable information during content retri
> - **Enhanced error details**: Timeout errors now display the retrieval phase where failure occurred (e.g., "connecting to providers", "fetching data") and up to 3 peer IDs that were attempted but couldn't deliver the content, making it easier to diagnose network or provider issues.
> - **Retry button on all error pages**: Every gateway error page now includes a retry button for quick page refresh without manual URL re-entry.
#### 🎨 Updated WebUI
The Web UI has been updated to [v4.9.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.9.0) with a new **Diagnostics** screen for troubleshooting and system monitoring. Access it at `http://127.0.0.1:5001/webui` when running your local IPFS node.
| Diagnostics: Logs | Files: Check Retrieval | Diagnostics: Retrieval Results |
|:---:|:---:|:---:|
| ![Diagnostics logs](https://github.com/user-attachments/assets/a1560fd2-6f4e-4e4f-9506-85ecb10f96e5) | ![Retrieval check interface](https://github.com/user-attachments/assets/6efa8bf1-705e-4256-8c66-282455daf789) | ![Retrieval check results](https://github.com/user-attachments/assets/970f2de3-94a3-4d48-b0a4-46832f73c2e9) |
| Debug issues in real-time by adjusting [log level](https://github.com/ipfs/kubo/blob/master/docs/environment-variables.md#golog_log_level) without restart (global or per-subsystem like bitswap) | Check if content is available to other peers directly from Files screen | Find out why content won't load or who is providing it to the network |
| Peers: Agent Versions | Files: Custom Sorting |
|:---:|:---:|
| ![Peers with Agent Version](https://github.com/user-attachments/assets/4bf95e72-193a-415d-9428-dd222795107a) | ![File sorting options](https://github.com/user-attachments/assets/fd7a1807-c487-4393-ab60-a16ae087e6cd) |
| Know what software peers run | Find files faster with new sorting |
Additional improvements include a close button in the file viewer, better error handling, and fixed navigation highlighting.
#### 📌 Pin name improvements
`ipfs pin ls <cid> --names` now correctly returns pin names for specific CIDs ([#10649](https://github.com/ipfs/kubo/issues/10649), [boxo#1035](https://github.com/ipfs/boxo/pull/1035)), and RPC no longer incorrectly returns names from other pins ([#10966](https://github.com/ipfs/kubo/pull/10966)).
@ -87,6 +104,10 @@ The new [`Internal.MFSAutoflushThreshold`](https://github.com/ipfs/kubo/blob/mas
### 📦️ Important dependency updates
- update `boxo` to [v0.34.1-0.20250919000230-031df82b284f](https://github.com/ipfs/boxo/commit/031df82b284f)
- update `go-libp2p-kad-dht` to [v0.34.1-0.20250918150625-2e3cea182b63](https://github.com/libp2p/go-libp2p-kad-dht/commit/2e3cea182b63)
- update `ipfs-webui` to [v4.9.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.9.0)
### 📝 Changelog
<details><summary>Full Changelog</summary>

88
test/cli/webui_test.go Normal file
View File

@ -0,0 +1,88 @@
package cli
import (
"net/http"
"testing"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/stretchr/testify/assert"
)
func TestWebUI(t *testing.T) {
t.Parallel()
t.Run("NoFetch=true shows not available error", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.NoFetch = true
})
node.StartDaemon("--offline")
apiClient := node.APIClient()
resp := apiClient.Get("/webui/")
// Should return 503 Service Unavailable when WebUI is not in local store
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
// Check response contains helpful information
body := resp.Body
assert.Contains(t, body, "IPFS WebUI Not Available")
assert.Contains(t, body, "Gateway.NoFetch=true")
assert.Contains(t, body, "ipfs pin add")
assert.Contains(t, body, "ipfs dag import")
assert.Contains(t, body, "https://github.com/ipfs/ipfs-webui/releases")
})
t.Run("DeserializedResponses=false shows incompatible error", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.DeserializedResponses = config.False
})
node.StartDaemon()
apiClient := node.APIClient()
resp := apiClient.Get("/webui/")
// Should return 503 Service Unavailable
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
// Check response contains incompatibility message
body := resp.Body
assert.Contains(t, body, "IPFS WebUI Incompatible")
assert.Contains(t, body, "Gateway.DeserializedResponses=false")
assert.Contains(t, body, "WebUI requires deserializing IPFS responses")
assert.Contains(t, body, "Gateway.DeserializedResponses=true")
})
t.Run("Both NoFetch=true and DeserializedResponses=false shows incompatible error", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.NoFetch = true
cfg.Gateway.DeserializedResponses = config.False
})
node.StartDaemon("--offline")
apiClient := node.APIClient()
resp := apiClient.Get("/webui/")
// Should return 503 Service Unavailable
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
// DeserializedResponses=false takes priority
body := resp.Body
assert.Contains(t, body, "IPFS WebUI Incompatible")
assert.Contains(t, body, "Gateway.DeserializedResponses=false")
// Should NOT mention NoFetch since DeserializedResponses check comes first
assert.NotContains(t, body, "NoFetch")
})
}