@cryptotaxi247 / kubo / commits / 90740dca2

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 +| ![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) |
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 +| ![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) |
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 +}