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
Marcin Rataj committed
Sep 19, 2025 at 04:16 UTC
90740dca21641992f1258f27a0a289dcefc784ea
3 files changed
+217
-15
core/corehttp/webui.go
+107
-14
@@ -1,19 +1,31 @@
1
package corehttp
2
3
+import (
4
+ "fmt"
5
+ "net"
6
+ "net/http"
7
+ "strings"
8
+
9
+ "github.com/ipfs/go-cid"
10
+ "github.com/ipfs/kubo/config"
11
+ core "github.com/ipfs/kubo/core"
12
+)
13
+
14
// WebUI version confirmed to work with this Kubo version
4
-const WebUIPath = "/ipfs/bafybeifplj2s3yegn7ko7tdnwpoxa4c5uaqnk2ajnw5geqm34slcj6b6mu" // v4.8.0
15
+const WebUIPath = "/ipfs/bafybeietkqxghs3hm56e3w64s4papqlvvzqzjigs4eyuy24plkpz652fee" // v4.9.0
16
17
// WebUIPaths is a list of all past webUI paths.
18
var WebUIPaths = []string{
19
WebUIPath,
20
+ "/ipfs/bafybeifplj2s3yegn7ko7tdnwpoxa4c5uaqnk2ajnw5geqm34slcj6b6mu", // v4.8.0
21
"/ipfs/bafybeibfd5kbebqqruouji6ct5qku3tay273g7mt24mmrfzrsfeewaal5y", // v4.7.0
22
"/ipfs/bafybeibpaa5kqrj4gkemiswbwndjqiryl65cks64ypwtyerxixu56gnvvm", // v4.6.0
23
"/ipfs/bafybeiata4qg7xjtwgor6r5dw63jjxyouenyromrrb4lrewxrlvav7gzgi", // v4.5.0
24
"/ipfs/bafybeigp3zm7cqoiciqk5anlheenqjsgovp7j7zq6hah4nu6iugdgb4nby", // v4.4.2
25
"/ipfs/bafybeiatztgdllxnp5p6zu7bdwhjmozsmd7jprff4bdjqjljxtylitvss4", // v4.4.1
14
- "/ipfs/bafybeibgic2ex3fvzkinhy6k6aqyv3zy2o7bkbsmrzvzka24xetv7eeadm",
15
- "/ipfs/bafybeid4uxz7klxcu3ffsnmn64r7ihvysamlj4ohl5h2orjsffuegcpaeq",
16
- "/ipfs/bafybeif6abowqcavbkz243biyh7pde7ick5kkwwytrh7pd2hkbtuqysjxy",
26
+ "/ipfs/bafybeibgic2ex3fvzkinhy6k6aqyv3zy2o7bkbsmrzvzka24xetv7eeadm", // v4.4.0
27
+ "/ipfs/bafybeid4uxz7klxcu3ffsnmn64r7ihvysamlj4ohl5h2orjsffuegcpaeq", // v4.3.3
28
+ "/ipfs/bafybeif6abowqcavbkz243biyh7pde7ick5kkwwytrh7pd2hkbtuqysjxy", // v4.3.2
29
"/ipfs/bafybeihatzsgposbr3hrngo42yckdyqcc56yean2rynnwpzxstvdlphxf4",
30
"/ipfs/bafybeigggyffcf6yfhx5irtwzx3cgnk6n3dwylkvcpckzhqqrigsxowjwe",
31
"/ipfs/bafybeidf7cpkwsjkq6xs3r6fbbxghbugilx3jtezbza7gua3k5wjixpmba",
@@ -22,18 +34,18 @@ var WebUIPaths = []string{
34
"/ipfs/bafybeicyp7ssbnj3hdzehcibmapmpuc3atrsc4ch3q6acldfh4ojjdbcxe",
35
"/ipfs/bafybeigs6d53gpgu34553mbi5bbkb26e4ikruoaaar75jpfdywpup2r3my",
36
"/ipfs/bafybeic4gops3d3lyrisqku37uio33nvt6fqxvkxihrwlqsuvf76yln4fm",
25
- "/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte",
37
+ "/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte", // v2.22.0
38
"/ipfs/bafybeiequgo72mrvuml56j4gk7crewig5bavumrrzhkqbim6b3s2yqi7ty",
27
- "/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy",
28
- "/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa",
29
- "/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y",
30
- "/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm",
31
- "/ipfs/bafybeibozpulxtpv5nhfa2ue3dcjx23ndh3gwr5vwllk7ptoyfwnfjjr4q",
32
- "/ipfs/bafybeiednzu62vskme5wpoj4bjjikeg3xovfpp4t7vxk5ty2jxdi4mv4bu",
33
- "/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva",
39
+ "/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy", // v2.20.0
40
+ "/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa", // v2.19.0
41
+ "/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y", // v2.18.1
42
+ "/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm", // v2.18.0
43
+ "/ipfs/bafybeibozpulxtpv5nhfa2ue3dcjx23ndh3gwr5vwllk7ptoyfwnfjjr4q", // v2.15.1
44
+ "/ipfs/bafybeiednzu62vskme5wpoj4bjjikeg3xovfpp4t7vxk5ty2jxdi4mv4bu", // v2.15.0
45
+ "/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva", // v2.13.0
46
"/ipfs/bafybeiflkjt66aetfgcrgvv75izymd5kc47g6luepqmfq6zsf5w6ueth6y",
47
"/ipfs/bafybeid26vjplsejg7t3nrh7mxmiaaxriebbm4xxrxxdunlk7o337m5sqq",
36
- "/ipfs/bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i",
48
+ "/ipfs/bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i", // v2.11.4
49
"/ipfs/bafybeianwe4vy7sprht5sm3hshvxjeqhwcmvbzq73u55sdhqngmohkjgs4",
50
"/ipfs/bafybeicitin4p7ggmyjaubqpi3xwnagrwarsy6hiihraafk5rcrxqxju6m",
51
"/ipfs/bafybeihpetclqvwb4qnmumvcn7nh4pxrtugrlpw4jgjpqicdxsv7opdm6e",
@@ -72,4 +84,85 @@ var WebUIPaths = []string{
84
"/ipfs/Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip",
85
}
86
75
-var WebUIOption = RedirectOption("webui", WebUIPath)
87
+// WebUIOption provides the WebUI handler for the RPC API.
88
+func WebUIOption(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
89
+ cfg, err := n.Repo.Config()
90
+ if err != nil {
91
+ return nil, err
92
+ }
93
+
94
+ handler := &webUIHandler{
95
+ headers: cfg.API.HTTPHeaders,
96
+ node: n,
97
+ noFetch: cfg.Gateway.NoFetch,
98
+ deserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses),
99
+ }
100
+
101
+ mux.Handle("/webui/", handler)
102
+ return mux, nil
103
+}
104
+
105
+type webUIHandler struct {
106
+ headers map[string][]string
107
+ node *core.IpfsNode
108
+ noFetch bool
109
+ deserializedResponses bool
110
+}
111
+
112
+func (h *webUIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
113
+ for k, v := range h.headers {
114
+ w.Header()[http.CanonicalHeaderKey(k)] = v
115
+ }
116
+
117
+ // Check if WebUI is incompatible with current configuration
118
+ if !h.deserializedResponses {
119
+ h.writeIncompatibleError(w)
120
+ return
121
+ }
122
+
123
+ // Check if WebUI is available locally when Gateway.NoFetch is true
124
+ if h.noFetch {
125
+ cidStr := strings.TrimPrefix(WebUIPath, "/ipfs/")
126
+ webUICID, err := cid.Parse(cidStr)
127
+ if err != nil {
128
+ // This should never happen with hardcoded constant
129
+ log.Errorf("failed to parse WebUI CID: %v", err)
130
+ } else {
131
+ has, err := h.node.Blockstore.Has(r.Context(), webUICID)
132
+ if err != nil {
133
+ log.Debugf("error checking WebUI availability: %v", err)
134
+ } else if !has {
135
+ h.writeNotAvailableError(w)
136
+ return
137
+ }
138
+ }
139
+ }
140
+
141
+ // Default behavior: redirect to the WebUI path
142
+ http.Redirect(w, r, WebUIPath, http.StatusFound)
143
+}
144
+
145
+func (h *webUIHandler) writeIncompatibleError(w http.ResponseWriter) {
146
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
147
+ w.WriteHeader(http.StatusServiceUnavailable)
148
+ fmt.Fprintf(w, `IPFS WebUI Incompatible
149
+
150
+WebUI is not compatible with Gateway.DeserializedResponses=false.
151
+
152
+The WebUI requires deserializing IPFS responses to render the interface.
153
+To use the WebUI, set Gateway.DeserializedResponses=true in your config.
154
+`)
155
+}
156
+
157
+func (h *webUIHandler) writeNotAvailableError(w http.ResponseWriter) {
158
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
159
+ w.WriteHeader(http.StatusServiceUnavailable)
160
+ fmt.Fprintf(w, `IPFS WebUI Not Available
161
+
162
+WebUI at %s is not in your local node due to Gateway.NoFetch=true.
163
+
164
+To use the WebUI, either:
165
+1. Run: ipfs pin add --progress --name ipfs-webui %s
166
+2. Download from https://github.com/ipfs/ipfs-webui/releases and import with: ipfs dag import ipfs-webui.car
167
+`, WebUIPath, WebUIPath)
168
+}
docs/changelogs/v0.38.md
+22
-1
@@ -14,7 +14,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
14
- [🧹 Experimental Sweeping DHT Provider](#-experimental-sweeping-dht-provider)
15
- [📊 Exposed DHT metrics](#-exposed-dht-metrics)
16
- [🚨 Improved gateway error pages with diagnostic tools](#-improved-gateway-error-pages-with-diagnostic-tools)
17
- - [🛠️ Identity CID size enforcement and `ipfs files write` fixes](#-identity-cid-size-enforcement-and-ipfs-files-write-fixes)
17
+ - [🎨 Updated WebUI](#-updated-webui)
18
+ - [🛠️ Identity CID size enforcement and `ipfs files write` fixes](#️-identity-cid-size-enforcement-and-ipfs-files-write-fixes)
19
- [📦️ Important dependency updates](#-important-dependency-updates)
20
- [📝 Changelog](#-changelog)
21
- [👨👩👧👦 Contributors](#-contributors)
@@ -64,6 +65,22 @@ Gateway error pages now provide more actionable information during content retri
65
> - **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.
66
> - **Retry button on all error pages**: Every gateway error page now includes a retry button for quick page refresh without manual URL re-entry.
67
68
+#### 🎨 Updated WebUI
69
+
70
+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.
71
+
72
+| Diagnostics: Logs | Files: Check Retrieval | Diagnostics: Retrieval Results |
73
+|:---:|:---:|:---:|
74
+|  |  |  |
75
+| 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 |
76
+
77
+| Peers: Agent Versions | Files: Custom Sorting |
78
+|:---:|:---:|
79
+|  |  |
80
+| Know what software peers run | Find files faster with new sorting |
81
+
82
+Additional improvements include a close button in the file viewer, better error handling, and fixed navigation highlighting.
83
+
84
#### 📌 Pin name improvements
85
86
`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
104
105
### 📦️ Important dependency updates
106
107
+- update `boxo` to [v0.34.1-0.20250919000230-031df82b284f](https://github.com/ipfs/boxo/commit/031df82b284f)
108
+- update `go-libp2p-kad-dht` to [v0.34.1-0.20250918150625-2e3cea182b63](https://github.com/libp2p/go-libp2p-kad-dht/commit/2e3cea182b63)
109
+- update `ipfs-webui` to [v4.9.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.9.0)
110
+
111
### 📝 Changelog
112
113
<details><summary>Full Changelog</summary>
test/cli/webui_test.go
new
+88
@@ -0,0 +1,88 @@
1
+package cli
2
+
3
+import (
4
+ "net/http"
5
+ "testing"
6
+
7
+ "github.com/ipfs/kubo/config"
8
+ "github.com/ipfs/kubo/test/cli/harness"
9
+ "github.com/stretchr/testify/assert"
10
+)
11
+
12
+func TestWebUI(t *testing.T) {
13
+ t.Parallel()
14
+
15
+ t.Run("NoFetch=true shows not available error", func(t *testing.T) {
16
+ t.Parallel()
17
+ node := harness.NewT(t).NewNode().Init()
18
+
19
+ node.UpdateConfig(func(cfg *config.Config) {
20
+ cfg.Gateway.NoFetch = true
21
+ })
22
+
23
+ node.StartDaemon("--offline")
24
+
25
+ apiClient := node.APIClient()
26
+ resp := apiClient.Get("/webui/")
27
+
28
+ // Should return 503 Service Unavailable when WebUI is not in local store
29
+ assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
30
+
31
+ // Check response contains helpful information
32
+ body := resp.Body
33
+ assert.Contains(t, body, "IPFS WebUI Not Available")
34
+ assert.Contains(t, body, "Gateway.NoFetch=true")
35
+ assert.Contains(t, body, "ipfs pin add")
36
+ assert.Contains(t, body, "ipfs dag import")
37
+ assert.Contains(t, body, "https://github.com/ipfs/ipfs-webui/releases")
38
+ })
39
+
40
+ t.Run("DeserializedResponses=false shows incompatible error", func(t *testing.T) {
41
+ t.Parallel()
42
+ node := harness.NewT(t).NewNode().Init()
43
+
44
+ node.UpdateConfig(func(cfg *config.Config) {
45
+ cfg.Gateway.DeserializedResponses = config.False
46
+ })
47
+
48
+ node.StartDaemon()
49
+
50
+ apiClient := node.APIClient()
51
+ resp := apiClient.Get("/webui/")
52
+
53
+ // Should return 503 Service Unavailable
54
+ assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
55
+
56
+ // Check response contains incompatibility message
57
+ body := resp.Body
58
+ assert.Contains(t, body, "IPFS WebUI Incompatible")
59
+ assert.Contains(t, body, "Gateway.DeserializedResponses=false")
60
+ assert.Contains(t, body, "WebUI requires deserializing IPFS responses")
61
+ assert.Contains(t, body, "Gateway.DeserializedResponses=true")
62
+ })
63
+
64
+ t.Run("Both NoFetch=true and DeserializedResponses=false shows incompatible error", func(t *testing.T) {
65
+ t.Parallel()
66
+ node := harness.NewT(t).NewNode().Init()
67
+
68
+ node.UpdateConfig(func(cfg *config.Config) {
69
+ cfg.Gateway.NoFetch = true
70
+ cfg.Gateway.DeserializedResponses = config.False
71
+ })
72
+
73
+ node.StartDaemon("--offline")
74
+
75
+ apiClient := node.APIClient()
76
+ resp := apiClient.Get("/webui/")
77
+
78
+ // Should return 503 Service Unavailable
79
+ assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
80
+
81
+ // DeserializedResponses=false takes priority
82
+ body := resp.Body
83
+ assert.Contains(t, body, "IPFS WebUI Incompatible")
84
+ assert.Contains(t, body, "Gateway.DeserializedResponses=false")
85
+ // Should NOT mention NoFetch since DeserializedResponses check comes first
86
+ assert.NotContains(t, body, "NoFetch")
87
+ })
88
+}