From 90740dca21641992f1258f27a0a289dcefc784ea Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 19 Sep 2025 04:16:21 +0200 Subject: [PATCH] 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 --- core/corehttp/webui.go | 121 ++++++++++++++++++++++++++++++++++----- docs/changelogs/v0.38.md | 23 +++++++- test/cli/webui_test.go | 88 ++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 test/cli/webui_test.go diff --git a/core/corehttp/webui.go b/core/corehttp/webui.go index 9c3244ad2..717c8ae1a 100644 --- a/core/corehttp/webui.go +++ b/core/corehttp/webui.go @@ -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) +} diff --git a/docs/changelogs/v0.38.md b/docs/changelogs/v0.38.md index ccf60730d..1fd774f04 100644 --- a/docs/changelogs/v0.38.md +++ b/docs/changelogs/v0.38.md @@ -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 --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
Full Changelog diff --git a/test/cli/webui_test.go b/test/cli/webui_test.go new file mode 100644 index 000000000..93b8fe4cc --- /dev/null +++ b/test/cli/webui_test.go @@ -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") + }) +}