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 @@

Admin Panel

-
+
-

Voting Wallet Status

- - - - - - - - - - - {{ range $host, $status := .WalletStatus }} - - - - {{ if $status.Connected }} - {{ if $status.BestBlockError }} - - {{ else }} - - {{ end }} +
+ + +
    +
  • +
  • +
+ +
- {{ if $status.InfoError }} -
- {{ else }} - - - - - - - - - - {{ end }} +
+
URLBest Block HeightDaemon ConnectedUnlockedVotingVote Version
{{ $host }}Error{{ $status.BestBlockHeight }}Error getting wallet info{{ $status.DaemonConnected }}{{ $status.Unlocked }}{{ $status.Voting }}{{ $status.VoteVersion }}
+ + + + + + + + + + {{ range $host, $status := .WalletStatus }} + + + + {{ if $status.Connected }} + + {{ if $status.BestBlockError }} + + {{ else }} + + {{ end }} + + {{ if $status.InfoError }} + + {{ else }} + + + + + + + + + + {{ end }} + + {{else}} + + {{end}} + + {{end}} + +
URLHeightConnectedUnlockedVotingVote Version
{{ stripWss $host }} +
+
+ Error +
+
+
{{ $status.BestBlockHeight }} +
+
+ Error getting wallet info +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $status.VoteVersion }} +
+
+ Cannot connect to wallet +
+
+
+ + +
+
+ + +
+ + {{ with .SearchResult }} + {{ if .Found }} - {{else}} - Cannot connect to wallet - {{end}} - - {{end}} - +

Ticket

-
-
- -
-
+ + + + + + + + {{ if .Ticket.Confirmed }} + + {{ else }} + + {{ end }} + + + + + + + + + + + + + +
Hash + + {{ .Ticket.Hash }} + +
Status + Confirmed (purchase height: + {{ .Ticket.PurchaseHeight }}) + Not confirmed
Ticket Outcome{{ .Ticket.Outcome }}
Commitment Address + + {{ .Ticket.CommitmentAddress }} + +
Voting WIF{{ .Ticket.VotingWIF }}
-
-
+

Fee

-
-
-

Ticket Search

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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 }}
-
- - -
+

Voting

+ + + + + + + + + + +
Current Vote Choices + {{ range $key, $value := .Ticket.VoteChoices }} + {{ $key }}: {{ $value }}
+ {{ end }} +
+ Vote Choice Changes
+ ({{ .MaxVoteChanges }} most recent) +
+ {{ range $key, $value := .VoteChanges }} +
+ + {{ if eq $key 0}} + Initial choices + {{ else }} + Change {{ $key }} + {{ end }} + + + + + + + + + + + + + + + + + + +
Request{{ indentJSON $value.Request }}
Request
Signature
{{ $value.RequestSignature }}
Response{{ indentJSON $value.Response }}
Response
Signature
{{ $value.ResponseSignature }}
+
+ {{end}} +
- {{ with .SearchResult }} - {{ if .Found }} -

Search Result

- - - - - - - - {{ if .Ticket.Confirmed }} - {{ else }} - +

No ticket found with hash {{ .Hash }}

{{ end }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hash - - {{ .Ticket.Hash }} - -
Status - Confirmed (purchase height: - {{ .Ticket.PurchaseHeight }}) - 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 }} -
- - {{ if eq $key 0}} - Initial choices - {{ else }} - Change {{ $key }} - {{ end }} - - - - - - - - - - - - - - - - - - -
Request{{ $value.Request }}
Request
Signature
{{ $value.RequestSignature }}
Response{{ $value.Response }}
Response
Signature
{{ $value.ResponseSignature }}
-
- {{end}} -
Voting WIF{{ .Ticket.VotingWIF }}
Fee Tx Hash - - {{ .Ticket.FeeTxHash }} - -
Fee Tx{{ .Ticket.FeeTxHex }}
Fee Tx Status{{ .Ticket.FeeTxStatus }}
Ticket Outcome{{ .Ticket.Outcome }}
- {{ else }} -

No ticket found with hash {{ .Hash }}

- {{ end }} - {{ end }} + {{ end }} + + +
+
diff --git a/webapi/templates/header.html b/webapi/templates/header.html index e93eda46..4409d050 100644 --- a/webapi/templates/header.html +++ b/webapi/templates/header.html @@ -67,7 +67,7 @@ {{ if .VspStats.Debug }}
- diff --git a/webapi/templates/homepage.html b/webapi/templates/homepage.html index ed298b9c..a0faec90 100644 --- a/webapi/templates/homepage.html +++ b/webapi/templates/homepage.html @@ -4,7 +4,7 @@
{{ if .VspStats.VspClosed }} -