diff --git a/client/comms/wsconn.go b/client/comms/wsconn.go
index 04d8071b65..0992a9f25d 100644
--- a/client/comms/wsconn.go
+++ b/client/comms/wsconn.go
@@ -41,6 +41,15 @@ const (
DefaultResponseTimeout = 30 * time.Second
)
+// ConnectionStatus represents the current status of the websocket connection.
+type ConnectionStatus uint32
+
+const (
+ Disconnected ConnectionStatus = iota
+ Connected
+ InvalidCert
+)
+
// ErrInvalidCert is the error returned when attempting to use an invalid cert
// to set up a ws connection.
var ErrInvalidCert = fmt.Errorf("invalid certificate")
@@ -88,7 +97,7 @@ type WsCfg struct {
//
// NOTE: Disconnect event notifications may lag behind actual
// disconnections.
- ConnectEventFunc func(bool)
+ ConnectEventFunc func(ConnectionStatus)
// Logger is the logger for the WsConn.
Logger dex.Logger
@@ -112,8 +121,8 @@ type wsConn struct {
wsMtx sync.Mutex
ws *websocket.Conn
- connectedMtx sync.RWMutex
- connected bool
+ connectedMtx sync.RWMutex
+ connectionStatus ConnectionStatus
reqMtx sync.RWMutex
respHandlers map[uint64]*responseHandler
@@ -165,18 +174,18 @@ func NewWsConn(cfg *WsCfg) (WsConn, error) {
func (conn *wsConn) IsDown() bool {
conn.connectedMtx.RLock()
defer conn.connectedMtx.RUnlock()
- return !conn.connected
+ return conn.connectionStatus != Connected
}
-// setConnected updates the connection's connected state and runs the
+// setConnectionStatus updates the connection's status and runs the
// ConnectEventFunc in case of a change.
-func (conn *wsConn) setConnected(connected bool) {
+func (conn *wsConn) setConnectionStatus(status ConnectionStatus) {
conn.connectedMtx.Lock()
- statusChange := conn.connected != connected
- conn.connected = connected
+ statusChange := conn.connectionStatus != status
+ conn.connectionStatus = status
conn.connectedMtx.Unlock()
if statusChange && conn.cfg.ConnectEventFunc != nil {
- conn.cfg.ConnectEventFunc(connected)
+ conn.cfg.ConnectEventFunc(status)
}
}
@@ -195,11 +204,13 @@ func (conn *wsConn) connect(ctx context.Context) error {
if err != nil {
var e x509.UnknownAuthorityError
if errors.As(err, &e) {
+ conn.setConnectionStatus(InvalidCert)
if conn.tlsCfg == nil {
return ErrCertRequired
}
return ErrInvalidCert
}
+ conn.setConnectionStatus(Disconnected)
return err
}
@@ -241,7 +252,7 @@ func (conn *wsConn) connect(ctx context.Context) error {
conn.ws = ws
conn.wsMtx.Unlock()
- conn.setConnected(true)
+ conn.setConnectionStatus(Connected)
conn.wg.Add(1)
go func() {
defer conn.wg.Done()
@@ -264,7 +275,7 @@ func (conn *wsConn) close() {
// run as a goroutine. Increment the wg before calling read.
func (conn *wsConn) read(ctx context.Context) {
reconnect := func() {
- conn.setConnected(false)
+ conn.setConnectionStatus(Disconnected)
conn.reconnectCh <- struct{}{}
}
@@ -416,7 +427,7 @@ func (conn *wsConn) Connect(ctx context.Context) (*sync.WaitGroup, error) {
go func() {
defer conn.wg.Done()
<-ctxInternal.Done()
- conn.setConnected(false)
+ conn.setConnectionStatus(Disconnected)
conn.wsMtx.Lock()
if conn.ws != nil {
conn.log.Debug("Sending close 1000 (normal) message.")
diff --git a/client/core/core.go b/client/core/core.go
index 21fcb6cb67..25b03e0b7d 100644
--- a/client/core/core.go
+++ b/client/core/core.go
@@ -134,8 +134,9 @@ type dexConnection struct {
epochMtx sync.RWMutex
epoch map[string]uint64
- // connected is a best guess on the ws connection status.
- connected uint32
+
+ // connectionStatus is a best guess on the ws connection status.
+ connectionStatus uint32
pendingFeeMtx sync.RWMutex
pendingFee *pendingFeeState
@@ -294,12 +295,14 @@ func (dc *dexConnection) exchangeInfo() *Exchange {
dc.cfgMtx.RLock()
cfg := dc.cfg
dc.cfgMtx.RUnlock()
+ connectionStatus := comms.ConnectionStatus(
+ atomic.LoadUint32(&dc.connectionStatus))
if cfg == nil { // no config, assets, or markets data
return &Exchange{
- Host: dc.acct.host,
- AcctID: acctID,
- Connected: atomic.LoadUint32(&dc.connected) == 1,
- PendingFee: dc.getPendingFee(),
+ Host: dc.acct.host,
+ AcctID: acctID,
+ ConnectionStatus: connectionStatus,
+ PendingFee: dc.getPendingFee(),
}
}
@@ -328,16 +331,18 @@ func (dc *dexConnection) exchangeInfo() *Exchange {
feeAssets["dcr"] = dcrAsset
}
+ connectionStatus = comms.ConnectionStatus(
+ atomic.LoadUint32(&dc.connectionStatus))
return &Exchange{
- Host: dc.acct.host,
- AcctID: acctID,
- Markets: dc.marketMap(),
- Assets: assets,
- Connected: atomic.LoadUint32(&dc.connected) == 1,
- Fee: dcrAsset,
- RegFees: feeAssets,
- PendingFee: dc.getPendingFee(),
- CandleDurs: cfg.BinSizes,
+ Host: dc.acct.host,
+ AcctID: acctID,
+ Markets: dc.marketMap(),
+ Assets: assets,
+ ConnectionStatus: connectionStatus,
+ Fee: dcrAsset,
+ RegFees: feeAssets,
+ PendingFee: dc.getPendingFee(),
+ CandleDurs: cfg.BinSizes,
}
}
@@ -947,10 +952,12 @@ func (c *Core) dex(addr string) (*dexConnection, bool, error) {
c.connMtx.RLock()
dc, found := c.conns[host]
c.connMtx.RUnlock()
- connected := found && atomic.LoadUint32(&dc.connected) == 1
if !found {
return nil, false, fmt.Errorf("unknown DEX %s", addr)
}
+ connectionStatus := comms.ConnectionStatus(
+ atomic.LoadUint32(&dc.connectionStatus))
+ connected := connectionStatus == comms.Connected
return dc, connected, nil
}
@@ -2769,6 +2776,31 @@ func (c *Core) EstimateRegistrationTxFee(host string, certI interface{}, assetID
return txFee, nil
}
+// UpdateCert attempts to connect to a server using a new TLS certificate. If
+// the connection is successful, then the cert in the database is updated.
+func (c *Core) UpdateCert(host string, cert []byte) error {
+ account, err := c.db.Account(host)
+ if err != nil {
+ return err
+ }
+ if account == nil {
+ return fmt.Errorf("account does not exist for host: %v", host)
+ }
+ account.Cert = cert
+
+ _, connected := c.connectAccount(account)
+ if !connected {
+ return errors.New("failed to connect using new cert")
+ }
+
+ err = c.db.UpdateAccountInfo(account)
+ if err != nil {
+ return fmt.Errorf("failed to update account info: %w", err)
+ }
+
+ return nil
+}
+
// Register registers an account with a new DEX. If an error occurs while
// fetching the DEX configuration or creating the fee transaction, it will be
// returned immediately.
@@ -5555,6 +5587,7 @@ func (c *Core) connectDEX(acctInfo *db.AccountInfo, temporary ...bool) (*dexConn
apiVer: -1,
reportingConnects: reporting,
spots: make(map[string]*msgjson.Spot),
+ connectionStatus: uint32(comms.Disconnected),
// On connect, must set: cfg, epoch, and assets.
}
@@ -5584,8 +5617,8 @@ func (c *Core) connectDEX(acctInfo *db.AccountInfo, temporary ...bool) (*dexConn
return nil, errors.New("a TLS connection is required when not using a hidden service")
}
- wsCfg.ConnectEventFunc = func(connected bool) {
- c.handleConnectEvent(dc, connected)
+ wsCfg.ConnectEventFunc = func(status comms.ConnectionStatus) {
+ c.handleConnectEvent(dc, status)
}
wsCfg.ReconnectSync = func() {
go c.handleReconnect(host)
@@ -5739,11 +5772,9 @@ func (dc *dexConnection) broadcastingConnect() bool {
// lost or established.
//
// NOTE: Disconnect event notifications may lag behind actual disconnections.
-func (c *Core) handleConnectEvent(dc *dexConnection, connected bool) {
- var v uint32
+func (c *Core) handleConnectEvent(dc *dexConnection, status comms.ConnectionStatus) {
topic := TopicDEXDisconnected
- if connected {
- v = 1
+ if status == comms.Connected {
topic = TopicDEXConnected
} else {
for _, tracker := range dc.trackedTrades() {
@@ -5759,10 +5790,10 @@ func (c *Core) handleConnectEvent(dc *dexConnection, connected bool) {
tracker.mtx.Unlock()
}
}
- atomic.StoreUint32(&dc.connected, v)
+ atomic.StoreUint32(&dc.connectionStatus, uint32(status))
if dc.broadcastingConnect() {
subject, details := c.formatDetails(topic, dc.acct.host)
- dc.notify(newConnEventNote(topic, subject, dc.acct.host, connected, details, db.Poke))
+ dc.notify(newConnEventNote(topic, subject, dc.acct.host, status, details, db.Poke))
}
}
diff --git a/client/core/core_test.go b/client/core/core_test.go
index 7db7614715..7a34ac044b 100644
--- a/client/core/core_test.go
+++ b/client/core/core_test.go
@@ -267,7 +267,7 @@ func testDexConnection(ctx context.Context, crypter *tCrypter) (*dexConnection,
trades: make(map[order.OrderID]*trackedTrade),
epoch: map[string]uint64{tDcrBtcMktName: 0},
apiVer: serverdex.PreAPIVersion,
- connected: 1,
+ connectionStatus: uint32(comms.Connected),
reportingConnects: 1,
spots: make(map[string]*msgjson.Spot),
}, conn, acct
@@ -396,7 +396,7 @@ func (tdb *TDB) DisableAccount(url string) error {
return tdb.disableAccountErr
}
-func (tdb *TDB) UpdateAccount(ai *db.AccountInfo) error {
+func (tdb *TDB) UpdateAccountInfo(ai *db.AccountInfo) error {
tdb.verifyUpdateAccount = true
tdb.acct = ai
return nil
@@ -2662,12 +2662,12 @@ wait:
rig.dc.acct.unlock(rig.crypter)
// DEX not connected
- atomic.StoreUint32(&rig.dc.connected, 0)
+ atomic.StoreUint32(&rig.dc.connectionStatus, uint32(comms.Disconnected))
_, err = tCore.Trade(tPW, form)
if err == nil {
t.Fatalf("no error for disconnected dex")
}
- atomic.StoreUint32(&rig.dc.connected, 1)
+ atomic.StoreUint32(&rig.dc.connectionStatus, uint32(comms.Connected))
// No base asset
form.Base = 12345
diff --git a/client/core/notification.go b/client/core/notification.go
index 9b6be2e0f2..62936fd9e4 100644
--- a/client/core/notification.go
+++ b/client/core/notification.go
@@ -6,6 +6,7 @@ package core
import (
"fmt"
+ "decred.org/dcrdex/client/comms"
"decred.org/dcrdex/client/db"
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/msgjson"
@@ -335,8 +336,8 @@ func (on *EpochNotification) String() string {
// ConnEventNote is a notification regarding individual DEX connection status.
type ConnEventNote struct {
db.Notification
- Host string `json:"host"`
- Connected bool `json:"connected"`
+ Host string `json:"host"`
+ ConnectionStatus comms.ConnectionStatus `json:"connectionStatus"`
}
const (
@@ -344,11 +345,11 @@ const (
TopicDEXDisconnected Topic = "DEXDisconnected"
)
-func newConnEventNote(topic Topic, subject, host string, connected bool, details string, severity db.Severity) *ConnEventNote {
+func newConnEventNote(topic Topic, subject, host string, status comms.ConnectionStatus, details string, severity db.Severity) *ConnEventNote {
return &ConnEventNote{
- Notification: db.NewNotification(NoteTypeConnEvent, topic, subject, details, severity),
- Host: host,
- Connected: connected,
+ Notification: db.NewNotification(NoteTypeConnEvent, topic, subject, details, severity),
+ Host: host,
+ ConnectionStatus: status,
}
}
diff --git a/client/core/trade_simnet_test.go b/client/core/trade_simnet_test.go
index b113e22fed..b132e59206 100644
--- a/client/core/trade_simnet_test.go
+++ b/client/core/trade_simnet_test.go
@@ -39,6 +39,7 @@ import (
"decred.org/dcrdex/client/asset/btc"
"decred.org/dcrdex/client/asset/dcr"
+ "decred.org/dcrdex/client/comms"
"decred.org/dcrdex/client/db"
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/calc"
@@ -677,7 +678,7 @@ func TestOrderStatusReconciliation(t *testing.T) {
disconnectTimeout := 10 * sleepFactor * time.Second
disconnected := client2.notes.find(context.Background(), disconnectTimeout, func(n Notification) bool {
connNote, ok := n.(*ConnEventNote)
- return ok && connNote.Host == dexHost && !connNote.Connected
+ return ok && connNote.Host == dexHost && connNote.ConnectionStatus != comms.Connected
})
if !disconnected {
t.Fatalf("client 2 dex not disconnected after %v", disconnectTimeout)
diff --git a/client/core/types.go b/client/core/types.go
index 80236c5d56..5cef60f205 100644
--- a/client/core/types.go
+++ b/client/core/types.go
@@ -11,6 +11,7 @@ import (
"sync"
"decred.org/dcrdex/client/asset"
+ "decred.org/dcrdex/client/comms"
"decred.org/dcrdex/client/db"
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/calc"
@@ -491,15 +492,15 @@ type PendingFeeState struct {
// Exchange represents a single DEX with any number of markets.
type Exchange struct {
- Host string `json:"host"`
- AcctID string `json:"acctID"`
- Markets map[string]*Market `json:"markets"`
- Assets map[uint32]*dex.Asset `json:"assets"`
- Connected bool `json:"connected"`
- Fee *FeeAsset `json:"feeAsset"` // DEPRECATED. DCR.
- RegFees map[string]*FeeAsset `json:"regFees"`
- PendingFee *PendingFeeState `json:"pendingFee,omitempty"`
- CandleDurs []string `json:"candleDurs"`
+ Host string `json:"host"`
+ AcctID string `json:"acctID"`
+ Markets map[string]*Market `json:"markets"`
+ Assets map[uint32]*dex.Asset `json:"assets"`
+ ConnectionStatus comms.ConnectionStatus `json:"connectionStatus"`
+ Fee *FeeAsset `json:"feeAsset"` // DEPRECATED. DCR.
+ RegFees map[string]*FeeAsset `json:"regFees"`
+ PendingFee *PendingFeeState `json:"pendingFee,omitempty"`
+ CandleDurs []string `json:"candleDurs"`
}
// newDisplayID creates a display-friendly market ID for a base/quote ID pair.
diff --git a/client/db/bolt/db.go b/client/db/bolt/db.go
index acf6e67e8a..4cc805b7fa 100644
--- a/client/db/bolt/db.go
+++ b/client/db/bolt/db.go
@@ -521,6 +521,19 @@ func (db *BoltDB) CreateAccount(ai *dexdb.AccountInfo) error {
})
}
+// UpdateAccountInfo updates the account info for an existing account with
+// the same Host as the parameter. If no account exists with this host,
+// an error is returned.
+func (db *BoltDB) UpdateAccountInfo(ai *dexdb.AccountInfo) error {
+ return db.acctsUpdate(func(accts *bbolt.Bucket) error {
+ acct := accts.Bucket([]byte(ai.Host))
+ if acct == nil {
+ return fmt.Errorf("account not found for %s", ai.Host)
+ }
+ return acct.Put(accountKey, ai.Encode())
+ })
+}
+
// deleteAccount removes the account by host.
func (db *BoltDB) deleteAccount(host string) error {
acctKey := []byte(host)
diff --git a/client/db/interface.go b/client/db/interface.go
index 05de76c931..c02dc989fb 100644
--- a/client/db/interface.go
+++ b/client/db/interface.go
@@ -34,6 +34,10 @@ type DB interface {
Account(host string) (*AccountInfo, error)
// CreateAccount saves the AccountInfo.
CreateAccount(ai *AccountInfo) error
+ // UpdateAccountInfo updates the account info for an existing account with
+ // the same Host as the parameter. If no account exists with this host,
+ // an error is returned.
+ UpdateAccountInfo(ai *AccountInfo) error
// DisableAccount sets the AccountInfo disabled status to true.
DisableAccount(host string) error
// AccountProof retrieves the AccountPoof value specified by url.
diff --git a/client/webserver/api.go b/client/webserver/api.go
index 30f39a226f..e2e22f7787 100644
--- a/client/webserver/api.go
+++ b/client/webserver/api.go
@@ -375,6 +375,24 @@ func (s *WebServer) apiAccountImport(w http.ResponseWriter, r *http.Request) {
writeJSON(w, simpleAck(), s.indent)
}
+func (s *WebServer) apiUpdateCert(w http.ResponseWriter, r *http.Request) {
+ form := &struct {
+ Host string `json:"host"`
+ Cert string `json:"cert"`
+ }{}
+ if !readPost(w, r, form) {
+ return
+ }
+
+ err := s.core.UpdateCert(form.Host, []byte(form.Cert))
+ if err != nil {
+ s.writeAPIError(w, fmt.Errorf("error updating cert: %w", err))
+ return
+ }
+
+ writeJSON(w, simpleAck(), s.indent)
+}
+
// apiAccountDisable is the handler for the '/disableaccount' API request.
func (s *WebServer) apiAccountDisable(w http.ResponseWriter, r *http.Request) {
form := new(accountDisableForm)
diff --git a/client/webserver/http.go b/client/webserver/http.go
index 9d1b914038..6cbc3bd18a 100644
--- a/client/webserver/http.go
+++ b/client/webserver/http.go
@@ -244,6 +244,34 @@ func (s *WebServer) handleSettings(w http.ResponseWriter, r *http.Request) {
s.sendTemplate(w, "settings", data)
}
+// handleDexSettings is the handler for the '/dexsettings' page request.
+func (s *WebServer) handleDexSettings(w http.ResponseWriter, r *http.Request) {
+ host, err := getHostCtx(r)
+ if err != nil {
+ log.Errorf("error getting host ctx: %v", err)
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ exchanges := s.core.Exchanges()
+ exchange, ok := exchanges[host]
+ if !ok {
+ log.Errorf("no exchange with host: %v", host)
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ data := &struct {
+ CommonArguments
+ Exchange *core.Exchange
+ }{
+ CommonArguments: *commonArgs(r, fmt.Sprintf("%v Settings | Decred DEX", host)),
+ Exchange: exchange,
+ }
+
+ s.sendTemplate(w, "dexsettings", data)
+}
+
type ordersTmplData struct {
CommonArguments
Assets map[uint32]*core.SupportedAsset
diff --git a/client/webserver/live_test.go b/client/webserver/live_test.go
index b55ca7469a..b5937825ae 100644
--- a/client/webserver/live_test.go
+++ b/client/webserver/live_test.go
@@ -25,6 +25,7 @@ import (
"decred.org/dcrdex/client/asset/btc"
"decred.org/dcrdex/client/asset/dcr"
"decred.org/dcrdex/client/asset/ltc"
+ "decred.org/dcrdex/client/comms"
"decred.org/dcrdex/client/core"
"decred.org/dcrdex/client/db"
"decred.org/dcrdex/dex"
@@ -303,7 +304,7 @@ var tExchanges = map[string]*core.Exchange{
mkid(22, 42): mkMrkt("mona", "dcr"),
mkid(28, 0): mkMrkt("vtc", "btc"),
},
- Connected: true,
+ ConnectionStatus: comms.Connected,
RegFees: map[string]*core.FeeAsset{
"dcr": {
ID: 42,
@@ -352,7 +353,7 @@ var tExchanges = map[string]*core.Exchange{
mkid(0, 2): mkMrkt("btc", "ltc"),
mkid(22, 141): mkMrkt("mona", "kmd"),
},
- Connected: true,
+ ConnectionStatus: comms.Connected,
RegFees: map[string]*core.FeeAsset{
"dcr": {
ID: 42,
@@ -1586,6 +1587,9 @@ func (c *TCore) ExportSeed(pw []byte) ([]byte, error) {
func (c *TCore) WalletLogFilePath(uint32) (string, error) {
return "", nil
}
+func (c *TCore) UpdateCert(string, []byte) error {
+ return nil
+}
func TestServer(t *testing.T) {
// Register dummy drivers for unimplemented assets.
diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go
index f52e16ad99..0cc301531b 100644
--- a/client/webserver/locales/en-us.go
+++ b/client/webserver/locales/en-us.go
@@ -215,4 +215,5 @@ var EnUS = map[string]string{
"Maximum Possible Swap Fees": "Maximum Possible Swap Fees",
"max_fee_conditions": "This is the most you would ever pay in fees on your swap. Fees are normally assessed at a fraction of this rate. The maximum is not subject to changes once your order is placed.",
"wallet_logs": "Wallet Logs",
+ "Update TLS Certificate": "Update TLS Certificate",
}
diff --git a/client/webserver/middleware.go b/client/webserver/middleware.go
index f3e39fa494..43065ebbfe 100644
--- a/client/webserver/middleware.go
+++ b/client/webserver/middleware.go
@@ -19,6 +19,7 @@ type ctxID int
const (
ctxOID ctxID = iota
+ ctxHost
)
// securityMiddleware adds security headers to the server responses.
@@ -133,7 +134,29 @@ func (s *WebServer) requireDEXConnection(next http.Handler) http.Handler {
})
}
-// orderIDCtx embeds order ID into the request context
+// dexHostCtx embeds the host into the request context.
+func dexHostCtx(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ host := chi.URLParam(r, "host")
+ ctx := context.WithValue(r.Context(), ctxHost, host)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
+
+// getHostCtx interprets the context value at ctxHost as a string host.
+func getHostCtx(r *http.Request) (string, error) {
+ untypedHost := r.Context().Value(ctxHost)
+ if untypedHost == nil {
+ return "", errors.New("host not set in request")
+ }
+ host, ok := untypedHost.(string)
+ if !ok {
+ return "", errors.New("type assertion failed")
+ }
+ return host, nil
+}
+
+// orderIDCtx embeds order ID into the request context.
func orderIDCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
oid := chi.URLParam(r, "oid")
diff --git a/client/webserver/site/src/css/application.scss b/client/webserver/site/src/css/application.scss
index 0f2265371e..48f3cabf6a 100644
--- a/client/webserver/site/src/css/application.scss
+++ b/client/webserver/site/src/css/application.scss
@@ -78,3 +78,4 @@ $grid-breakpoints: (
@import "./settings.scss";
@import "./forms.scss";
@import "./forms_dark.scss";
+@import "./dex_settings.scss";
diff --git a/client/webserver/site/src/css/dex_settings.scss b/client/webserver/site/src/css/dex_settings.scss
new file mode 100644
index 0000000000..b0600bb70f
--- /dev/null
+++ b/client/webserver/site/src/css/dex_settings.scss
@@ -0,0 +1,7 @@
+.connection {
+ color: green;
+}
+
+.disconnected {
+ color: red;
+}
diff --git a/client/webserver/site/src/css/icons.scss b/client/webserver/site/src/css/icons.scss
index 46e76949bd..15c3787785 100644
--- a/client/webserver/site/src/css/icons.scss
+++ b/client/webserver/site/src/css/icons.scss
@@ -111,3 +111,7 @@
.ico-checkbox::before {
content: "\e90d";
}
+
+.ico-connection::before {
+ content: "\e91b";
+}
diff --git a/client/webserver/site/src/css/settings.scss b/client/webserver/site/src/css/settings.scss
index ea962ae73f..0ed3a2df3a 100644
--- a/client/webserver/site/src/css/settings.scss
+++ b/client/webserver/site/src/css/settings.scss
@@ -49,3 +49,8 @@ div.settings-exchange {
word-break: break-all;
user-select: all;
}
+
+.dex-settings-icon {
+ font-size: 15;
+ margin-left: 0.5rem;
+}
diff --git a/client/webserver/site/src/font/icomoon.svg b/client/webserver/site/src/font/icomoon.svg
index 1b4f26b54c..c2186cb555 100644
--- a/client/webserver/site/src/font/icomoon.svg
+++ b/client/webserver/site/src/font/icomoon.svg
@@ -7,25 +7,26 @@
[[[simultaneous_servers_msg]]]
@@ -74,11 +59,6 @@ {{template "confirmRegistrationForm"}} - {{- /* AUTHORIZE EXPORT ACCOUNT */ -}} - - {{- /* AUTHORIZE IMPORT ACCOUNT */ -}} - {{- /* DISABLE ACCOUNT */ -}} - - {{- /* CHANGE APP PASSWORD */ -}}