Minor GUI improvements and bugfixes.

Also included is an upgrade from bootstrap 4.5 to 4.6.2. Nothing major changes
in the new version, just some improved compatibility/consistency between
browsers.

- Fixed an issue where long pages (such as ticket search result) would allow the
viewer to scroll beyond the footer.
- Fixed footer stretching to full widescreen size rather than staying inside
  bounds of bootstrap container
- More consistent style for inputs (password prompt, ticket search input)
This commit is contained in:
Jamie Holdstock 2024-11-02 08:19:01 +00:00 committed by GitHub
parent b0fb5469c0
commit 363fdcb224
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 489 additions and 382 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,43 +1,49 @@
html, body { html {
height: 100%; height: 100%;
overflow-x: hidden;
} }
body { body {
background-color: #F3F5F6; background-color: #F3F5F6;
color: #3D5873; width: 100vw;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
/* Bootstrap adds ugly outlines to search inputs. Setting outline-offset and .vsp-page-content {
appearance makes them look the same as text inputs. */ flex: 1 0 auto;
input[type="search"] { color: #3D5873;
outline-offset: 0;
appearance: auto;
} }
.navbar { .vsp-text-orange {
color: #ed6d47;
}
/*
Navbar
*/
.vsp-navbar {
background-color: #F9FAFA; background-color: #F9FAFA;
} }
.navbar a { .vsp-logo-logo {
text-decoration: none;
}
.page-content {
flex: 1 0 auto;
}
.logo--logo {
padding-right: 5px; padding-right: 5px;
border-right: 0.3px solid #091440; border-right: 0.3px solid #091440;
} }
.logo--text {
.vsp-logo-text {
padding: 0 5px; padding: 0 5px;
font-size: 15px; font-size: 15px;
line-height: 1; line-height: 1;
color: #091440; color: #091440;
} }
/*
Text above stats
*/
.vsp-overview { .vsp-overview {
color: #8997A5; color: #8997A5;
background-color: #ffffff; background-color: #ffffff;
@ -49,49 +55,79 @@ input[type="search"] {
font-size: 28px; font-size: 28px;
} }
.vsp-stats .stat-title { /*
VSP stats bar
*/
.vsp-stats .vsp-stat-title {
font-size: 14px; font-size: 14px;
color: #596D81; color: #596D81;
} }
.vsp-stats .stat-value { .vsp-stats .vsp-stat-value {
font-size: 24px; font-size: 24px;
color: #091440; color: #091440;
} }
.vsp-stats .stat-value .text-muted{ .vsp-stats .vsp-stat-value .vsp-stat-subtext {
font-size: 18px; font-size: 18px;
color: #8997A5; color: #8997A5;
} }
.orange { /*
color: #ed6d47; Page footer
} */
footer { .vsp-footer {
flex-shrink: 0; line-height: 1.2rem;
font-size: 0.8rem; font-size: 0.8rem;
position: relative;
background-color: #091440; background-color: #091440;
color: #8997a5; color: #8997a5;
width: 100%;
} }
footer .code { .vsp-footer-bg-left,
word-break: break-word; .vsp-footer-bg-right {
position: absolute;
height: 100%;
} }
.footer__credit { .vsp-footer-bg-right {
background-color: rgba(237,239,241,.1); right: 0;
text-align: right; bottom: 0;
color: #8997a5; background-color: #202a52;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.footer__credit { .vsp-footer-bg-left,
text-align: center; .vsp-footer-bg-right {
height: 50%;
} }
} }
.vsp-footer-left,
.vsp-footer-right {
display: flex;
padding-left: 0;
padding-right: 0;
}
.vsp-footer-left {
background-color: #091440;
}
.vsp-footer-right {
background-color: #202a52;
/* Top & bottom padding to ensure right is same height as left despite
having one less line of text */
padding-top: 0.6rem;
padding-bottom: 0.6rem;
}
/*
Buttons
*/
.btn-primary, .btn-secondary { .btn-primary, .btn-secondary {
outline: 0; outline: 0;
-webkit-box-shadow: 1px 3px 14px 0px rgba(0,0,0,0.19); -webkit-box-shadow: 1px 3px 14px 0px rgba(0,0,0,0.19);
@ -106,30 +142,102 @@ footer .code {
border-color: transparent; border-color: transparent;
} }
.block__content h1 { /*
Admin page collapsible tabset
*/
.vsp-tabset {
background-color: #ffffff;
padding: 20px 30px;
margin-top: 0.5rem;
margin-bottom: 1.5rem;
color: #3D5873;
line-height: 1.4;
width: 100%;
}
.vsp-tabset > ul {
margin: 0 0 25px;
}
.vsp-tabset > ul label {
padding: 10px 20px;
}
.vsp-tabset h1 {
color: #091440; color: #091440;
font-size: 24px; font-size: 24px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.block__content { .vsp-tabset .collapsible-tab-content {
background-color: #ffffff; display: flex;
padding: 20px 30px; flex-wrap: wrap;
color: #3D5873; justify-content: center;
line-height: 1.4; width: 100%;
padding: 0 10px 10px 10px;
} }
/* Highlight labels on hover/focus to indicate they are clickable */
.vsp-tabset > input[type="radio"]:nth-child(1):focus ~ ul li:nth-child(1) label,
.vsp-tabset > input[type="radio"]:nth-child(1):hover ~ ul li:nth-child(1) label,
.vsp-tabset > input[type="radio"]:nth-child(2):focus ~ ul li:nth-child(2) label,
.vsp-tabset > input[type="radio"]:nth-child(2):hover ~ ul li:nth-child(2) label,
.vsp-tabset > input[type="radio"]:nth-child(3):focus ~ ul li:nth-child(3) label,
.vsp-tabset > input[type="radio"]:nth-child(3):hover ~ ul li:nth-child(3) label,
.vsp-tabset > input[type="radio"]:nth-child(4):focus ~ ul li:nth-child(4) label,
.vsp-tabset > input[type="radio"]:nth-child(4):hover ~ ul li:nth-child(4) label,
.vsp-tabset > input[type="radio"]:nth-child(5):focus ~ ul li:nth-child(5) label,
.vsp-tabset > input[type="radio"]:nth-child(5):hover ~ ul li:nth-child(5) label,
.vsp-tabset > input[type="radio"]:nth-child(6):focus ~ ul li:nth-child(6) label,
.vsp-tabset > input[type="radio"]:nth-child(6):hover ~ ul li:nth-child(6) label {
cursor: pointer;
color: #091440;
}
.block__content table td , /* Highlight the label of the currently selected tab */
.block__content table th { .vsp-tabset > input[type="radio"]:nth-child(1):checked ~ ul li:nth-child(1) label,
.vsp-tabset > input[type="radio"]:nth-child(2):checked ~ ul li:nth-child(2) label,
.vsp-tabset > input[type="radio"]:nth-child(3):checked ~ ul li:nth-child(3) label,
.vsp-tabset > input[type="radio"]:nth-child(4):checked ~ ul li:nth-child(4) label,
.vsp-tabset > input[type="radio"]:nth-child(5):checked ~ ul li:nth-child(5) label,
.vsp-tabset > input[type="radio"]:nth-child(6):checked ~ ul li:nth-child(6) label {
border-bottom: 5px solid #2ed8a3;
color: #091440;
cursor: default;
}
/* By default the collapsible tabs should all be hidden */
.vsp-tabset .collapsible-tab {
display: none;
}
/* Show tab when its corresponding radio is checked */
.vsp-tabset > input[type="radio"]:nth-child(1):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(1),
.vsp-tabset > input[type="radio"]:nth-child(2):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(2),
.vsp-tabset > input[type="radio"]:nth-child(3):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(3),
.vsp-tabset > input[type="radio"]:nth-child(4):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(4),
.vsp-tabset > input[type="radio"]:nth-child(5):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(5),
.vsp-tabset > input[type="radio"]:nth-child(6):checked ~ .collapsible-tab-wrapper > .collapsible-tab:nth-child(6) {
display: flex;
}
.vsp-tabset table td ,
.vsp-tabset table th {
padding: 10px 16px; padding: 10px 16px;
} }
.block__content table td {
.vsp-tabset table td,
.vsp-tabset table .btn-link {
font-size: 14px;
}
.vsp-tabset table td {
word-break: break-word; word-break: break-word;
font-family: "vspd-code"; font-family: "vspd-code";
} }
.block__content table th { .vsp-tabset table th {
white-space: nowrap; white-space: nowrap;
font-family: "vspd"; font-family: "vspd";
vertical-align: top; vertical-align: top;
@ -137,148 +245,100 @@ footer .code {
background-color: #edeff1; background-color: #edeff1;
} }
/*
VSP Status tab
*/
#ticket-table th, .vsp-status-tab {
#ticket-table td { flex-direction: row;
border-top: 1px solid #dee2e6;
} }
#ticket-table th { .vsp-status-tab table th,
text-align: right; .vsp-status-tab table td {
}
#ticket-table td {
font-size: 14px;
text-align: left;
padding-right: 0;
width: 100%;
}
#ticket-table details table td {
font-size: 12px;
}
#ticket-table .small-text {
padding: 10px 16px;
font-size: 12px;
}
#ticket-table pre {
color: #3D5873;
font-size: 12px;
white-space: pre-wrap;
}
.vsp-status {
padding: 10px;
}
.vsp-status table {
margin-bottom: 28px;
}
.vsp-status table th,
.vsp-status table td {
border: 1px solid #edeff1; border: 1px solid #edeff1;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
} }
.vsp-status table .center { .vsp-status-tab table .center {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.vsp-status table .status { .vsp-status-tab table .status {
height: 30px; height: 30px;
padding-left: 30px; padding-left: 30px;
} }
.vsp-status table .good { .vsp-status-tab table .good {
background: url(/public/images/success-icon.svg) no-repeat center center; background: url(/public/images/success-icon.svg) no-repeat center center;
} }
.vsp-status table .bad { .vsp-status-tab table .bad {
background: url(/public/images/error-icon.svg) no-repeat left center; background: url(/public/images/error-icon.svg) no-repeat left center;
} }
.vsp-status table .with-text { .vsp-status-tab table .with-text {
padding-left: 40px; padding-left: 40px;
} }
table.missed-tickets th, /*
table.missed-tickets td { Ticket Search tab
*/
.ticket-search-tab input[type="search"] {
font-size: 14px;
font-family: "vspd-code";
}
.ticket-search-tab input[type="search"]::placeholder {
font-family: "vspd";
}
.ticket-search-tab table {
width: 100%;
margin-top: 0.5rem;
margin-bottom: 1.5rem;
}
.ticket-search-tab table th,
.ticket-search-tab table td {
border-top: 1px solid #dee2e6;
}
.ticket-search-tab table th {
text-align: right;
}
.ticket-search-tab table td {
text-align: left;
padding-right: 0;
width: 100%;
}
.ticket-search-tab table details table td {
font-size: 12px;
}
.ticket-search-tab table .small-text {
padding: 10px 16px;
font-size: 12px;
}
.ticket-search-tab table pre {
color: #3D5873;
font-size: 12px;
white-space: pre-wrap;
}
/*
Missed tickets tab
*/
.missed-tickets-tab table th,
.missed-tickets-tab table td {
border: 1px solid #edeff1; border: 1px solid #edeff1;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
} }
.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 25px;
}
.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,
.tabset > input:nth-child(6):focus ~ ul li:nth-child(6) label,
.tabset > input:nth-child(6):hover ~ ul li:nth-child(6) 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,
.tabset > input:nth-child(6):checked ~ ul li:nth-child(6) 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 10px 10px 10px;
}
.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),
.tabset > input:nth-child(6):checked ~ div > section:nth-child(6) {
position:static;
}

View File

@ -15,199 +15,214 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 pt-2 pb-4"> <div class="vsp-tabset">
<div class="block__content"> <input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_1"
hidden
{{ with .SearchResult }}{{ else }}checked{{ end }}
>
<input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_2"
hidden
{{ with .SearchResult }}checked{{ end }}
>
<input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_3"
hidden
>
<input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_4"
hidden
>
<input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_5"
hidden
>
<input
class="d-none"
type="radio"
name="tabset_1"
id="tabset_1_6"
hidden
>
<ul class="d-flex p-0 list-unstyled">
<li><label for="tabset_1_1">VSP Status</label></li>
<li><label for="tabset_1_2">Ticket Search</label></li>
<li><label for="tabset_1_3">Missed Tickets</label></li>
<li><label for="tabset_1_4">Fee X Pubs</label></li>
<li><label for="tabset_1_5">Database</label></li>
<li><label for="tabset_1_6">Logout</label></li>
</ul>
<div class="tabset"> <div class="collapsible-tab-wrapper">
<input
type="radio"
name="tabset_1"
id="tabset_1_1"
hidden
{{ with .SearchResult }}{{ else }}checked{{ end }}
>
<input
type="radio"
name="tabset_1"
id="tabset_1_2"
hidden
{{ with .SearchResult }}checked{{ end }}
>
<input
type="radio"
name="tabset_1"
id="tabset_1_3"
hidden
>
<input
type="radio"
name="tabset_1"
id="tabset_1_4"
hidden
>
<input
type="radio"
name="tabset_1"
id="tabset_1_5"
hidden
>
<input
type="radio"
name="tabset_1"
id="tabset_1_6"
hidden
>
<ul>
<li><label for="tabset_1_1">VSP Status</label></li>
<li><label for="tabset_1_2">Ticket Search</label></li>
<li><label for="tabset_1_3">Missed Tickets</label></li>
<li><label for="tabset_1_4">Fee X Pubs</label></li>
<li><label for="tabset_1_5">Database</label></li>
<li><label for="tabset_1_6">Logout</label></li>
</ul>
<div> <section class="collapsible-tab">
<div class="vsp-status-tab collapsible-tab-content">
<section class="d-flex row justify-content-center"> <div class="p-2">
<h1>Local dcrd</h1>
<div class="vsp-status"> <table>
<h1>Local dcrd</h1> <thead>
<th>URL</th>
<th>Height</th>
</thead>
<tbody>
<tr>
<td>{{ stripWss .DcrdStatus.Host }}</td>
<table class="vsp-status"> {{ if .DcrdStatus.Connected }}
<thead>
<th>URL</th>
<th>Height</th>
</thead>
<tbody>
<tr>
<td>{{ stripWss .DcrdStatus.Host }}</td>
{{ if .DcrdStatus.Connected }} {{ if .DcrdStatus.BestBlockError }}
{{ if .DcrdStatus.BestBlockError }}
<td>
<div class="center">
<div class="status bad center with-text">
Error
</div>
</div>
</td>
{{ else }}
<td>{{ .DcrdStatus.BestBlockHeight }}</td>
{{ end }}
{{else}}
<td> <td>
<div class="center"> <div class="center">
<div class="status bad center with-text"> <div class="status bad center with-text">
Cannot connect Error
</div> </div>
</div> </div>
</td> </td>
{{end}} {{ else }}
</tr> <td>{{ .DcrdStatus.BestBlockHeight }}</td>
</tbody> {{ end }}
</table>
</div>
<div class="vsp-status"> {{else}}
<h1>Voting Wallets</h1> <td>
<div class="center">
<div class="status bad center with-text">
Cannot connect
</div>
</div>
</td>
{{end}}
</tr>
</tbody>
</table>
</div>
<table class="vsp-status"> <div class="p-2">
<thead> <h1>Voting Wallets</h1>
<th>URL</th>
<th>Height</th>
<th>Connected<br />to dcrd</th>
<th>Unlocked</th>
<th>Voting</th>
<th>Vote<br />Version</th>
</thead>
<tbody>
{{ range $host, $status := .WalletStatus }}
<tr>
<td>{{ stripWss $host }}</td>
{{ if $status.Connected }} <table>
<thead>
<th>URL</th>
<th>Height</th>
<th>Connected<br />to dcrd</th>
<th>Unlocked</th>
<th>Voting</th>
<th>Vote<br />Version</th>
</thead>
<tbody>
{{ range $host, $status := .WalletStatus }}
<tr>
<td>{{ stripWss $host }}</td>
{{ if $status.BestBlockError }} {{ if $status.Connected }}
<td>
<div class="center">
<div class="status bad center with-text">
Error
</div>
</div>
</td>
{{ else }}
<td>{{ $status.BestBlockHeight }}</td>
{{ end }}
{{ if $status.InfoError }} {{ if $status.BestBlockError }}
<td colspan="4"> <td>
<div class="center">
<div class="status bad center with-text">
Error getting wallet info
</div>
</div>
</td>
{{ else }}
<td>
<div class="center">
<div class="status {{ if $status.DaemonConnected }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>
<div class="center">
<div class="status {{ if $status.Unlocked }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>
<div class="center">
<div class="status {{ if $status.Voting }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>{{ $status.VoteVersion }}</td>
{{ end }}
{{else}}
<td colspan="5">
<div class="center"> <div class="center">
<div class="status bad center with-text"> <div class="status bad center with-text">
Cannot connect to wallet Error
</div> </div>
</div> </div>
</td> </td>
{{end}} {{ else }}
</tr> <td>{{ $status.BestBlockHeight }}</td>
{{ end }}
{{ if $status.InfoError }}
<td colspan="4">
<div class="center">
<div class="status bad center with-text">
Error getting wallet info
</div>
</div>
</td>
{{ else }}
<td>
<div class="center">
<div class="status {{ if $status.DaemonConnected }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>
<div class="center">
<div class="status {{ if $status.Unlocked }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>
<div class="center">
<div class="status {{ if $status.Voting }}good{{else}}bad{{end}}"></div>
</div>
</td>
<td>{{ $status.VoteVersion }}</td>
{{ end }}
{{else}}
<td colspan="5">
<div class="center">
<div class="status bad center with-text">
Cannot connect to wallet
</div>
</div>
</td>
{{end}} {{end}}
</tbody> </tr>
</table> {{end}}
</div> </tbody>
</table>
</div>
</div>
</section>
</section> <section class="collapsible-tab">
<div class="ticket-search-tab collapsible-tab-content">
<section> <div class="p-2">
<form class="mt-2 mb-4" action="/admin/ticket" method="post"> <form class="form-inline" action="/admin/ticket" method="post">
<input type="search" name="hash" size="64" minlength="64" maxlength="64" spellcheck="false" required placeholder="Ticket hash" autocomplete="off" <input class="form-control mx-3" type="search" name="hash" size="64" minlength="64" maxlength="64" spellcheck="false" required placeholder="Ticket hash" autocomplete="off"
{{ with .SearchResult }} {{ with .SearchResult }}
value="{{ .Ticket.Hash }}" value="{{ .Ticket.Hash }}"
{{ end }}> {{ end }}>
<button class="btn btn-primary d-block my-2" type="submit">Search</button> <button class="btn btn-primary" type="submit">Search</button>
</form> </form>
</div>
<div class="p-2">
{{ with .SearchResult }} {{ with .SearchResult }}
{{ template "ticket-search-result" . }} {{ template "ticket-search-result" . }}
{{ end }} {{ end }}
</section> </div>
<section> </div>
</section>
<section class="collapsible-tab">
<div class="missed-tickets-tab collapsible-tab-content">
<div class="p-2">
<h1>{{ pluralize (len .MissedTickets) "Missed Ticket" }}</h1> <h1>{{ pluralize (len .MissedTickets) "Missed Ticket" }}</h1>
{{ with .MissedTickets }} {{ with .MissedTickets }}
<table class="missed-tickets mx-auto"> <table class="mx-auto">
<thead> <thead>
<th>Purchase Height</th> <th>Purchase Height</th>
<th>Ticket Hash</th> <th>Ticket Hash</th>
@ -227,9 +242,15 @@
</tbody> </tbody>
</table> </table>
{{ end}} {{ end}}
</section> </div>
<section> </div>
</section>
<section class="collapsible-tab">
<div class="collapsible-tab-content">
<div class="p-2">
<h1>All X Pubs</h1> <h1>All X Pubs</h1>
{{ with .XPubs }} {{ with .XPubs }}
<table class="mx-auto"> <table class="mx-auto">
@ -251,23 +272,36 @@
</tbody> </tbody>
</table> </table>
{{ end}} {{ end}}
</section> </div>
<section> </div>
</section>
<section class="collapsible-tab">
<div class="collapsible-tab-content">
<div class="p-2">
<p>Database size: {{ .WebApiCache.DatabaseSize }}</p> <p>Database size: {{ .WebApiCache.DatabaseSize }}</p>
<a class="btn btn-primary" href="/admin/backup" download>Download Backup</a> <a class="btn btn-primary" href="/admin/backup" download>Download Backup</a>
</section> </div>
<section> </div>
</section>
<section class="collapsible-tab">
<div class="collapsible-tab-content">
<div class="p-2">
<form action="/admin/logout" method="post"> <form action="/admin/logout" method="post">
<button type="submit" class="btn btn-primary">Logout</button> <button type="submit" class="btn btn-primary">Logout</button>
</form> </form>
</section> </div>
</div> </div>
</div> </section>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,25 +1,36 @@
{{ define "footer" }} {{ define "footer" }}
</div> <!-- page-content --> </div> <!-- vsp-page-content -->
<footer class="row m-0"> <footer class="vsp-footer flex-md-row flex-row-reverse">
<div class="col-md-8 col-12 d-flex justify-content-center align-items-center"> <div class="vsp-footer-bg-left col-md-6 col-12"></div>
<div class="vsp-footer-bg-right col-md-6 col-12"></div>
<div class="container d-flex flex-wrap">
<div class="vsp-footer-left col-md-8 col-12 justify-content-center justify-content-md-start">
<div class="d-inline-block text-left">
<p class="py-4 m-0"> <p class="py-4 m-0">
<strong>Stats&nbsp;updated:</strong>&nbsp;{{ timeAgo .WebApiCache.UpdateTime }} <strong>Stats&nbsp;updated:</strong>&nbsp;{{ timeAgo .WebApiCache.UpdateTime }}
<br /> <br />
<strong>Support:</strong>&nbsp;<a href="mailto:{{ .WebApiCfg.SupportEmail }}" rel="noopener noreferrer">{{ .WebApiCfg.SupportEmail }}</a> <strong>Support:</strong>&nbsp;<a href="mailto:{{ .WebApiCfg.SupportEmail }}" rel="noopener noreferrer">{{ .WebApiCfg.SupportEmail }}</a>
<br /> <br />
<strong>VSP&nbsp;public&nbsp;key:</strong>&nbsp;<span class="code">{{ .WebApiCache.PubKey }}</span> <strong>VSP&nbsp;public&nbsp;key:</strong>&nbsp;<span class="code text-break">{{ .WebApiCache.PubKey }}</span>
</p> </p>
</div>
</div> </div>
<div class="col-md-4 col-12 footer__credit d-flex justify-content-center align-items-center"> <div class="vsp-footer-right col-md-4 col-12 justify-content-center justify-content-md-end">
<div class="d-block text-center text-md-right">
<p class="py-4 m-0"> <p class="py-4 m-0">
Decred Developers | 2020-2024 Decred Developers | 2020-2024
<br /> <br />
The source code is available on <a href="https://github.com/decred/vspd" target="_blank" rel="noopener noreferrer">GitHub</a> The source code is available on <a href="https://github.com/decred/vspd" target="_blank" rel="noopener noreferrer">GitHub</a>
</p> </p>
</div>
</div> </div>
</div>
</footer> </footer>
</body> </body>

View File

@ -7,7 +7,7 @@
<title>Decred VSP - {{ .WebApiCfg.Designation }}</title> <title>Decred VSP - {{ .WebApiCfg.Designation }}</title>
<link rel="stylesheet" href="/public/css/vendor/bootstrap-4.5.0.min.css" /> <link rel="stylesheet" href="/public/css/vendor/bootstrap-4.6.2.min.css" />
<link rel="stylesheet" href="/public/css/vspd.css?v={{ .WebApiCfg.VspdVersion }}" /> <link rel="stylesheet" href="/public/css/vspd.css?v={{ .WebApiCfg.VspdVersion }}" />
<!-- fonts.css should be last to ensure dcr fonts take precedence. --> <!-- fonts.css should be last to ensure dcr fonts take precedence. -->
<link rel="stylesheet" href="/public/css/fonts.css?v={{ .WebApiCfg.VspdVersion }}" /> <link rel="stylesheet" href="/public/css/fonts.css?v={{ .WebApiCfg.VspdVersion }}" />
@ -43,12 +43,12 @@
</head> </head>
<body> <body>
<div class="page-content"> <div class="vsp-page-content">
<nav class="py-sm-3 px-sm-5 navbar"> <nav class="py-sm-3 px-sm-5 navbar vsp-navbar">
<div class="container"> <div class="container">
<a class="py-1 border-0 d-flex justify-content-start align-items-center" href="/"> <a class="text-decoration-none py-1 border-0 d-flex justify-content-start align-items-center" href="/">
<div class="logo--logo"> <div class="vsp-logo-logo">
<svg xmlns="http://www.w3.org/2000/svg" width="38" height="32" fill="none" viewBox="0 0 23 20"> <svg xmlns="http://www.w3.org/2000/svg" width="38" height="32" fill="none" viewBox="0 0 23 20">
<path fill="#000" fill-opacity="0" d="M0 0h23v19.167H0z" /> <path fill="#000" fill-opacity="0" d="M0 0h23v19.167H0z" />
<path fill="#091440" <path fill="#091440"
@ -57,7 +57,7 @@
d="M18.208 14.375L23 19.167h-5.175L10.158 11.5h5.175a3.834 3.834 0 0 0 0-7.667h-1.917L9.583 0h5.75A7.667 7.667 0 0 1 23 7.667c0 3.109-1.533 5.615-4.792 6.708z" /> d="M18.208 14.375L23 19.167h-5.175L10.158 11.5h5.175a3.834 3.834 0 0 0 0-7.667h-1.917L9.583 0h5.75A7.667 7.667 0 0 1 23 7.667c0 3.109-1.533 5.615-4.792 6.708z" />
</svg> </svg>
</div> </div>
<div class="logo--text"> <div class="vsp-logo-text">
VSP<br> VSP<br>
<b>{{ .WebApiCfg.Designation }}</b> <b>{{ .WebApiCfg.Designation }}</b>
</div> </div>

View File

@ -2,13 +2,13 @@
<div class="py-4 container"> <div class="py-4 container">
<h1>Login</h1> <h1>Login</h1>
<form class="py-3" action="/admin" method="post"> <form action="/admin" method="post">
<input type="password" name="password" autofocus required placeholder="Enter password"> <input class="form-control w-auto my-2" type="password" name="password" autofocus required placeholder="Enter password">
<p class="my-1 orange" style="visibility:{{ if .FailedLoginMsg }}visible{{ else }}hidden{{ end }};">{{ .FailedLoginMsg }}</p> <p class="my-1 vsp-text-orange" style="visibility:{{ if .FailedLoginMsg }}visible{{ else }}hidden{{ end }};">{{ .FailedLoginMsg }}</p>
<button class="btn btn-primary" type="submit">Login</button> <button class="btn btn-primary my-2" type="submit">Login</button>
</form> </form>
</div> </div>

View File

@ -2,9 +2,11 @@
{{ if .Found }} {{ if .Found }}
<hr />
<h1>Ticket</h1> <h1>Ticket</h1>
<table id="ticket-table" class="mt-2 mb-4 w-100"> <table>
<tr> <tr>
<th>Hash</th> <th>Hash</th>
<td> <td>
@ -44,7 +46,7 @@
<h1>Fee</h1> <h1>Fee</h1>
<table id="ticket-table" class="mt-2 mb-4 w-100"> <table>
<tr> <tr>
<th>Fee Address</th> <th>Fee Address</th>
<td> <td>
@ -97,7 +99,7 @@
<h1>Vote Choices</h1> <h1>Vote Choices</h1>
<table id="ticket-table" class="mt-2 mb-4 w-100"> <table>
<tr> <tr>
<th>Consensus Vote Choices</th> <th>Consensus Vote Choices</th>
<td> <td>
@ -163,7 +165,7 @@
<h1>Alternate Signing Address</h1> <h1>Alternate Signing Address</h1>
<table id="ticket-table" class="mt-2 mb-4 w-100"> <table>
<tr> <tr>
<th>Alternate Signing Address</th> <th>Alternate Signing Address</th>
<td> <td>
@ -206,6 +208,6 @@
</table> </table>
{{ else }} {{ else }}
<p>No ticket found with hash <span class="code">{{ .Hash }}</span></p> <p class="vsp-text-orange">No ticket found with hash <span class="code">{{ .Hash }}</span></p>
{{ end }} {{ end }}
{{ end }} {{ end }}

View File

@ -3,39 +3,39 @@
<div class="row vsp-stats"> <div class="row vsp-stats">
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Live tickets</div> <div class="vsp-stat-title">Live tickets</div>
<div class="stat-value">{{ comma .WebApiCache.Voting }}</div> <div class="vsp-stat-value">{{ comma .WebApiCache.Voting }}</div>
</div> </div>
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Voted tickets</div> <div class="vsp-stat-title">Voted tickets</div>
<div class="stat-value">{{ comma .WebApiCache.Voted }}</div> <div class="vsp-stat-value">{{ comma .WebApiCache.Voted }}</div>
</div> </div>
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Expired tickets</div> <div class="vsp-stat-title">Expired tickets</div>
<div class="stat-value"> <div class="vsp-stat-value">
{{ comma .WebApiCache.Expired }} {{ comma .WebApiCache.Expired }}
<span class="text-muted">({{ float32ToPercent .WebApiCache.ExpiredProportion }})</span> <span class="vsp-stat-subtext">({{ float32ToPercent .WebApiCache.ExpiredProportion }})</span>
</div> </div>
</div> </div>
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Missed tickets</div> <div class="vsp-stat-title">Missed tickets</div>
<div class="stat-value"> <div class="vsp-stat-value">
{{ comma .WebApiCache.Missed }} {{ comma .WebApiCache.Missed }}
<span class="text-muted">({{ float32ToPercent .WebApiCache.MissedProportion }})</span> <span class="vsp-stat-subtext">({{ float32ToPercent .WebApiCache.MissedProportion }})</span>
</div> </div>
</div> </div>
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">VSP Fee</div> <div class="vsp-stat-title">VSP Fee</div>
<div class="stat-value">{{ .WebApiCfg.VSPFee }}%</div> <div class="vsp-stat-value">{{ .WebApiCfg.VSPFee }}%</div>
</div> </div>
<div class="col-6 col-sm-4 col-lg-2 py-3"> <div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Network Proportion</div> <div class="vsp-stat-title">Network Proportion</div>
<div class="stat-value">{{ float32ToPercent .WebApiCache.NetworkProportion }}</div> <div class="vsp-stat-value">{{ float32ToPercent .WebApiCache.NetworkProportion }}</div>
</div> </div>
</div> </div>