diff --git a/cmd/lotus-provider/rpc/rpc.go b/cmd/lotus-provider/rpc/rpc.go index 1f075f79a..4b4a77cf9 100644 --- a/cmd/lotus-provider/rpc/rpc.go +++ b/cmd/lotus-provider/rpc/rpc.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" logging "github.com/ipfs/go-log/v2" "go.opencensus.io/tag" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc" @@ -20,6 +21,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/cmd/lotus-provider/deps" + "github.com/filecoin-project/lotus/cmd/lotus-provider/web" "github.com/filecoin-project/lotus/lib/rpcenc" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/metrics/proxy" @@ -126,15 +128,27 @@ func ListenAndServe(ctx context.Context, dependencies *deps.Deps, shutdownChan c Addr: dependencies.ListenAddr, } + log.Infof("Setting up RPC server at %s", dependencies.ListenAddr) + + web, err := web.GetSrv(ctx, dependencies) + if err != nil { + return err + } + go func() { <-ctx.Done() log.Warn("Shutting down...") if err := srv.Shutdown(context.TODO()); err != nil { log.Errorf("shutting down RPC server failed: %s", err) } + if err := web.Shutdown(context.Background()); err != nil { + log.Errorf("shutting down web server failed: %s", err) + } log.Warn("Graceful shutdown successful") }() - log.Infof("Setting up RPC server at %s", dependencies.ListenAddr) - return srv.ListenAndServe() + eg := errgroup.Group{} + eg.Go(srv.ListenAndServe) + eg.Go(web.ListenAndServe) + return eg.Wait() } diff --git a/cmd/lotus-provider/web/api/debug/debug.go b/cmd/lotus-provider/web/api/debug/debug.go new file mode 100644 index 000000000..1dcd7c5a3 --- /dev/null +++ b/cmd/lotus-provider/web/api/debug/debug.go @@ -0,0 +1,95 @@ +// Package debug provides the API for various debug endpoints in lotus-provider. +package debug + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/gorilla/mux" + logging "github.com/ipfs/go-log/v2" + + "github.com/filecoin-project/lotus/build" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/cmd/lotus-provider/deps" +) + +var log = logging.Logger("lp/web/debug") + +type debug struct { + *deps.Deps +} + +func Routes(r *mux.Router, deps *deps.Deps) { + d := debug{deps} + r.Methods("GET").Path("chain-state-sse").HandlerFunc(d.chainStateSSE) +} + +type rpcInfo struct { + Address string + CLayers []string + Reachable bool + SyncState string + Version string +} + +func (d *debug) chainStateSSE(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + v1api := d.Deps.Full + ctx := r.Context() + + ai := cliutil.ParseApiInfo(d.Deps.Cfg.Apis.ChainApiInfo[0]) + ver, err := v1api.Version(ctx) + if err != nil { + log.Warnw("Version", "error", err) + return + } + +sse: + for { + head, err := v1api.ChainHead(ctx) + if err != nil { + log.Warnw("ChainHead", "error", err) + return + } + + var syncState string + switch { + case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*3/2): // within 1.5 epochs + syncState = "ok" + case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*5): // within 5 epochs + syncState = fmt.Sprintf("slow (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)) + default: + syncState = fmt.Sprintf("behind (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)) + } + + select { + case <-ctx.Done(): + break sse + default: + } + + fmt.Fprintf(w, "data: ") + err = json.NewEncoder(w).Encode(rpcInfo{ + Address: ai.Addr, + CLayers: []string{}, + Reachable: true, + Version: ver.Version, + SyncState: syncState, + }) + if err != nil { + log.Warnw("json encode", "error", err) + return + } + fmt.Fprintf(w, "\n\n") + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + } +} diff --git a/cmd/lotus-provider/web/api/routes.go b/cmd/lotus-provider/web/api/routes.go new file mode 100644 index 000000000..91e317722 --- /dev/null +++ b/cmd/lotus-provider/web/api/routes.go @@ -0,0 +1,12 @@ +// Package api provides the HTTP API for the lotus provider web gui. +package api + +import ( + "github.com/filecoin-project/lotus/cmd/lotus-provider/deps" + "github.com/filecoin-project/lotus/cmd/lotus-provider/web/api/debug" + "github.com/gorilla/mux" +) + +func Routes(r *mux.Router, deps *deps.Deps) { + debug.Routes(r.PathPrefix("/debug").Subrouter(), deps) +} diff --git a/cmd/lotus-provider/web/srv.go b/cmd/lotus-provider/web/srv.go new file mode 100644 index 000000000..f59a8d837 --- /dev/null +++ b/cmd/lotus-provider/web/srv.go @@ -0,0 +1,40 @@ +// Package web defines the HTTP web server for static files and endpoints. +package web + +import ( + "context" + "embed" + "net" + "net/http" + "strings" + + "github.com/filecoin-project/lotus/cmd/lotus-provider/deps" + "github.com/filecoin-project/lotus/cmd/lotus-provider/web/api" + "github.com/filecoin-project/lotus/metrics" + "github.com/gorilla/mux" + "go.opencensus.io/tag" +) + +// go:embed static +var static embed.FS + +func GetSrv(ctx context.Context, deps *deps.Deps) (*http.Server, error) { + mux := mux.NewRouter() + api.Routes(mux.PathPrefix("/api").Subrouter(), deps) + mux.NotFoundHandler = http.FileServer(http.FS(static)) + + return &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/") { + r.URL.Path = r.URL.Path + "index.html" + return + } + mux.ServeHTTP(w, r) + }), + BaseContext: func(listener net.Listener) context.Context { + ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "lotus-provider")) + return ctx + }, + Addr: deps.Cfg.Subsystems.GuiAddress, + }, nil +} diff --git a/cmd/lotus-provider/web/static/index.html b/cmd/lotus-provider/web/static/index.html new file mode 100644 index 000000000..731ee799b --- /dev/null +++ b/cmd/lotus-provider/web/static/index.html @@ -0,0 +1,181 @@ + + +
+Address | +Config Layers | +QaP | +Deadlines | +
---|---|---|---|
f01234 | +mig0 | +23TiB | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
RPC Address | +Reachability | +Sync Status | +Version | +|
---|---|---|---|---|
{{.Address}} | +${item.Address} | +${item.Reachable ? html`ok` : html`FAIL`} | +${item.SyncState === "ok" ? html`ok` : html`${item.SyncState}`} | +${item.Version} | +
Data incoming... | +