mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
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:
parent
fa17b69c7d
commit
90740dca21
@ -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)
|
||||
}
|
||||
|
||||
@ -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 |
|
||||
|:---:|:---:|:---:|
|
||||
|  |  |  |
|
||||
| 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 |
|
||||
|:---:|:---:|
|
||||
|  |  |
|
||||
| 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
88
test/cli/webui_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user