Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

admin: add gui #2735

Merged
merged 3 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -45,3 +45,4 @@ tatanka/cmd/demo/demo
server/cmd/validatemarkets
client/cmd/translationsreport/translationsreport
client/cmd/translationsreport/worksheets
server/cmd/dexadm/dexadm
5 changes: 5 additions & 0 deletions client/webserver/site/src/css/utilities.scss
Expand Up @@ -200,6 +200,11 @@ div.clear {
user-select: all;
}

.overflow-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
}

.user-select-none {
user-select: none;
}
Expand Down
5 changes: 4 additions & 1 deletion client/webserver/site/src/html/dexsettings.tmpl
Expand Up @@ -13,7 +13,10 @@
<span class="me-2 text-danger ico-disconnected d-hide" id="disconnectedIcon"></span>
<span id="connectionStatus"></span>
</div>
<hr>
<div class="py-1 border-top border-bottom">
<div>Account ID</div>
<div class="user-select-all w-100 overflow-ellipsis">{{.Exchange.AcctID}}</div>
</div>
<div class="flex-stretch-column">
<div class="d-flex align-items-stretch">
<div class="flex-center flex-grow-1 pe-3">
Expand Down
23 changes: 23 additions & 0 deletions server/admin/api.go
Expand Up @@ -574,6 +574,29 @@ func (s *Server) apiForgiveMatchFail(w http.ResponseWriter, r *http.Request) {
writeJSON(w, res)
}

func (s *Server) apiMatchOutcomes(w http.ResponseWriter, r *http.Request) {
acctIDStr := chi.URLParam(r, accountIDKey)
acctID, err := decodeAcctID(acctIDStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var n int = 100
if nStr := r.URL.Query().Get("n"); nStr != "" {
n, err = strconv.Atoi(nStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
outcomes, err := s.core.AccountMatchOutcomesN(acctID, n)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, outcomes)
}

func toNote(r *http.Request) (*msgjson.Message, int, error) {
body, err := io.ReadAll(r.Body)
r.Body.Close()
Expand Down
32 changes: 23 additions & 9 deletions server/admin/server.go
Expand Up @@ -13,6 +13,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"sync"
"time"
Expand All @@ -22,6 +23,7 @@ import (
"decred.org/dcrdex/dex/order"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/asset"
"decred.org/dcrdex/server/auth"
"decred.org/dcrdex/server/db"
dexsrv "decred.org/dcrdex/server/dex"
"decred.org/dcrdex/server/market"
Expand Down Expand Up @@ -69,6 +71,7 @@ type SvrCore interface {
SuspendMarket(name string, tSusp time.Time, persistBooks bool) (*market.SuspendEpoch, error)
ResumeMarket(name string, asSoonAs time.Time) (startEpoch int64, startTime time.Time, err error)
ForgiveMatchFail(aid account.AccountID, mid order.MatchID) (forgiven, unbanned bool, err error)
AccountMatchOutcomesN(user account.AccountID, n int) ([]*auth.MatchOutcome, error)
BookOrders(base, quote uint32) (orders []*order.LimitOrder, err error)
EpochOrders(base, quote uint32) (orders []order.Order, err error)
MarketMatchesStreaming(base, quote uint32, includeInactive bool, N int64, f func(*dexsrv.MatchData) error) (int, error)
Expand All @@ -90,6 +93,7 @@ type SrvConfig struct {
Core SvrCore
Addr, Cert, Key string
AuthSHA [32]byte
NoTLS bool
}

// UseLogger sets the logger for the admin package.
Expand All @@ -104,15 +108,18 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
return nil, fmt.Errorf("missing certificates")
}

keypair, err := tls.LoadX509KeyPair(cfg.Cert, cfg.Key)
if err != nil {
return nil, err
}
var tlsConfig *tls.Config
if !cfg.NoTLS {
keypair, err := tls.LoadX509KeyPair(cfg.Cert, cfg.Key)
if err != nil {
return nil, err
}

// Prepare the TLS configuration.
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{keypair},
MinVersion: tls.VersionTLS12,
// Prepare the TLS configuration.
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{keypair},
MinVersion: tls.VersionTLS12,
}
}

// Create an HTTP router.
Expand Down Expand Up @@ -147,6 +154,7 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
r.Get("/enabledataapi/{"+yesKey+"}", s.apiEnableDataAPI)
r.Route("/account/{"+accountIDKey+"}", func(rm chi.Router) {
rm.Get("/", s.apiAccountInfo)
rm.Get("/outcomes", s.apiMatchOutcomes)
rm.Get("/forgive_match/{"+matchIDKey+"}", s.apiForgiveMatchFail)
rm.Post("/notify", s.apiNotify)
})
Expand All @@ -173,7 +181,13 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
// Run starts the server.
func (s *Server) Run(ctx context.Context) {
// Create listener.
listener, err := tls.Listen("tcp", s.addr, s.tlsConfig)
var listener net.Listener
var err error
if s.tlsConfig != nil {
listener, err = tls.Listen("tcp", s.addr, s.tlsConfig)
} else {
listener, err = net.Listen("tcp", s.addr)
}
if err != nil {
log.Errorf("can't listen on %s. admin server quitting: %v", s.addr, err)
return
Expand Down
4 changes: 4 additions & 0 deletions server/admin/server_test.go
Expand Up @@ -28,6 +28,7 @@ import (
"decred.org/dcrdex/dex/order"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/asset"
"decred.org/dcrdex/server/auth"
"decred.org/dcrdex/server/db"
dexsrv "decred.org/dcrdex/server/dex"
"decred.org/dcrdex/server/market"
Expand Down Expand Up @@ -213,6 +214,9 @@ func (c *TCore) ForgiveMatchFail(_ account.AccountID, _ order.MatchID) (bool, bo
func (c *TCore) CreatePrepaidBonds(n int, strength uint32, durSecs int64) ([][]byte, error) {
return nil, nil
}
func (c *TCore) AccountMatchOutcomesN(user account.AccountID, n int) ([]*auth.MatchOutcome, error) {
return nil, nil
}
func (c *TCore) Notify(_ account.AccountID, _ *msgjson.Message) {}
func (c *TCore) NotifyAll(_ *msgjson.Message) {}

Expand Down
32 changes: 32 additions & 0 deletions server/auth/auth.go
Expand Up @@ -1459,6 +1459,38 @@ func (auth *AuthManager) loadUserOutcomes(user account.AccountID) (*latestMatchO
return latestMatches, latestPreimageResults, orderOutcomes, nil
}

// MatchOutcome is a JSON-friendly version of db.MatchOutcome.
type MatchOutcome struct {
ID dex.Bytes `json:"matchID"`
Status string `json:"status"`
Fail bool `json:"failed"`
Stamp int64 `json:"stamp"`
Value uint64 `json:"value"`
BaseID uint32 `json:"baseID"`
Quote uint32 `json:"quoteID"`
}

// AccountMatchOutcomesN generates a list of recent match outcomes for a user.
func (auth *AuthManager) AccountMatchOutcomesN(user account.AccountID, n int) ([]*MatchOutcome, error) {
dbOutcomes, err := auth.storage.CompletedAndAtFaultMatchStats(user, n)
if err != nil {
return nil, err
}
outcomes := make([]*MatchOutcome, len(dbOutcomes))
for i, o := range dbOutcomes {
outcomes[i] = &MatchOutcome{
ID: o.ID[:],
Status: o.Status.String(),
Fail: o.Fail,
Stamp: o.Time,
Value: o.Value,
BaseID: o.Base,
Quote: o.Quote,
}
}
return outcomes, nil
}

// loadUserScore computes the user's current score from order and swap data
// retrieved from the DB. Use this instead of userScore if the user is offline.
func (auth *AuthManager) loadUserScore(user account.AccountID) (int32, error) {
Expand Down
3 changes: 3 additions & 0 deletions server/cmd/dcrdex/config.go
Expand Up @@ -95,6 +95,7 @@ type dexConf struct {
AdminSrvOn bool
AdminSrvAddr string
AdminSrvPW []byte
AdminSrvNoTLS bool
NoResumeSwaps bool
DisableDataAPI bool
NodeRelayAddr string
Expand Down Expand Up @@ -144,6 +145,7 @@ type flagsData struct {
AdminSrvOn bool `long:"adminsrvon" description:"Turn on the admin server."`
AdminSrvAddr string `long:"adminsrvaddr" description:"Administration HTTPS server address (default: 127.0.0.1:6542)."`
AdminSrvPassword string `long:"adminsrvpass" description:"Admin server password. INSECURE. Do not set unless absolutely necessary."`
AdminSrvNoTLS bool `long:"adminsrvnotls" description:"Run admin server without TLS. Only use this option if you are using a securely configured reverse proxy."`

NoResumeSwaps bool `long:"noresumeswaps" description:"Do not attempt to resume swaps that are active in the DB."`

Expand Down Expand Up @@ -555,6 +557,7 @@ func loadConfig() (*dexConf, *procOpts, error) {
AdminSrvAddr: adminSrvAddr,
AdminSrvOn: cfg.AdminSrvOn,
AdminSrvPW: []byte(cfg.AdminSrvPassword),
AdminSrvNoTLS: cfg.AdminSrvNoTLS,
NoResumeSwaps: cfg.NoResumeSwaps,
DisableDataAPI: cfg.DisableDataAPI,
NodeRelayAddr: cfg.NodeRelayAddr,
Expand Down
1 change: 1 addition & 0 deletions server/cmd/dcrdex/main.go
Expand Up @@ -161,6 +161,7 @@ func mainCore(ctx context.Context) error {
AuthSHA: adminSrvAuthSHA,
Cert: cfg.RPCCert,
Key: cfg.RPCKey,
NoTLS: cfg.AdminSrvNoTLS,
}
adminServer, err := admin.NewServer(srvCFG)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions server/cmd/dexadm/dexadm_harness.conf
@@ -0,0 +1,3 @@
adminsrvurl=https://127.0.0.1:16542
adminsrvpass=adminpass
adminsrvcertpath=~/dextest/dcrdex/rpc.cert