diff --git a/webapi/formatting.go b/webapi/formatting.go
index a65e0686..2e6a9eb9 100644
--- a/webapi/formatting.go
+++ b/webapi/formatting.go
@@ -1,8 +1,14 @@
package webapi
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "html/template"
+ "strings"
"time"
+
+ "github.com/decred/dcrd/dcrutil/v4"
)
func addressURL(blockExplorerURL string) func(string) string {
@@ -26,3 +32,24 @@ func blockURL(blockExplorerURL string) func(int64) string {
func dateTime(t int64) string {
return time.Unix(t, 0).Format("2 Jan 2006 15:04:05 MST")
}
+
+func stripWss(input string) string {
+ input = strings.ReplaceAll(input, "wss://", "")
+ input = strings.ReplaceAll(input, "/ws", "")
+ return input
+}
+
+func indentJSON(input string) template.HTML {
+ var indented bytes.Buffer
+ err := json.Indent(&indented, []byte(input), "
", " ")
+ if err != nil {
+ log.Errorf("Failed to indent JSON: %w", err)
+ return template.HTML(input)
+ }
+
+ return template.HTML(indented.String())
+}
+
+func atomsToDCR(atoms int64) string {
+ return dcrutil.Amount(atoms).String()
+}
diff --git a/webapi/public/css/vspd.css b/webapi/public/css/vspd.css
index b20055f2..0b6da3e8 100644
--- a/webapi/public/css/vspd.css
+++ b/webapi/public/css/vspd.css
@@ -2,12 +2,17 @@ html, body {
height: 100%;
}
body {
- background-color: #F9FAFA;
+ overflow-y: overlay;
+ background-color: #F3F5F6;
color: #3D5873;
display: flex;
flex-direction: column;
}
+.navbar {
+ background-color: #F9FAFA;
+}
+
.navbar a {
text-decoration: none;
}
@@ -67,7 +72,7 @@ footer .code {
}
@media (max-width: 768px) {
- .footer__credit {
+ .footer__credit {
text-align: center;
}
}
@@ -104,39 +109,138 @@ footer .code {
line-height: 1.4;
}
-.block__content th {
- font-weight: normal;
- padding-right: 15px;
- color: #495057;
- background-color: #e9ecef;
-}
+.block__content table td ,
+.block__content table th {
+ padding: 10px 16px;
+}
.block__content table td {
word-break: break-word;
font-family: "vspd-code";
}
.block__content table th {
- vertical-align: top;
white-space: nowrap;
+ font-family: "vspd";
+ vertical-align: top;
+ font-weight: normal;
+ background-color: #edeff1;
}
-td.status-good{
- background-color: #C4ECCA;
-}
-td.status-bad{
- background-color: #FEB8A5;
+
+#ticket-table th,
+#ticket-table td {
+ border-top: 1px solid #dee2e6;
}
-.ticket-table th {
+#ticket-table th {
text-align: right;
}
-.ticket-table td {
+#ticket-table td {
font-size: 14px;
text-align: left;
+ padding-right: 0;
+ width: 100%;
+}
+
+#ticket-table details table td {
+ font-size: 12px;
}
-.status-table th,
-.status-table td {
+
+#status-table th,
+#status-table td {
+ border: 1px solid #edeff1;
+ vertical-align: middle;
text-align: center;
-}
\ No newline at end of file
+}
+
+#status-table .center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+#status-table .status {
+ height: 30px;
+ padding-left: 30px;
+}
+
+#status-table .good {
+ background: url(/public/images/success-icon.svg) no-repeat center center;
+}
+
+#status-table .bad {
+ background: url(/public/images/error-icon.svg) no-repeat left center;
+}
+
+#status-table .with-text {
+ padding-left: 40px;
+}
+
+
+.tabset > input {
+ display:block; /* "enable" hidden elements in IE/edge */
+ position:absolute; /* then hide them off-screen */
+ left:-100%;
+}
+
+.tabset > ul {
+ position:relative;
+ z-index:999;
+ list-style:none;
+ display:flex;
+ padding: 0;
+ margin: 0 0 30px;
+}
+
+.tabset > ul label {
+ display:inline-block;
+ padding: 10px 20px;
+}
+
+.tabset > div {
+ position:relative;
+}
+
+.tabset > input:nth-child(1):focus ~ ul li:nth-child(1) label,
+.tabset > input:nth-child(1):hover ~ ul li:nth-child(1) label,
+.tabset > input:nth-child(2):focus ~ ul li:nth-child(2) label,
+.tabset > input:nth-child(2):hover ~ ul li:nth-child(2) label,
+.tabset > input:nth-child(3):focus ~ ul li:nth-child(3) label,
+.tabset > input:nth-child(3):hover ~ ul li:nth-child(3) label,
+.tabset > input:nth-child(4):focus ~ ul li:nth-child(4) label,
+.tabset > input:nth-child(4):hover ~ ul li:nth-child(4) label,
+.tabset > input:nth-child(5):focus ~ ul li:nth-child(5) label,
+.tabset > input:nth-child(5):hover ~ ul li:nth-child(5) label {
+ cursor: pointer;
+ color: #091440;
+}
+
+.tabset > input:nth-child(1):checked ~ ul li:nth-child(1) label,
+.tabset > input:nth-child(2):checked ~ ul li:nth-child(2) label,
+.tabset > input:nth-child(3):checked ~ ul li:nth-child(3) label,
+.tabset > input:nth-child(4):checked ~ ul li:nth-child(4) label,
+.tabset > input:nth-child(5):checked ~ ul li:nth-child(5) label {
+ border-bottom: 5px solid #2ed8a3;
+ color: #091440;
+ cursor: default;
+}
+
+.tabset > div > section,
+.tabset > div > section h2 {
+ position:absolute;
+ top:-999em;
+ left:-999em;
+}
+.tabset > div > section {
+ padding: 0;
+}
+
+.tabset > input:nth-child(1):checked ~ div > section:nth-child(1),
+.tabset > input:nth-child(2):checked ~ div > section:nth-child(2),
+.tabset > input:nth-child(3):checked ~ div > section:nth-child(3),
+.tabset > input:nth-child(4):checked ~ div > section:nth-child(4),
+.tabset > input:nth-child(5):checked ~ div > section:nth-child(5) {
+ position:static;
+}
diff --git a/webapi/public/images/error-icon.svg b/webapi/public/images/error-icon.svg
new file mode 100644
index 00000000..c05d7db3
--- /dev/null
+++ b/webapi/public/images/error-icon.svg
@@ -0,0 +1,7 @@
+
diff --git a/webapi/public/images/success-icon.svg b/webapi/public/images/success-icon.svg
new file mode 100644
index 00000000..0474d7b2
--- /dev/null
+++ b/webapi/public/images/success-icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/webapi/templates/admin.html b/webapi/templates/admin.html
index 67b681e5..e8d6938c 100644
--- a/webapi/templates/admin.html
+++ b/webapi/templates/admin.html
@@ -24,194 +24,260 @@
URL | -Best Block Height | -Daemon Connected | -Unlocked | -Voting | -Vote Version | -||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{{ $host }} | - - {{ if $status.Connected }} - {{ if $status.BestBlockError }} -Error | - {{ else }} -{{ $status.BestBlockHeight }} | - {{ end }} +Error getting wallet info | - {{ else }} -{{ $status.DaemonConnected }} | - - -{{ $status.Unlocked }} | - - -{{ $status.Voting }} | - -{{ $status.VoteVersion }} | - {{ end }} +
URL | +Height | +Connected | +Unlocked | +Voting | +Vote Version | + + + {{ range $host, $status := .WalletStatus }} +||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{{ stripWss $host }} | + + {{ if $status.Connected }} + + {{ if $status.BestBlockError }} +
+
+
+
+ Error
+
+ |
+ {{ else }}
+ {{ $status.BestBlockHeight }} | + {{ end }} + + {{ if $status.InfoError }} +
+
+
+
+ Error getting wallet info
+
+ |
+ {{ else }}
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+ {{ $status.VoteVersion }} | + {{ end }} + + {{else}} +
+
+
+
+ Cannot connect to wallet
+
+ |
+ {{end}}
+
Hash | ++ + {{ .Ticket.Hash }} + + | +|
---|---|---|
Status | + {{ if .Ticket.Confirmed }} ++ Confirmed (purchase height: + {{ .Ticket.PurchaseHeight }}) + | + {{ else }} +Not confirmed | + {{ end }} +
Ticket Outcome | +{{ .Ticket.Outcome }} | +|
Commitment Address | ++ + {{ .Ticket.CommitmentAddress }} + + | +|
Voting WIF | +{{ .Ticket.VotingWIF }} | +
Fee Address | ++ + {{ .Ticket.FeeAddress }} + + | +
---|---|
Fee Address Index | +{{ .Ticket.FeeAddressIndex }} | +
Fee Amount | +{{ atomsToDCR .Ticket.FeeAmount }} | +
Fee Expiration | +{{ .Ticket.FeeExpiration }} ({{ dateTime .Ticket.FeeExpiration }}) | +
Fee Tx Hash | ++ + {{ .Ticket.FeeTxHash }} + + | +
Fee Tx | +{{ .Ticket.FeeTxHex }} | +
Fee Tx Status | +{{ .Ticket.FeeTxStatus }} | +
Current Vote Choices | +
+ {{ range $key, $value := .Ticket.VoteChoices }}
+ {{ $key }}: {{ $value }} + {{ end }} + |
+ ||||||||
---|---|---|---|---|---|---|---|---|---|
+ Vote Choice Changes + ({{ .MaxVoteChanges }} most recent) + |
+
+ {{ range $key, $value := .VoteChanges }}
+
+
+ {{end}}
+ + {{ if eq $key 0}} + Initial choices + {{ else }} + Change {{ $key }} + {{ end }} ++
|
+
Hash | -- - {{ .Ticket.Hash }} - - | -||||||||
---|---|---|---|---|---|---|---|---|---|
Status | - {{ if .Ticket.Confirmed }} -- Confirmed (purchase height: - {{ .Ticket.PurchaseHeight }}) - | {{ else }} -Not confirmed | +|||||||
Commitment Address | -{{ .Ticket.CommitmentAddress }} | -||||||||
Fee Address Index | -{{ .Ticket.FeeAddressIndex }} | -||||||||
Fee Address | -- - {{ .Ticket.FeeAddress }} - - | -||||||||
Fee Amount | -{{ .Ticket.FeeAmount }} atoms | -||||||||
Fee Expiration | -{{ .Ticket.FeeExpiration }} ({{ dateTime .Ticket.FeeExpiration }}) | -||||||||
Current Vote Choices | -
- {{ range $key, $value := .Ticket.VoteChoices }}
- {{ $key }}: {{ $value }} - {{ end }} - |
- ||||||||
- Vote Choice Changes - ({{ .MaxVoteChanges }} most recent) - |
-
- {{ range $key, $value := .VoteChanges }}
-
-
- {{end}}
- - {{ if eq $key 0}} - Initial choices - {{ else }} - Change {{ $key }} - {{ end }} --
|
- ||||||||
Voting WIF | -{{ .Ticket.VotingWIF }} | -||||||||
Fee Tx Hash | -- - {{ .Ticket.FeeTxHash }} - - | -||||||||
Fee Tx | -{{ .Ticket.FeeTxHex }} | -||||||||
Fee Tx Status | -{{ .Ticket.FeeTxStatus }} | -||||||||
Ticket Outcome | -{{ .Ticket.Outcome }} | -
No ticket found with hash {{ .Hash }}
- {{ end }} - {{ end }} + {{ end }} + + +