package main import ( "fmt" "html/template" "net/http" "os" "path/filepath" "strconv" rice "github.com/GeertJohan/go.rice" "github.com/ipfs/go-cid" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" ) type handler struct { api api.FullNode st *storage site *rice.Box assets http.Handler templates map[string]*template.Template } func newHandler(api api.FullNode, st *storage) (*handler, error) { h := &handler{ api: api, st: st, site: rice.MustFindBox("site"), templates: map[string]*template.Template{}, } h.assets = http.FileServer(h.site.HTTPBox()) funcs := template.FuncMap{ "count": h.count, "countCol": h.countCol, "sum": h.sum, "netPower": h.netPower, "queryNum": h.queryNum, "sizeStr": sizeStr, "strings": h.strings, "qstr": h.qstr, "qstrs": h.qstrs, "messages": h.messages, "pageDown": pageDown, "parseInt": func(s string) (int, error) { i, e := strconv.ParseInt(s, 10, 64); return int(i), e }, "substr": func(i, j int, s string) string { return s[i:j] }, "sub": func(a, b int) int { return a - b }, // TODO: really not builtin? "param": func(string) string { return "" }, // replaced in request handler } base := template.New("") base.Funcs(funcs) return h, h.site.Walk("", func(path string, info os.FileInfo, err error) error { if filepath.Ext(path) != ".html" { return nil } if err != nil { return err } log.Info(path) h.templates["/"+path], err = base.New(path).Parse(h.site.MustString(path)) return err }) } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h, err := newHandler(h.api, h.st) // for faster dev if err != nil { log.Error(err) return } p := r.URL.Path if p == "/" { p = "/index.html" } t, ok := h.templates[p] if !ok { h.assets.ServeHTTP(w, r) return } t, err = t.Clone() if err != nil { log.Error(err) return } t.Funcs(map[string]interface{}{ "param": r.FormValue, }) if err := t.Execute(w, nil); err != nil { log.Errorf("%+v", err) return } log.Info(r.URL.Path) } // Template funcs func (h *handler) count(table string, filters ...string) (int, error) { // explicitly not caring about sql injection too much, this doesn't take user input filts := "" if len(filters) > 0 { filts = " where " for _, filter := range filters { filts += filter + " and " } filts = filts[:len(filts)-5] } var c int err := h.st.db.QueryRow("select count(1) from " + table + filts).Scan(&c) if err != nil { return 0, err } return c, nil } func (h *handler) countCol(table string, col string, filters ...string) (int, error) { // explicitly not caring about sql injection too much, this doesn't take user input filts := "" if len(filters) > 0 { filts = " where " for _, filter := range filters { filts += filter + " and " } filts = filts[:len(filts)-5] } var c int err := h.st.db.QueryRow("select count(distinct " + col + ") from " + table + filts).Scan(&c) if err != nil { return 0, err } return c, nil } func (h *handler) sum(table string, col string) (types.BigInt, error) { return h.queryNum("select sum(cast(" + col + " as bigint)) from " + table) } func (h *handler) netPower(slashFilt string) (types.BigInt, error) { if slashFilt != "" { slashFilt = " where " + slashFilt } return h.queryNum(`select sum(power) from (select distinct on (addr) power, slashed_at from miner_heads inner join blocks b on miner_heads.stateroot = b.parentStateRoot order by addr, height desc) as p` + slashFilt) } func (h *handler) queryNum(q string, p ...interface{}) (types.BigInt, error) { // explicitly not caring about sql injection too much, this doesn't take user input var c string err := h.st.db.QueryRow(q, p...).Scan(&c) if err != nil { log.Error("qnum ", q, p, err) return types.NewInt(0), err } i := types.NewInt(0) _, ok := i.SetString(c, 10) if !ok { return types.NewInt(0), xerrors.New("num parse error: " + c) } return i, nil } var units = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"} func sizeStr(size types.BigInt) string { size = types.BigMul(size, types.NewInt(100)) i := 0 for types.BigCmp(size, types.NewInt(102400)) >= 0 && i < len(units)-1 { size = types.BigDiv(size, types.NewInt(1024)) i++ } return fmt.Sprintf("%s.%s %s", types.BigDiv(size, types.NewInt(100)), types.BigMod(size, types.NewInt(100)), units[i]) } func (h *handler) strings(table string, col string, filter string, args ...interface{}) (out []string, err error) { if len(filter) > 0 { filter = " where " + filter } log.Info("strings qstr ", "select "+col+" from "+table+filter, args) rws, err := h.st.db.Query("select "+col+" from "+table+filter, args...) if err != nil { return nil, err } for rws.Next() { var r string if err := rws.Scan(&r); err != nil { return nil, err } out = append(out, r) } return } func (h *handler) qstr(q string, p ...interface{}) (string, error) { // explicitly not caring about sql injection too much, this doesn't take user input r, err := h.qstrs(q, 1, p...) if err != nil { return "", err } return r[0], nil } func (h *handler) qstrs(q string, n int, p ...interface{}) ([]string, error) { // explicitly not caring about sql injection too much, this doesn't take user input c := make([]string, n) ia := make([]interface{}, n) for i := range c { ia[i] = &c[i] } err := h.st.db.QueryRow(q, p...).Scan(ia...) if err != nil { log.Error("qnum ", q, p, err) return nil, err } return c, nil } func (h *handler) messages(filter string, args ...interface{}) (out []types.Message, err error) { if len(filter) > 0 { filter = " where " + filter } log.Info("select * from messages " + filter) rws, err := h.st.db.Query("select * from messages "+filter, args...) if err != nil { return nil, err } for rws.Next() { var r types.Message var cs string if err := rws.Scan( &cs, &r.From, &r.To, &r.Nonce, &r.Value, &r.GasPrice, &r.GasLimit, &r.Method, &r.Params, ); err != nil { return nil, err } c, err := cid.Parse(cs) if err != nil { return nil, err } if c != r.Cid() { log.Warn("msg cid doesn't match") } out = append(out, r) } return } func pageDown(base, n int) []int { out := make([]int, n) for i := range out { out[i] = base - i } return out } var _ http.Handler = &handler{}