v1.27.0-a #10
@ -445,7 +445,7 @@ func GetFullNodeAPIV1LotusProvider(ctx *cli.Context, ainfoCfg []string, opts ...
|
|||||||
for _, head := range heads {
|
for _, head := range heads {
|
||||||
v1api, closer, err := client.NewFullNodeRPCV1(ctx.Context, head.addr, head.header, rpcOpts...)
|
v1api, closer, err := client.NewFullNodeRPCV1(ctx.Context, head.addr, head.header, rpcOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Not able to establish connection to node with addr: %s", head.addr)
|
log.Warnf("Not able to establish connection to node with addr: %s, Reason: %s", head.addr, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fullNodes = append(fullNodes, v1api)
|
fullNodes = append(fullNodes, v1api)
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
||||||
|
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
|
||||||
"github.com/filecoin-project/lotus/node/config"
|
"github.com/filecoin-project/lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,9 +109,8 @@ var configSetCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
_ = lp
|
_ = lp
|
||||||
|
|
||||||
_, err = db.Exec(context.Background(),
|
err = setConfig(db, name, string(bytes))
|
||||||
`INSERT INTO harmony_config (title, config) VALUES ($1, $2)
|
|
||||||
ON CONFLICT (title) DO UPDATE SET config = excluded.config`, name, string(bytes))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to save config layer: %w", err)
|
return fmt.Errorf("unable to save config layer: %w", err)
|
||||||
}
|
}
|
||||||
@ -120,6 +120,13 @@ var configSetCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setConfig(db *harmonydb.DB, name, config string) error {
|
||||||
|
_, err := db.Exec(context.Background(),
|
||||||
|
`INSERT INTO harmony_config (title, config) VALUES ($1, $2)
|
||||||
|
ON CONFLICT (title) DO UPDATE SET config = excluded.config`, name, config)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var configGetCmd = &cli.Command{
|
var configGetCmd = &cli.Command{
|
||||||
Name: "get",
|
Name: "get",
|
||||||
Aliases: []string{"cat", "show"},
|
Aliases: []string{"cat", "show"},
|
||||||
@ -135,8 +142,7 @@ var configGetCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg string
|
cfg, err := getConfig(db, args.First())
|
||||||
err = db.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title=$1`, args.First()).Scan(&cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -146,6 +152,15 @@ var configGetCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getConfig(db *harmonydb.DB, layer string) (string, error) {
|
||||||
|
var cfg string
|
||||||
|
err := db.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
var configListCmd = &cli.Command{
|
var configListCmd = &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
@ -176,7 +177,11 @@ func (deps *Deps) PopulateRemainingDeps(ctx context.Context, cctx *cli.Context,
|
|||||||
|
|
||||||
if deps.Full == nil {
|
if deps.Full == nil {
|
||||||
var fullCloser func()
|
var fullCloser func()
|
||||||
deps.Full, fullCloser, err = cliutil.GetFullNodeAPIV1LotusProvider(cctx, deps.Cfg.Apis.ChainApiInfo)
|
cfgApiInfo := deps.Cfg.Apis.ChainApiInfo
|
||||||
|
if v := os.Getenv("FULLNODE_API_INFO"); v != "" {
|
||||||
|
cfgApiInfo = []string{v}
|
||||||
|
}
|
||||||
|
deps.Full, fullCloser, err = cliutil.GetFullNodeAPIV1LotusProvider(cctx, cfgApiInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -267,6 +272,7 @@ func GetConfig(cctx *cli.Context, db *harmonydb.DB) (*config.LotusProviderConfig
|
|||||||
for _, k := range meta.Keys() {
|
for _, k := range meta.Keys() {
|
||||||
have = append(have, strings.Join(k, " "))
|
have = append(have, strings.Join(k, " "))
|
||||||
}
|
}
|
||||||
|
log.Infow("Using layer", "layer", layer, "config", lp)
|
||||||
}
|
}
|
||||||
_ = have // FUTURE: verify that required fields are here.
|
_ = have // FUTURE: verify that required fields are here.
|
||||||
// If config includes 3rd-party config, consider JSONSchema as a way that
|
// If config includes 3rd-party config, consider JSONSchema as a way that
|
||||||
|
@ -46,6 +46,7 @@ func main() {
|
|||||||
stopCmd,
|
stopCmd,
|
||||||
configCmd,
|
configCmd,
|
||||||
testCmd,
|
testCmd,
|
||||||
|
webCmd,
|
||||||
//backupCmd,
|
//backupCmd,
|
||||||
//lcli.WithCategory("chain", actorCmd),
|
//lcli.WithCategory("chain", actorCmd),
|
||||||
//lcli.WithCategory("storage", sectorsCmd),
|
//lcli.WithCategory("storage", sectorsCmd),
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
logging "github.com/ipfs/go-log/v2"
|
logging "github.com/ipfs/go-log/v2"
|
||||||
"go.opencensus.io/tag"
|
"go.opencensus.io/tag"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-jsonrpc"
|
"github.com/filecoin-project/go-jsonrpc"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
"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/lib/rpcenc"
|
||||||
"github.com/filecoin-project/lotus/metrics"
|
"github.com/filecoin-project/lotus/metrics"
|
||||||
"github.com/filecoin-project/lotus/metrics/proxy"
|
"github.com/filecoin-project/lotus/metrics/proxy"
|
||||||
@ -126,15 +128,29 @@ func ListenAndServe(ctx context.Context, dependencies *deps.Deps, shutdownChan c
|
|||||||
Addr: dependencies.ListenAddr,
|
Addr: dependencies.ListenAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("Setting up RPC server at %s", dependencies.ListenAddr)
|
||||||
|
eg := errgroup.Group{}
|
||||||
|
eg.Go(srv.ListenAndServe)
|
||||||
|
|
||||||
|
if dependencies.Cfg.Subsystems.EnableWebGui {
|
||||||
|
web, err := web.GetSrv(ctx, dependencies)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
log.Warn("Shutting down...")
|
log.Warn("Shutting down...")
|
||||||
if err := srv.Shutdown(context.TODO()); err != nil {
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||||
log.Errorf("shutting down RPC server failed: %s", err)
|
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.Warn("Graceful shutdown successful")
|
||||||
}()
|
}()
|
||||||
|
log.Infof("Setting up web server at %s", dependencies.Cfg.Subsystems.GuiAddress)
|
||||||
log.Infof("Setting up RPC server at %s", dependencies.ListenAddr)
|
eg.Go(web.ListenAndServe)
|
||||||
return srv.ListenAndServe()
|
}
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.opencensus.io/stats"
|
"go.opencensus.io/stats"
|
||||||
@ -19,6 +22,7 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/lib/ulimit"
|
"github.com/filecoin-project/lotus/lib/ulimit"
|
||||||
"github.com/filecoin-project/lotus/metrics"
|
"github.com/filecoin-project/lotus/metrics"
|
||||||
"github.com/filecoin-project/lotus/node"
|
"github.com/filecoin-project/lotus/node"
|
||||||
|
"github.com/filecoin-project/lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stackTracer interface {
|
type stackTracer interface {
|
||||||
@ -113,10 +117,8 @@ var runCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("before populateRemainingDeps")
|
|
||||||
dependencies := &deps.Deps{}
|
dependencies := &deps.Deps{}
|
||||||
err = dependencies.PopulateRemainingDeps(ctx, cctx, true)
|
err = dependencies.PopulateRemainingDeps(ctx, cctx, true)
|
||||||
fmt.Println("after popdeps")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("err", err)
|
fmt.Println("err", err)
|
||||||
return err
|
return err
|
||||||
@ -142,3 +144,51 @@ var runCmd = &cli.Command{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var webCmd = &cli.Command{
|
||||||
|
Name: "web",
|
||||||
|
Usage: "Start lotus provider web interface",
|
||||||
|
Description: `Start an instance of lotus provider web interface.
|
||||||
|
This creates the 'web' layer if it does not exist, then calls run with that layer.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "listen",
|
||||||
|
Usage: "Address to listen on",
|
||||||
|
Value: "127.0.0.1:4701",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "layers",
|
||||||
|
Usage: "list of layers to be interpreted (atop defaults). Default: base. Web will be added",
|
||||||
|
Value: cli.NewStringSlice("base"),
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "nosync",
|
||||||
|
Usage: "don't check full-node sync status",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
db, err := deps.MakeDB(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
webtxt, err := getConfig(db, "web")
|
||||||
|
if err != nil || webtxt == "" {
|
||||||
|
cfg := config.DefaultLotusProvider()
|
||||||
|
cfg.Subsystems.EnableWebGui = true
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err = toml.NewEncoder(&b).Encode(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = setConfig(db, "web", b.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layers := append([]string{"web"}, cctx.StringSlice("layers")...)
|
||||||
|
err = cctx.Set("layers", strings.Join(layers, ","))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runCmd.Action(cctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
229
cmd/lotus-provider/web/api/debug/debug.go
Normal file
229
cmd/lotus-provider/web/api/debug/debug.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// Package debug provides the API for various debug endpoints in lotus-provider.
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api/client"
|
||||||
|
"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.HandleFunc("/chain-state-sse", 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")
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
type minimalApiInfo struct {
|
||||||
|
Apis struct {
|
||||||
|
ChainApiInfo []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcInfos := map[string]minimalApiInfo{} // config name -> api info
|
||||||
|
confNameToAddr := map[string]string{} // config name -> api address
|
||||||
|
|
||||||
|
err := forEachConfig[minimalApiInfo](d, func(name string, info minimalApiInfo) error {
|
||||||
|
if len(info.Apis.ChainApiInfo) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcInfos[name] = info
|
||||||
|
|
||||||
|
for _, addr := range info.Apis.ChainApiInfo {
|
||||||
|
ai := cliutil.ParseApiInfo(addr)
|
||||||
|
confNameToAddr[name] = ai.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorw("getting api info", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dedup := map[string]bool{} // for dedup by address
|
||||||
|
|
||||||
|
infos := map[string]rpcInfo{} // api address -> rpc info
|
||||||
|
var infosLk sync.Mutex
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, info := range rpcInfos {
|
||||||
|
ai := cliutil.ParseApiInfo(info.Apis.ChainApiInfo[0])
|
||||||
|
if dedup[ai.Addr] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dedup[ai.Addr] = true
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
var clayers []string
|
||||||
|
for layer, a := range confNameToAddr {
|
||||||
|
if a == ai.Addr {
|
||||||
|
clayers = append(clayers, layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myinfo := rpcInfo{
|
||||||
|
Address: ai.Addr,
|
||||||
|
Reachable: false,
|
||||||
|
CLayers: clayers,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
infosLk.Lock()
|
||||||
|
defer infosLk.Unlock()
|
||||||
|
infos[ai.Addr] = myinfo
|
||||||
|
}()
|
||||||
|
da, err := ai.DialArgs("v1")
|
||||||
|
if err != nil {
|
||||||
|
log.Warnw("DialArgs", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah := ai.AuthHeader()
|
||||||
|
|
||||||
|
v1api, closer, err := client.NewFullNodeRPCV1(ctx, da, ah)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Not able to establish connection to node with addr: %s", ai.Addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ver, err := v1api.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnw("Version", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
myinfo = rpcInfo{
|
||||||
|
Address: ai.Addr,
|
||||||
|
CLayers: clayers,
|
||||||
|
Reachable: true,
|
||||||
|
Version: ver.Version,
|
||||||
|
SyncState: syncState,
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
var infoList []rpcInfo
|
||||||
|
for _, i := range infos {
|
||||||
|
infoList = append(infoList, i)
|
||||||
|
}
|
||||||
|
sort.Slice(infoList, func(i, j int) bool {
|
||||||
|
return infoList[i].Address < infoList[j].Address
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "data: ")
|
||||||
|
err = json.NewEncoder(w).Encode(&infoList)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnw("json encode", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n\n")
|
||||||
|
if f, ok := w.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(build.BlockDelaySecs) * time.Second)
|
||||||
|
|
||||||
|
select { // stop running if there is reader.
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func forEachConfig[T any](a *debug, cb func(name string, v T) error) error {
|
||||||
|
confs, err := a.loadConfigs(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tomlStr := range confs { // todo for-each-config
|
||||||
|
var info T
|
||||||
|
if err := toml.Unmarshal([]byte(tomlStr), &info); err != nil {
|
||||||
|
return xerrors.Errorf("unmarshaling %s config: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cb(name, info); err != nil {
|
||||||
|
return xerrors.Errorf("cb: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debug) loadConfigs(ctx context.Context) (map[string]string, error) {
|
||||||
|
//err := db.QueryRow(cctx.Context, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text)
|
||||||
|
|
||||||
|
rows, err := d.DB.Query(ctx, `SELECT title, config FROM harmony_config`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("getting db configs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configs := make(map[string]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var title, config string
|
||||||
|
if err := rows.Scan(&title, &config); err != nil {
|
||||||
|
return nil, xerrors.Errorf("scanning db configs: %w", err)
|
||||||
|
}
|
||||||
|
configs[title] = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, nil
|
||||||
|
}
|
13
cmd/lotus-provider/web/api/routes.go
Normal file
13
cmd/lotus-provider/web/api/routes.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Package api provides the HTTP API for the lotus provider web gui.
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/web/api/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Routes(r *mux.Router, deps *deps.Deps) {
|
||||||
|
debug.Routes(r.PathPrefix("/debug").Subrouter(), deps)
|
||||||
|
}
|
35
cmd/lotus-provider/web/hapi/routes.go
Normal file
35
cmd/lotus-provider/web/hapi/routes.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package hapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed web/*
|
||||||
|
var templateFS embed.FS
|
||||||
|
|
||||||
|
func Routes(r *mux.Router, deps *deps.Deps) error {
|
||||||
|
t, err := template.ParseFS(templateFS, "web/*")
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("parse templates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &app{
|
||||||
|
db: deps.DB,
|
||||||
|
t: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HandleFunc("/simpleinfo/actorsummary", a.actorSummary)
|
||||||
|
r.HandleFunc("/simpleinfo/machines", a.indexMachines)
|
||||||
|
r.HandleFunc("/simpleinfo/tasks", a.indexTasks)
|
||||||
|
r.HandleFunc("/simpleinfo/taskhistory", a.indexTasksHistory)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = logging.Logger("lpweb")
|
187
cmd/lotus-provider/web/hapi/simpleinfo.go
Normal file
187
cmd/lotus-provider/web/hapi/simpleinfo.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package hapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type app struct {
|
||||||
|
db *harmonydb.DB
|
||||||
|
t *template.Template
|
||||||
|
|
||||||
|
actorInfoLk sync.Mutex
|
||||||
|
actorInfos []actorInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type actorInfo struct {
|
||||||
|
Address string
|
||||||
|
CLayers []string
|
||||||
|
|
||||||
|
QualityAdjustedPower string
|
||||||
|
RawBytePower string
|
||||||
|
|
||||||
|
Deadlines []actorDeadline
|
||||||
|
}
|
||||||
|
|
||||||
|
type actorDeadline struct {
|
||||||
|
Empty bool
|
||||||
|
Current bool
|
||||||
|
Proven bool
|
||||||
|
PartFaulty bool
|
||||||
|
Faulty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) actorSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a.actorInfoLk.Lock()
|
||||||
|
defer a.actorInfoLk.Unlock()
|
||||||
|
|
||||||
|
a.executeTemplate(w, "actor_summary", a.actorInfos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) indexMachines(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s, err := a.clusterMachineSummary(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cluster machine summary: %v", err)
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.executeTemplate(w, "cluster_machines", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) indexTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s, err := a.clusterTaskSummary(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cluster task summary: %v", err)
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.executeTemplate(w, "cluster_tasks", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) indexTasksHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s, err := a.clusterTaskHistorySummary(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cluster task history summary: %v", err)
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.executeTemplate(w, "cluster_task_history", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var templateDev = os.Getenv("LOTUS_WEB_DEV") == "1"
|
||||||
|
|
||||||
|
func (a *app) executeTemplate(w http.ResponseWriter, name string, data interface{}) {
|
||||||
|
if templateDev {
|
||||||
|
fs := os.DirFS("./cmd/lotus-provider/web/hapi/web")
|
||||||
|
a.t = template.Must(template.ParseFS(fs, "web/*"))
|
||||||
|
}
|
||||||
|
if err := a.t.ExecuteTemplate(w, name, data); err != nil {
|
||||||
|
log.Errorf("execute template %s: %v", name, err)
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type machineSummary struct {
|
||||||
|
Address string
|
||||||
|
ID int64
|
||||||
|
SinceContact string
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskSummary struct {
|
||||||
|
Name string
|
||||||
|
SincePosted string
|
||||||
|
Owner *string
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskHistorySummary struct {
|
||||||
|
Name string
|
||||||
|
TaskID int64
|
||||||
|
|
||||||
|
Posted, Start, End string
|
||||||
|
|
||||||
|
Result bool
|
||||||
|
Err string
|
||||||
|
|
||||||
|
CompletedBy string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) clusterMachineSummary(ctx context.Context) ([]machineSummary, error) {
|
||||||
|
rows, err := a.db.Query(ctx, "SELECT id, host_and_port, last_contact FROM harmony_machines")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var summaries []machineSummary
|
||||||
|
for rows.Next() {
|
||||||
|
var m machineSummary
|
||||||
|
var lastContact time.Time
|
||||||
|
|
||||||
|
if err := rows.Scan(&m.ID, &m.Address, &lastContact); err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SinceContact = time.Since(lastContact).Round(time.Second).String()
|
||||||
|
|
||||||
|
summaries = append(summaries, m)
|
||||||
|
}
|
||||||
|
return summaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) clusterTaskSummary(ctx context.Context) ([]taskSummary, error) {
|
||||||
|
rows, err := a.db.Query(ctx, "SELECT id, name, update_time, owner_id FROM harmony_task")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var summaries []taskSummary
|
||||||
|
for rows.Next() {
|
||||||
|
var t taskSummary
|
||||||
|
var posted time.Time
|
||||||
|
|
||||||
|
if err := rows.Scan(&t.ID, &t.Name, &posted, &t.Owner); err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
t.SincePosted = time.Since(posted).Round(time.Second).String()
|
||||||
|
|
||||||
|
summaries = append(summaries, t)
|
||||||
|
}
|
||||||
|
return summaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) clusterTaskHistorySummary(ctx context.Context) ([]taskHistorySummary, error) {
|
||||||
|
rows, err := a.db.Query(ctx, "SELECT id, name, task_id, posted, work_start, work_end, result, err, completed_by_host_and_port FROM harmony_task_history ORDER BY work_end DESC LIMIT 15")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var summaries []taskHistorySummary
|
||||||
|
for rows.Next() {
|
||||||
|
var t taskHistorySummary
|
||||||
|
var posted, start, end time.Time
|
||||||
|
|
||||||
|
if err := rows.Scan(&t.TaskID, &t.Name, &t.TaskID, &posted, &start, &end, &t.Result, &t.Err, &t.CompletedBy); err != nil {
|
||||||
|
return nil, err // Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Posted = posted.Round(time.Second).Format("02 Jan 06 15:04")
|
||||||
|
t.Start = start.Round(time.Second).Format("02 Jan 06 15:04")
|
||||||
|
t.End = end.Round(time.Second).Format("02 Jan 06 15:04")
|
||||||
|
|
||||||
|
summaries = append(summaries, t)
|
||||||
|
}
|
||||||
|
return summaries, nil
|
||||||
|
}
|
20
cmd/lotus-provider/web/hapi/web/actor_summary.gohtml
Normal file
20
cmd/lotus-provider/web/hapi/web/actor_summary.gohtml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{{define "actor_summary"}}
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Address}}</td>
|
||||||
|
<td>
|
||||||
|
{{range .CLayers}}
|
||||||
|
<span>{{.}} </span>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>{{.QualityAdjustedPower}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="deadline-box">
|
||||||
|
{{range .Deadlines}}
|
||||||
|
<div class="deadline-entry{{if .Current}} deadline-entry-cur{{end}}{{if .Proven}} deadline-proven{{end}}{{if .PartFaulty}} deadline-partially-faulty{{end}}{{if .Faulty}} deadline-faulty{{end}}"></div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
15
cmd/lotus-provider/web/hapi/web/chain_rpcs.gohtml
Normal file
15
cmd/lotus-provider/web/hapi/web/chain_rpcs.gohtml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{{define "chain_rpcs"}}
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Address}}</td>
|
||||||
|
<td>
|
||||||
|
{{range .CLayers}}
|
||||||
|
<span>{{.}} </span>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>{{if .Reachable}}<span class="success">ok</span>{{else}}<span class="error">FAIL</span>{{end}}</td>
|
||||||
|
<td>{{if eq "ok" .SyncState}}<span class="success">ok</span>{{else}}<span class="warning">{{.SyncState}}</span>{{end}}</td>
|
||||||
|
<td>{{.Version}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
10
cmd/lotus-provider/web/hapi/web/cluster_machines.gohtml
Normal file
10
cmd/lotus-provider/web/hapi/web/cluster_machines.gohtml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{{define "cluster_machines"}}
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Address}}</td>
|
||||||
|
<td>{{.ID}}</td>
|
||||||
|
<td>todo</td>
|
||||||
|
<td>{{.SinceContact}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
14
cmd/lotus-provider/web/hapi/web/cluster_task_history.gohtml
Normal file
14
cmd/lotus-provider/web/hapi/web/cluster_task_history.gohtml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{{define "cluster_task_history"}}
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Name}}</td>
|
||||||
|
<td>{{.TaskID}}</td>
|
||||||
|
<td>{{.CompletedBy}}</td>
|
||||||
|
<td>{{.Posted}}</td>
|
||||||
|
<td>{{.Start}}</td>
|
||||||
|
<td>{{.End}}</td>
|
||||||
|
<td>{{if .Result}}<span class="success">success</span>{{else}}<span class="error">error</span>{{end}}</td>
|
||||||
|
<td>{{.Err}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
10
cmd/lotus-provider/web/hapi/web/cluster_tasks.gohtml
Normal file
10
cmd/lotus-provider/web/hapi/web/cluster_tasks.gohtml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{{define "cluster_tasks"}}
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Name}}</td>
|
||||||
|
<td>{{.ID}}</td>
|
||||||
|
<td>{{.SincePosted}}</td>
|
||||||
|
<td>{{.Owner}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
84
cmd/lotus-provider/web/srv.go
Normal file
84
cmd/lotus-provider/web/srv.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Package web defines the HTTP web server for static files and endpoints.
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.opencensus.io/tag"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/web/api"
|
||||||
|
"github.com/filecoin-project/lotus/cmd/lotus-provider/web/hapi"
|
||||||
|
"github.com/filecoin-project/lotus/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var static embed.FS
|
||||||
|
|
||||||
|
var basePath = "/static/"
|
||||||
|
|
||||||
|
// An dev mode hack for no-restart changes to static and templates.
|
||||||
|
// You still need to recomplie the binary for changes to go code.
|
||||||
|
var webDev = os.Getenv("LOTUS_WEB_DEV") == "1"
|
||||||
|
|
||||||
|
func GetSrv(ctx context.Context, deps *deps.Deps) (*http.Server, error) {
|
||||||
|
mx := mux.NewRouter()
|
||||||
|
err := hapi.Routes(mx.PathPrefix("/hapi").Subrouter(), deps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
api.Routes(mx.PathPrefix("/api").Subrouter(), deps)
|
||||||
|
|
||||||
|
basePath := basePath
|
||||||
|
|
||||||
|
var static fs.FS = static
|
||||||
|
if webDev {
|
||||||
|
basePath = "cmd/lotus-provider/web/static"
|
||||||
|
static = os.DirFS(basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
mx.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If the request is for a directory, redirect to the index file.
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/") {
|
||||||
|
r.URL.Path += "index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := static.Open(path.Join(basePath, r.URL.Path)[1:])
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = w.Write([]byte("404 Not Found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
fileInfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte("500 Internal Server Error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file.(io.ReadSeeker))
|
||||||
|
})
|
||||||
|
|
||||||
|
return &http.Server{
|
||||||
|
Handler: http.HandlerFunc(mx.ServeHTTP),
|
||||||
|
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,
|
||||||
|
ReadTimeout: time.Minute * 3,
|
||||||
|
ReadHeaderTimeout: time.Minute * 3, // lint
|
||||||
|
}, nil
|
||||||
|
}
|
73
cmd/lotus-provider/web/static/chain-connectivity.js
Normal file
73
cmd/lotus-provider/web/static/chain-connectivity.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
|
||||||
|
window.customElements.define('chain-connectivity', class MyElement extends LitElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.data = [];
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
loadData() {
|
||||||
|
const eventSource = new EventSource('/api/debug/chain-state-sse');
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
this.data = JSON.parse(event.data);
|
||||||
|
super.requestUpdate();
|
||||||
|
};
|
||||||
|
eventSource.onerror = (error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [css`
|
||||||
|
:host {
|
||||||
|
box-sizing: border-box; /* Don't forgert this to include padding/border inside width calculation */
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td, table th {
|
||||||
|
border-left: 1px solid #f0f0f0;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr td:first-child, table tr th:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`];
|
||||||
|
}
|
||||||
|
render = () => html`
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>RPC Address</th>
|
||||||
|
<th>Reachability</th>
|
||||||
|
<th>Sync Status</th>
|
||||||
|
<th>Version</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${this.data.map(item => html`
|
||||||
|
<tr>
|
||||||
|
<td>${item.Address}</td>
|
||||||
|
<td>${item.Reachable ? html`<span class="success">ok</span>` : html`<span class="error">FAIL</span>`}</td>
|
||||||
|
<td>${item.SyncState === "ok" ? html`<span class="success">ok</span>` : html`<span class="warning">${item.SyncState}</span>`}</td>
|
||||||
|
<td>${item.Version}</td>
|
||||||
|
</tr>
|
||||||
|
`)}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">Data incoming...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>`
|
||||||
|
});
|
193
cmd/lotus-provider/web/static/index.html
Normal file
193
cmd/lotus-provider/web/static/index.html
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lotus Provider Cluster Overview</title>
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script>
|
||||||
|
<script type="module" src="chain-connectivity.js"></script>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
background: #0f0f0f;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td, table th {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-head {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.head-left {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.head-right {
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td, table th {
|
||||||
|
border-left: 1px solid #f0f0f0;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr td:first-child, table tr th:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #dfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #af7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-tile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: #3f3f3f;
|
||||||
|
|
||||||
|
& b {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: deeppink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deadline-box {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(16, auto);
|
||||||
|
grid-template-rows: repeat(3, auto);
|
||||||
|
grid-gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deadline-entry {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: grey;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deadline-entry-cur {
|
||||||
|
border-bottom: 3px solid deepskyblue;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deadline-proven {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
.deadline-partially-faulty {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
.deadline-faulty {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-head">
|
||||||
|
<div class="head-left">
|
||||||
|
<h1>Lotus Provider Cluster</h1>
|
||||||
|
</div>
|
||||||
|
<div class="head-right">
|
||||||
|
version [todo]
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div class="page">
|
||||||
|
<div class="info-block">
|
||||||
|
<h2>Chain Connectivity</h2>
|
||||||
|
<chain-connectivity></chain-connectivity>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="info-block">
|
||||||
|
<h2>Actor Summary</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Config Layers</th>
|
||||||
|
<th>QaP</th>
|
||||||
|
<th>Deadlines</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody hx-get="/hapi/simpleinfo/actorsummary" hx-trigger="load,every 5s">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="info-block">
|
||||||
|
<h2>Cluster Machines</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Host</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Config Layers</th>
|
||||||
|
<th>Last Contact</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody hx-get="/hapi/simpleinfo/machines" hx-trigger="load,every 5s">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="info-block">
|
||||||
|
<h2>Recently Finished Tasks</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Executor</th>
|
||||||
|
<th>Posted</th>
|
||||||
|
<th>Start</th>
|
||||||
|
<th>End</th>
|
||||||
|
<th>Outcome</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody hx-get="/hapi/simpleinfo/taskhistory" hx-trigger="load, every 5s">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="info-block">
|
||||||
|
<h2>Cluster Tasks</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Task</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Posted</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody hx-get="/hapi/simpleinfo/tasks" hx-trigger="load,every 5s">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -14,6 +14,7 @@ COMMANDS:
|
|||||||
stop Stop a running lotus provider
|
stop Stop a running lotus provider
|
||||||
config Manage node config by layers. The layer 'base' will always be applied.
|
config Manage node config by layers. The layer 'base' will always be applied.
|
||||||
test Utility functions for testing
|
test Utility functions for testing
|
||||||
|
web Start lotus provider web interface
|
||||||
version Print version
|
version Print version
|
||||||
help, h Shows a list of commands or help for one command
|
help, h Shows a list of commands or help for one command
|
||||||
DEVELOPER:
|
DEVELOPER:
|
||||||
@ -247,6 +248,25 @@ OPTIONS:
|
|||||||
--help, -h show help
|
--help, -h show help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## lotus-provider web
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
lotus-provider web - Start lotus provider web interface
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
lotus-provider web [command options] [arguments...]
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
Start an instance of lotus provider web interface.
|
||||||
|
This creates the 'web' layer if it does not exist, then calls run with that layer.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--listen value Address to listen on (default: "127.0.0.1:4701")
|
||||||
|
--layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base. Web will be added (default: "base")
|
||||||
|
--nosync don't check full-node sync status (default: false)
|
||||||
|
--help, -h show help
|
||||||
|
```
|
||||||
|
|
||||||
## lotus-provider version
|
## lotus-provider version
|
||||||
```
|
```
|
||||||
NAME:
|
NAME:
|
||||||
|
@ -11,6 +11,14 @@
|
|||||||
# type: int
|
# type: int
|
||||||
#WinningPostMaxTasks = 0
|
#WinningPostMaxTasks = 0
|
||||||
|
|
||||||
|
# type: bool
|
||||||
|
#EnableWebGui = false
|
||||||
|
|
||||||
|
# The address that should listen for Web GUI requests.
|
||||||
|
#
|
||||||
|
# type: string
|
||||||
|
#GuiAddress = ":4701"
|
||||||
|
|
||||||
|
|
||||||
[Fees]
|
[Fees]
|
||||||
# type: types.FIL
|
# type: types.FIL
|
||||||
|
@ -351,7 +351,9 @@ func DefaultUserRaftConfig() *UserRaftConfig {
|
|||||||
|
|
||||||
func DefaultLotusProvider() *LotusProviderConfig {
|
func DefaultLotusProvider() *LotusProviderConfig {
|
||||||
return &LotusProviderConfig{
|
return &LotusProviderConfig{
|
||||||
Subsystems: ProviderSubsystemsConfig{},
|
Subsystems: ProviderSubsystemsConfig{
|
||||||
|
GuiAddress: ":4701",
|
||||||
|
},
|
||||||
Fees: LotusProviderFees{
|
Fees: LotusProviderFees{
|
||||||
DefaultMaxFee: DefaultDefaultMaxFee,
|
DefaultMaxFee: DefaultDefaultMaxFee,
|
||||||
MaxPreCommitGasFee: types.MustParseFIL("0.025"),
|
MaxPreCommitGasFee: types.MustParseFIL("0.025"),
|
||||||
|
@ -1013,6 +1013,18 @@ block rewards will be missed!`,
|
|||||||
|
|
||||||
Comment: ``,
|
Comment: ``,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "EnableWebGui",
|
||||||
|
Type: "bool",
|
||||||
|
|
||||||
|
Comment: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "GuiAddress",
|
||||||
|
Type: "string",
|
||||||
|
|
||||||
|
Comment: `The address that should listen for Web GUI requests.`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"ProvingConfig": {
|
"ProvingConfig": {
|
||||||
{
|
{
|
||||||
|
@ -96,6 +96,10 @@ type ProviderSubsystemsConfig struct {
|
|||||||
WindowPostMaxTasks int
|
WindowPostMaxTasks int
|
||||||
EnableWinningPost bool
|
EnableWinningPost bool
|
||||||
WinningPostMaxTasks int
|
WinningPostMaxTasks int
|
||||||
|
|
||||||
|
EnableWebGui bool
|
||||||
|
// The address that should listen for Web GUI requests.
|
||||||
|
GuiAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DAGStoreConfig struct {
|
type DAGStoreConfig struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user