v1.27.0-a #10
@ -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()
|
||||
}
|
||||
|
95
cmd/lotus-provider/web/api/debug/debug.go
Normal file
95
cmd/lotus-provider/web/api/debug/debug.go
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
12
cmd/lotus-provider/web/api/routes.go
Normal file
12
cmd/lotus-provider/web/api/routes.go
Normal file
@ -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)
|
||||
}
|
40
cmd/lotus-provider/web/srv.go
Normal file
40
cmd/lotus-provider/web/srv.go
Normal file
@ -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
|
||||
}
|
181
cmd/lotus-provider/web/static/index.html
Normal file
181
cmd/lotus-provider/web/static/index.html
Normal file
@ -0,0 +1,181 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Lotus Provider Cluster Overview</title>
|
||||
<script module src="chain-connectivity.js"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
background: #0f0f0f;
|
||||
color: #ffffff;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.app-head {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.head-left {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.head-right {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
a:link {
|
||||
color: #cfc;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #dfa;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #af7;
|
||||
}
|
||||
|
||||
.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>
|
||||
</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>
|
||||
<tr>
|
||||
<td>f01234</td>
|
||||
<td>mig0</td>
|
||||
<td>23TiB</td>
|
||||
<td>
|
||||
<div class="deadline-box">
|
||||
<div class="deadline-entry deadline-proven"></div>
|
||||
<div class="deadline-entry deadline-partially-faulty"></div>
|
||||
<div class="deadline-entry deadline-faulty"></div>
|
||||
<div class="deadline-entry deadline-proven"></div>
|
||||
<div class="deadline-entry deadline-proven"></div>
|
||||
<div class="deadline-entry deadline-entry-cur"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
<div class="deadline-entry"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
73
cmd/lotus-provider/web/static/modules/chain-connectivity.js
Normal file
73
cmd/lotus-provider/web/static/modules/chain-connectivity.js
Normal file
@ -0,0 +1,73 @@
|
||||
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/npm/lit-html@3.1.0/lit-html.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.push(JSON.parse(event.data));
|
||||
};
|
||||
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>{{.Address}}</td>
|
||||
<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>`
|
||||
});
|
@ -351,7 +351,9 @@ func DefaultUserRaftConfig() *UserRaftConfig {
|
||||
|
||||
func DefaultLotusProvider() *LotusProviderConfig {
|
||||
return &LotusProviderConfig{
|
||||
Subsystems: ProviderSubsystemsConfig{},
|
||||
Subsystems: ProviderSubsystemsConfig{
|
||||
GuiAddress: ":4701",
|
||||
},
|
||||
Fees: LotusProviderFees{
|
||||
DefaultMaxFee: DefaultDefaultMaxFee,
|
||||
MaxPreCommitGasFee: types.MustParseFIL("0.025"),
|
||||
|
@ -96,6 +96,9 @@ type ProviderSubsystemsConfig struct {
|
||||
WindowPostMaxTasks int
|
||||
EnableWinningPost bool
|
||||
WinningPostMaxTasks int
|
||||
|
||||
// The address that should listen for Web GUI requests.
|
||||
GuiAddress string
|
||||
}
|
||||
|
||||
type DAGStoreConfig struct {
|
||||
|
Loading…
Reference in New Issue
Block a user