Add application status page to explorer
This commit is contained in:
parent
70bdd6c8fb
commit
f80c4d572e
23
api/types.go
23
api/types.go
@ -4,8 +4,11 @@ import (
|
|||||||
"blockbook/bchain"
|
"blockbook/bchain"
|
||||||
"blockbook/db"
|
"blockbook/db"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const BlockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
|
||||||
|
|
||||||
type ApiError struct {
|
type ApiError struct {
|
||||||
Text string
|
Text string
|
||||||
Public bool
|
Public bool
|
||||||
@ -100,3 +103,23 @@ type Blocks struct {
|
|||||||
Paging
|
Paging
|
||||||
Blocks []db.BlockInfo `json:"blocks"`
|
Blocks []db.BlockInfo `json:"blocks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BlockbookInfo struct {
|
||||||
|
Coin string `json:"coin"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
GitCommit string `json:"gitcommit"`
|
||||||
|
BuildTime string `json:"buildtime"`
|
||||||
|
InSync bool `json:"inSync"`
|
||||||
|
BestHeight uint32 `json:"bestHeight"`
|
||||||
|
LastBlockTime time.Time `json:"lastBlockTime"`
|
||||||
|
InSyncMempool bool `json:"inSyncMempool"`
|
||||||
|
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||||
|
DbSize int64 `json:"dbSize"`
|
||||||
|
About string `json:"about"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemInfo struct {
|
||||||
|
Blockbook *BlockbookInfo `json:"blockbook"`
|
||||||
|
Backend *bchain.ChainInfo `json:"backend"`
|
||||||
|
}
|
||||||
|
|||||||
@ -465,3 +465,31 @@ func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
|
|||||||
glog.Info("GetBlocks page ", page, " finished in ", time.Since(start))
|
glog.Info("GetBlocks page ", page, " finished in ", time.Since(start))
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSystemInfo returns information about system
|
||||||
|
func (w *Worker) GetSystemInfo() (*SystemInfo, error) {
|
||||||
|
start := time.Now()
|
||||||
|
ci, err := w.chain.GetChainInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "GetChainInfo")
|
||||||
|
}
|
||||||
|
vi := common.GetVersionInfo()
|
||||||
|
ss, bh, st := w.is.GetSyncState()
|
||||||
|
ms, mt, _ := w.is.GetMempoolSyncState()
|
||||||
|
bi := &BlockbookInfo{
|
||||||
|
Coin: w.is.Coin,
|
||||||
|
Host: w.is.Host,
|
||||||
|
Version: vi.Version,
|
||||||
|
GitCommit: vi.GitCommit,
|
||||||
|
BuildTime: vi.BuildTime,
|
||||||
|
InSync: ss,
|
||||||
|
BestHeight: bh,
|
||||||
|
LastBlockTime: st,
|
||||||
|
InSyncMempool: ms,
|
||||||
|
LastMempoolTime: mt,
|
||||||
|
About: BlockbookAbout,
|
||||||
|
DbSize: w.db.DatabaseSizeOnDisk(),
|
||||||
|
}
|
||||||
|
glog.Info("GetSystemInfo finished in ", time.Since(start))
|
||||||
|
return &SystemInfo{bi, ci}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
|
|
||||||
const txsOnPage = 25
|
const txsOnPage = 25
|
||||||
const blocksOnPage = 50
|
const blocksOnPage = 50
|
||||||
const txsInAPI = 1000
|
const txsInAPI = 1000
|
||||||
@ -95,10 +94,11 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
|
|||||||
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
|
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
|
||||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
||||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
||||||
|
serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex))
|
||||||
// handle socket.io
|
// handle socket.io
|
||||||
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
||||||
// default handler
|
// default handler
|
||||||
serveMux.HandleFunc(path, s.index)
|
serveMux.HandleFunc(path, s.htmlTemplateHandler(s.explorerIndex))
|
||||||
|
|
||||||
s.templates = parseTemplates()
|
s.templates = parseTemplates()
|
||||||
|
|
||||||
@ -277,6 +277,7 @@ type tpl int
|
|||||||
const (
|
const (
|
||||||
noTpl = tpl(iota)
|
noTpl = tpl(iota)
|
||||||
errorTpl
|
errorTpl
|
||||||
|
indexTpl
|
||||||
txTpl
|
txTpl
|
||||||
addressTpl
|
addressTpl
|
||||||
blocksTpl
|
blocksTpl
|
||||||
@ -292,6 +293,7 @@ type TemplateData struct {
|
|||||||
Tx *api.Tx
|
Tx *api.Tx
|
||||||
Error *api.ApiError
|
Error *api.ApiError
|
||||||
Blocks *api.Blocks
|
Blocks *api.Blocks
|
||||||
|
Info *api.SystemInfo
|
||||||
Page int
|
Page int
|
||||||
PrevPage int
|
PrevPage int
|
||||||
NextPage int
|
NextPage int
|
||||||
@ -300,6 +302,7 @@ type TemplateData struct {
|
|||||||
|
|
||||||
func parseTemplates() []*template.Template {
|
func parseTemplates() []*template.Template {
|
||||||
templateFuncMap := template.FuncMap{
|
templateFuncMap := template.FuncMap{
|
||||||
|
"formatTime": formatTime,
|
||||||
"formatUnixTime": formatUnixTime,
|
"formatUnixTime": formatUnixTime,
|
||||||
"formatAmount": formatAmount,
|
"formatAmount": formatAmount,
|
||||||
"setTxToTemplateData": setTxToTemplateData,
|
"setTxToTemplateData": setTxToTemplateData,
|
||||||
@ -307,6 +310,7 @@ func parseTemplates() []*template.Template {
|
|||||||
}
|
}
|
||||||
t := make([]*template.Template, tplCount)
|
t := make([]*template.Template, tplCount)
|
||||||
t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
|
t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
|
||||||
|
t[indexTpl] = template.Must(template.New("index").Funcs(templateFuncMap).ParseFiles("./static/templates/index.html", "./static/templates/base.html"))
|
||||||
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||||
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
|
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
|
||||||
t[blocksTpl] = template.Must(template.New("blocks").Funcs(templateFuncMap).ParseFiles("./static/templates/blocks.html", "./static/templates/paging.html", "./static/templates/base.html"))
|
t[blocksTpl] = template.Must(template.New("blocks").Funcs(templateFuncMap).ParseFiles("./static/templates/blocks.html", "./static/templates/paging.html", "./static/templates/base.html"))
|
||||||
@ -314,7 +318,11 @@ func parseTemplates() []*template.Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func formatUnixTime(ut int64) string {
|
func formatUnixTime(ut int64) string {
|
||||||
return time.Unix(ut, 0).Format(time.RFC1123)
|
return formatTime(time.Unix(ut, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTime(t time.Time) string {
|
||||||
|
return t.Format(time.RFC1123)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now return the string as it is
|
// for now return the string as it is
|
||||||
@ -388,6 +396,19 @@ func (s *PublicServer) explorerBlocks(w http.ResponseWriter, r *http.Request) (t
|
|||||||
return blocksTpl, data, nil
|
return blocksTpl, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) explorerIndex(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
|
var si *api.SystemInfo
|
||||||
|
var err error
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "index"}).Inc()
|
||||||
|
si, err = s.api.GetSystemInfo()
|
||||||
|
if err != nil {
|
||||||
|
return errorTpl, nil, err
|
||||||
|
}
|
||||||
|
data := s.newTemplateData()
|
||||||
|
data.Info = si
|
||||||
|
return indexTpl, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
q := strings.TrimSpace(r.URL.Query().Get("q"))
|
q := strings.TrimSpace(r.URL.Query().Get("q"))
|
||||||
var tx *api.Tx
|
var tx *api.Tx
|
||||||
@ -471,50 +492,14 @@ func getPagingRange(page int, total int) ([]int, int, int) {
|
|||||||
return r, pp, np
|
return r, pp, np
|
||||||
}
|
}
|
||||||
|
|
||||||
type resAboutBlockbookPublic struct {
|
func (s *PublicServer) apiIndex(r *http.Request) (interface{}, error) {
|
||||||
Coin string `json:"coin"`
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-index"}).Inc()
|
||||||
Host string `json:"host"`
|
return s.api.GetSystemInfo()
|
||||||
Version string `json:"version"`
|
|
||||||
GitCommit string `json:"gitcommit"`
|
|
||||||
BuildTime string `json:"buildtime"`
|
|
||||||
InSync bool `json:"inSync"`
|
|
||||||
BestHeight uint32 `json:"bestHeight"`
|
|
||||||
LastBlockTime time.Time `json:"lastBlockTime"`
|
|
||||||
InSyncMempool bool `json:"inSyncMempool"`
|
|
||||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
|
||||||
About string `json:"about"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - this is temporary, return html status page
|
|
||||||
func (s *PublicServer) index(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vi := common.GetVersionInfo()
|
|
||||||
ss, bh, st := s.is.GetSyncState()
|
|
||||||
ms, mt, _ := s.is.GetMempoolSyncState()
|
|
||||||
a := resAboutBlockbookPublic{
|
|
||||||
Coin: s.is.Coin,
|
|
||||||
Host: s.is.Host,
|
|
||||||
Version: vi.Version,
|
|
||||||
GitCommit: vi.GitCommit,
|
|
||||||
BuildTime: vi.BuildTime,
|
|
||||||
InSync: ss,
|
|
||||||
BestHeight: bh,
|
|
||||||
LastBlockTime: st,
|
|
||||||
InSyncMempool: ms,
|
|
||||||
LastMempoolTime: mt,
|
|
||||||
About: blockbookAbout,
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
buf, err := json.MarshalIndent(a, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
}
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
||||||
type resBlockIndex struct {
|
type resBlockIndex struct {
|
||||||
BlockHash string `json:"blockHash"`
|
BlockHash string `json:"blockHash"`
|
||||||
About string `json:"about"`
|
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var hash string
|
var hash string
|
||||||
@ -535,7 +520,6 @@ func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
return resBlockIndex{
|
return resBlockIndex{
|
||||||
BlockHash: hash,
|
BlockHash: hash,
|
||||||
About: blockbookAbout,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -551,7 +551,7 @@ func (s *SocketIoServer) getInfo() (res resultGetInfo, err error) {
|
|||||||
res.Result.Network = s.chain.GetNetworkName()
|
res.Result.Network = s.chain.GetNetworkName()
|
||||||
res.Result.Subversion = s.chain.GetSubversion()
|
res.Result.Subversion = s.chain.GetSubversion()
|
||||||
res.Result.CoinName = s.chain.GetCoinName()
|
res.Result.CoinName = s.chain.GetCoinName()
|
||||||
res.Result.About = blockbookAbout
|
res.Result.About = api.BlockbookAbout
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
94
static/templates/index.html
Normal file
94
static/templates/index.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$bb := .Info.Blockbook}}{{$be := .Info.Backend}}
|
||||||
|
<h1>Application status</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Blockbook</h3>
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 33%;">Coin</td>
|
||||||
|
<td class="data">{{$bb.Coin}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Host</td>
|
||||||
|
<td class="data">{{$bb.Host}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Version / Commit / Build</td>
|
||||||
|
<td class="data">{{$bb.Version}} / <a href="https://github.com/trezor/blockbook/commit/{{$bb.GitCommit}}" target="_blank" rel="noopener noreferrer">{{$bb.GitCommit}}</a> / {{$bb.BuildTime}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Synchronized</td>
|
||||||
|
<td class="data{{if not $bb.InSync}} text-danger{{end}}">{{$bb.InSync}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Block</td>
|
||||||
|
<td class="data"><a href="/explorer/block/{{$bb.BestHeight}}">{{$bb.BestHeight}}</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Block Update</td>
|
||||||
|
<td class="data">{{formatTime $bb.LastBlockTime}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mempool in Sync</td>
|
||||||
|
<td class="data{{if not $bb.InSyncMempool}} text-danger{{end}}">{{$bb.InSyncMempool}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Mempool Update</td>
|
||||||
|
<td class="data">{{formatTime $bb.LastMempoolTime}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Size On Disk</td>
|
||||||
|
<td class="data">{{$bb.DbSize}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Backend</h3>
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%;">Chain</td>
|
||||||
|
<td class="data">{{$be.Chain}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Version</td>
|
||||||
|
<td class="data">{{$be.Version}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Subversion</td>
|
||||||
|
<td class="data">{{$be.Subversion}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Protocol Version</td>
|
||||||
|
<td class="data">{{$be.ProtocolVersion}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Block</td>
|
||||||
|
<td class="data">{{$be.Blocks}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Difficulty</td>
|
||||||
|
<td class="data">{{$be.Difficulty}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Timeoffset</td>
|
||||||
|
<td class="data">{{$be.Timeoffset}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Size On Disk</td>
|
||||||
|
<td class="data">{{$be.SizeOnDisk}}</td>
|
||||||
|
</tr>
|
||||||
|
{{- if $be.Warnings -}}
|
||||||
|
<tr>
|
||||||
|
<td>Warnings</td>
|
||||||
|
<td class="data text-warning">{{$be.Warnings}}</td>
|
||||||
|
</tr>
|
||||||
|
{{- end -}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted">{{$bb.About}}</span>
|
||||||
|
{{end}}
|
||||||
Loading…
Reference in New Issue
Block a user