EthereumType: admin interface to read and update contract info
This commit is contained in:
parent
19a902360e
commit
a55c69a8a1
@ -35,6 +35,66 @@ type htmlTemplates[TD any] struct {
|
||||
postHtmlTemplateHandler func(data *TD, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
func (s *htmlTemplates[TD]) jsonHandler(handler func(r *http.Request, apiVersion int) (interface{}, error), apiVersion int) func(w http.ResponseWriter, r *http.Request) {
|
||||
type jsonError struct {
|
||||
Text string `json:"error"`
|
||||
HTTPStatus int `json:"-"`
|
||||
}
|
||||
handlerName := getFunctionName(handler)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var data interface{}
|
||||
var err error
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
glog.Error(handlerName, " recovered from panic: ", e)
|
||||
debug.PrintStack()
|
||||
if s.debug {
|
||||
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e), http.StatusInternalServerError}
|
||||
} else {
|
||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if e, isError := data.(jsonError); isError {
|
||||
w.WriteHeader(e.HTTPStatus)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
glog.Warning("json encode ", err)
|
||||
}
|
||||
if s.metrics != nil {
|
||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
|
||||
}
|
||||
}()
|
||||
if s.metrics != nil {
|
||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
|
||||
}
|
||||
data, err = handler(r, apiVersion)
|
||||
if err != nil || data == nil {
|
||||
if apiErr, ok := err.(*api.APIError); ok {
|
||||
if apiErr.Public {
|
||||
data = jsonError{apiErr.Error(), http.StatusBadRequest}
|
||||
} else {
|
||||
data = jsonError{apiErr.Error(), http.StatusInternalServerError}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
glog.Error(handlerName, " error: ", err)
|
||||
}
|
||||
if s.debug {
|
||||
if data != nil {
|
||||
data = jsonError{fmt.Sprintf("Internal server error: %v, data %+v", err, data), http.StatusInternalServerError}
|
||||
} else {
|
||||
data = jsonError{fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError}
|
||||
}
|
||||
} else {
|
||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *htmlTemplates[TD]) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TD, error)) func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerName := getFunctionName(handler)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -5,11 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/trezor/blockbook/api"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
@ -72,6 +76,8 @@ func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.B
|
||||
serveMux.HandleFunc(path+"admin/ws-limit-exceeding-ips", s.htmlTemplateHandler(s.wsLimitExceedingIPs))
|
||||
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
|
||||
serveMux.HandleFunc(path+"admin/internal-data-errors", s.htmlTemplateHandler(s.internalDataErrors))
|
||||
serveMux.HandleFunc(path+"admin/contract-info", s.htmlTemplateHandler(s.contractInfoPage))
|
||||
serveMux.HandleFunc(path+"admin/contract-info/", s.jsonHandler(s.apiContractInfo, 0))
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@ -118,7 +124,8 @@ func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
|
||||
const (
|
||||
adminIndexTpl = iota + errorInternalTpl + 1
|
||||
adminInternalErrorsTpl
|
||||
adminLimitExceedingIPS
|
||||
adminLimitExceedingIPSTpl
|
||||
adminContractInfoTpl
|
||||
|
||||
internalTplCount
|
||||
)
|
||||
@ -173,7 +180,8 @@ func (s *InternalServer) parseTemplates() []*template.Template {
|
||||
t[errorInternalTpl] = createTemplate("./static/internal_templates/error.html", "./static/internal_templates/base.html")
|
||||
t[adminIndexTpl] = createTemplate("./static/internal_templates/index.html", "./static/internal_templates/base.html")
|
||||
t[adminInternalErrorsTpl] = createTemplate("./static/internal_templates/block_internal_data_errors.html", "./static/internal_templates/base.html")
|
||||
t[adminLimitExceedingIPS] = createTemplate("./static/internal_templates/ws_limit_exceeding_ips.html", "./static/internal_templates/base.html")
|
||||
t[adminLimitExceedingIPSTpl] = createTemplate("./static/internal_templates/ws_limit_exceeding_ips.html", "./static/internal_templates/base.html")
|
||||
t[adminContractInfoTpl] = createTemplate("./static/internal_templates/contract_info.html", "./static/internal_templates/base.html")
|
||||
return t
|
||||
}
|
||||
|
||||
@ -213,5 +221,54 @@ func (s *InternalServer) wsLimitExceedingIPs(w http.ResponseWriter, r *http.Requ
|
||||
})
|
||||
data.WsLimitExceedingIPs = ips
|
||||
data.WsGetAccountInfoLimit = s.is.WsGetAccountInfoLimit
|
||||
return adminLimitExceedingIPS, data, nil
|
||||
return adminLimitExceedingIPSTpl, data, nil
|
||||
}
|
||||
|
||||
func (s *InternalServer) contractInfoPage(w http.ResponseWriter, r *http.Request) (tpl, *InternalTemplateData, error) {
|
||||
data := s.newTemplateData(r)
|
||||
return adminContractInfoTpl, data, nil
|
||||
}
|
||||
|
||||
func (s *InternalServer) apiContractInfo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
if r.Method == http.MethodPost {
|
||||
return s.updateContracts(r)
|
||||
}
|
||||
var contractAddress string
|
||||
i := strings.LastIndexByte(r.URL.Path, '/')
|
||||
if i > 0 {
|
||||
contractAddress = r.URL.Path[i+1:]
|
||||
}
|
||||
if len(contractAddress) == 0 {
|
||||
return nil, api.NewAPIError("Missing contract address", true)
|
||||
}
|
||||
|
||||
contractInfo, valid, err := s.api.GetContractInfo(contractAddress, bchain.UnknownTokenType)
|
||||
if err != nil {
|
||||
return nil, api.NewAPIError(err.Error(), true)
|
||||
}
|
||||
if !valid {
|
||||
return nil, api.NewAPIError("Not a contract", true)
|
||||
}
|
||||
return contractInfo, nil
|
||||
}
|
||||
|
||||
func (s *InternalServer) updateContracts(r *http.Request) (interface{}, error) {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, api.NewAPIError("Cannot get request body", true)
|
||||
}
|
||||
var contractInfos []bchain.ContractInfo
|
||||
err = json.Unmarshal(data, &contractInfos)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Cannot unmarshal body to array of ContractInfo objects")
|
||||
}
|
||||
for i := range contractInfos {
|
||||
c := &contractInfos[i]
|
||||
err := s.db.StoreContractInfo(c)
|
||||
if err != nil {
|
||||
return nil, api.NewAPIError("Error updating contract "+c.Contract+" "+err.Error(), true)
|
||||
}
|
||||
|
||||
}
|
||||
return "{\"success\":\"Updated " + strconv.Itoa(len(contractInfos)) + " contracts\"}", nil
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -289,62 +288,6 @@ func getFunctionName(i interface{}) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (s *PublicServer) jsonHandler(handler func(r *http.Request, apiVersion int) (interface{}, error), apiVersion int) func(w http.ResponseWriter, r *http.Request) {
|
||||
type jsonError struct {
|
||||
Text string `json:"error"`
|
||||
HTTPStatus int `json:"-"`
|
||||
}
|
||||
handlerName := getFunctionName(handler)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var data interface{}
|
||||
var err error
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
glog.Error(handlerName, " recovered from panic: ", e)
|
||||
debug.PrintStack()
|
||||
if s.debug {
|
||||
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e), http.StatusInternalServerError}
|
||||
} else {
|
||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if e, isError := data.(jsonError); isError {
|
||||
w.WriteHeader(e.HTTPStatus)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
glog.Warning("json encode ", err)
|
||||
}
|
||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
|
||||
}()
|
||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
|
||||
data, err = handler(r, apiVersion)
|
||||
if err != nil || data == nil {
|
||||
if apiErr, ok := err.(*api.APIError); ok {
|
||||
if apiErr.Public {
|
||||
data = jsonError{apiErr.Error(), http.StatusBadRequest}
|
||||
} else {
|
||||
data = jsonError{apiErr.Error(), http.StatusInternalServerError}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
glog.Error(handlerName, " error: ", err)
|
||||
}
|
||||
if s.debug {
|
||||
if data != nil {
|
||||
data = jsonError{fmt.Sprintf("Internal server error: %v, data %+v", err, data), http.StatusInternalServerError}
|
||||
} else {
|
||||
data = jsonError{fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError}
|
||||
}
|
||||
} else {
|
||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
|
||||
t := &TemplateData{
|
||||
CoinName: s.is.Coin,
|
||||
|
||||
39
static/internal_templates/contract_info.html
Normal file
39
static/internal_templates/contract_info.html
Normal file
@ -0,0 +1,39 @@
|
||||
{{define "specific"}} {{if eq .ChainType 1}}
|
||||
<script>
|
||||
function getContractInfo(event) {
|
||||
event.preventDefault();
|
||||
const address = document.getElementById('contractAddress').value.trim();
|
||||
if (!address) {
|
||||
alert('Enter a contract address');
|
||||
} else {
|
||||
window.location = window.location.href.split('?')[0] + '/' + address;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<form onsubmit="return getContractInfo(event)">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="contract address"
|
||||
id="contractAddress"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input class="btn btn-secondary" type="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row" style="margin: 35px">
|
||||
To update contract, use POST request to /admin/contract-info/ endpoint. Example:
|
||||
<div style="margin-top: 20px">
|
||||
<pre>
|
||||
curl -k -v \
|
||||
'https://<internaladdress>/admin/contract-info/' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '[{ContractInfo},{ContractInfo},...]'
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{{else}} Not supported {{end}}{{end}}
|
||||
@ -1,10 +1,14 @@
|
||||
{{define "specific"}}
|
||||
<div class="row">
|
||||
<div class="col"><a href="/admin/ws-limit-exceeding-ips">IP addresses that exceeded websocket usage limit</a></div>
|
||||
<div class="col">
|
||||
<a href="/admin/ws-limit-exceeding-ips">IP addresses that exceeded websocket usage limit</a>
|
||||
</div>
|
||||
</div>
|
||||
{{if eq .ChainType 1}}
|
||||
<div class="row">
|
||||
<div class="col"><a href="/admin/internal-data-errors">Internal Data Errors</a></div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<div class="col"><a href="/admin/contract-info">Contract Info</a></div>
|
||||
</div>
|
||||
{{end}}{{end}}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user