webapi: Add missed tickets to admin page.
A new tab on the admin page displays a list of all tickets which were registered with the VSP but missed their votes. Clicking on the ticket hash redirects to the Ticket Search tab with the details of the missed ticket displayed.
This commit is contained in:
parent
d960898987
commit
a254e943f7
@ -6,6 +6,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
@ -116,6 +117,12 @@ func (t TicketList) EarliestPurchaseHeight() int64 {
|
|||||||
return oldestHeight
|
return oldestHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t TicketList) SortByPurchaseHeight() {
|
||||||
|
sort.Slice(t, func(i, j int) bool {
|
||||||
|
return t[i].PurchaseHeight > t[j].PurchaseHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Ticket) FeeExpired() bool {
|
func (t *Ticket) FeeExpired() bool {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return now.After(time.Unix(t.FeeExpiration, 0))
|
return now.After(time.Unix(t.FeeExpiration, 0))
|
||||||
@ -385,6 +392,13 @@ func (vdb *VspDatabase) GetMissingPurchaseHeight() (TicketList, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMissedTickets returns all tickets which have outcome == missed.
|
||||||
|
func (vdb *VspDatabase) GetMissedTickets() (TicketList, error) {
|
||||||
|
return vdb.filterTickets(func(t *bolt.Bucket) bool {
|
||||||
|
return TicketOutcome(t.Get(outcomeK)) == Missed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// filterTickets accepts a filter function and returns all tickets from the
|
// filterTickets accepts a filter function and returns all tickets from the
|
||||||
// database which match the filter.
|
// database which match the filter.
|
||||||
func (vdb *VspDatabase) filterTickets(filter func(*bolt.Bucket) bool) (TicketList, error) {
|
func (vdb *VspDatabase) filterTickets(filter func(*bolt.Bucket) bool) (TicketList, error) {
|
||||||
|
|||||||
@ -146,11 +146,21 @@ func (w *WebAPI) statusJSON(c *gin.Context) {
|
|||||||
func (w *WebAPI) adminPage(c *gin.Context) {
|
func (w *WebAPI) adminPage(c *gin.Context) {
|
||||||
cacheData := c.MustGet(cacheKey).(cacheData)
|
cacheData := c.MustGet(cacheKey).(cacheData)
|
||||||
|
|
||||||
|
missed, err := w.db.GetMissedTickets()
|
||||||
|
if err != nil {
|
||||||
|
w.log.Errorf("db.GetMissedTickets error: %v", err)
|
||||||
|
c.String(http.StatusInternalServerError, "Error getting missed tickets from db")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
missed.SortByPurchaseHeight()
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "admin.html", gin.H{
|
c.HTML(http.StatusOK, "admin.html", gin.H{
|
||||||
"WebApiCache": cacheData,
|
"WebApiCache": cacheData,
|
||||||
"WebApiCfg": w.cfg,
|
"WebApiCfg": w.cfg,
|
||||||
"WalletStatus": w.walletStatus(c),
|
"WalletStatus": w.walletStatus(c),
|
||||||
"DcrdStatus": w.dcrdStatus(c),
|
"DcrdStatus": w.dcrdStatus(c),
|
||||||
|
"MissedTickets": missed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +222,15 @@ func (w *WebAPI) ticketSearch(c *gin.Context) {
|
|||||||
feeTxDecoded = string(decoded)
|
feeTxDecoded = string(decoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
missed, err := w.db.GetMissedTickets()
|
||||||
|
if err != nil {
|
||||||
|
w.log.Errorf("db.GetMissedTickets error: %v", err)
|
||||||
|
c.String(http.StatusInternalServerError, "Error getting missed tickets from db")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
missed.SortByPurchaseHeight()
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "admin.html", gin.H{
|
c.HTML(http.StatusOK, "admin.html", gin.H{
|
||||||
"SearchResult": searchResult{
|
"SearchResult": searchResult{
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
@ -226,6 +245,7 @@ func (w *WebAPI) ticketSearch(c *gin.Context) {
|
|||||||
"WebApiCfg": w.cfg,
|
"WebApiCfg": w.cfg,
|
||||||
"WalletStatus": w.walletStatus(c),
|
"WalletStatus": w.walletStatus(c),
|
||||||
"DcrdStatus": w.dcrdStatus(c),
|
"DcrdStatus": w.dcrdStatus(c),
|
||||||
|
"MissedTickets": missed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,3 +62,16 @@ func atomsToDCR(atoms int64) string {
|
|||||||
func float32ToPercent(input float32) string {
|
func float32ToPercent(input float32) string {
|
||||||
return fmt.Sprintf("%.2f%%", input*100)
|
return fmt.Sprintf("%.2f%%", input*100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pluralize suffixes the provided noun with "s" if n is not 1, then
|
||||||
|
// concatenates n and noun with a space between them. For example:
|
||||||
|
//
|
||||||
|
// (0, "biscuit") will return "0 biscuits"
|
||||||
|
// (1, "biscuit") will return "1 biscuit"
|
||||||
|
// (3, "biscuit") will return "3 biscuits"
|
||||||
|
func pluralize(n int, noun string) string {
|
||||||
|
if n != 1 {
|
||||||
|
noun += "s"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d %s", n, noun)
|
||||||
|
}
|
||||||
|
|||||||
@ -92,7 +92,7 @@ footer .code {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.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);
|
||||||
box-shadow: 1px 3px 14px 0px rgba(0,0,0,0.19);
|
box-shadow: 1px 3px 14px 0px rgba(0,0,0,0.19);
|
||||||
@ -206,6 +206,12 @@ footer .code {
|
|||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.missed-tickets th,
|
||||||
|
table.missed-tickets td {
|
||||||
|
border: 1px solid #edeff1;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.tabset > input {
|
.tabset > input {
|
||||||
display:block; /* "enable" hidden elements in IE/edge */
|
display:block; /* "enable" hidden elements in IE/edge */
|
||||||
|
|||||||
@ -45,11 +45,18 @@
|
|||||||
id="tabset_1_4"
|
id="tabset_1_4"
|
||||||
hidden
|
hidden
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="tabset_1"
|
||||||
|
id="tabset_1_5"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
<ul>
|
<ul>
|
||||||
<li><label for="tabset_1_1">VSP Status</label></li>
|
<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_2">Ticket Search</label></li>
|
||||||
<li><label for="tabset_1_3">Database</label></li>
|
<li><label for="tabset_1_3">Missed Tickets</label></li>
|
||||||
<li><label for="tabset_1_4">Logout</label></li>
|
<li><label for="tabset_1_4">Database</label></li>
|
||||||
|
<li><label for="tabset_1_5">Logout</label></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -190,6 +197,31 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>{{ pluralize (len .MissedTickets) "Missed Ticket" }}</h1>
|
||||||
|
{{ with .MissedTickets }}
|
||||||
|
<table class="missed-tickets mx-auto">
|
||||||
|
<thead>
|
||||||
|
<th>Purchase Height</th>
|
||||||
|
<th>Ticket Hash</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range . }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .PurchaseHeight }}</td>
|
||||||
|
<td>
|
||||||
|
<form action="/admin/ticket" method="post">
|
||||||
|
<input type="hidden" name="hash" value="{{ .Hash }}">
|
||||||
|
<button class="btn btn-link p-0 code" type="submit">{{ .Hash }}</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ end}}
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<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>
|
||||||
|
|||||||
@ -217,6 +217,7 @@ func (w *WebAPI) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
|
|||||||
"atomsToDCR": atomsToDCR,
|
"atomsToDCR": atomsToDCR,
|
||||||
"float32ToPercent": float32ToPercent,
|
"float32ToPercent": float32ToPercent,
|
||||||
"comma": humanize.Comma,
|
"comma": humanize.Comma,
|
||||||
|
"pluralize": pluralize,
|
||||||
})
|
})
|
||||||
|
|
||||||
router.LoadHTMLGlob("internal/webapi/templates/*.html")
|
router.LoadHTMLGlob("internal/webapi/templates/*.html")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user