Skip to content

Commit

Permalink
added table sorting to validators overview
Browse files Browse the repository at this point in the history
  • Loading branch information
pk910 committed Aug 31, 2023
1 parent 3b1420b commit abe43f3
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 40 deletions.
4 changes: 2 additions & 2 deletions handlers/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ func Validator(w http.ResponseWriter, r *http.Request) {
// search by index^
validatorIndex, err := strconv.ParseUint(vars["idxOrPubKey"], 10, 64)
if err == nil && validatorIndex < uint64(len(validatorSetRsp.Data)) {
validator = &validatorSetRsp.Data[validatorIndex]
validator = validatorSetRsp.Data[validatorIndex]
}
} else {
// search by pubkey
for _, val := range validatorSetRsp.Data {
if bytes.Equal(val.Validator.PubKey, validatorPubKey) {
validator = &val
validator = val
break
}
}
Expand Down
77 changes: 68 additions & 9 deletions handlers/validators.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package handlers

import (
"bytes"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -40,35 +42,39 @@ func Validators(w http.ResponseWriter, r *http.Request) {
if urlArgs.Has("q") {
stateFilter = urlArgs.Get("q")
}
data.Data = getValidatorsPageData(firstIdx, pageSize, stateFilter)
var sortOrder string
if urlArgs.Has("o") {
sortOrder = urlArgs.Get("o")
}
data.Data = getValidatorsPageData(firstIdx, pageSize, stateFilter, sortOrder)

if handleTemplateError(w, r, "validators.go", "Validators", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil {
return // an error has occurred and was processed
}
}

func getValidatorsPageData(firstValIdx uint64, pageSize uint64, stateFilter string) *models.ValidatorsPageData {
func getValidatorsPageData(firstValIdx uint64, pageSize uint64, stateFilter string, sortOrder string) *models.ValidatorsPageData {
pageData := &models.ValidatorsPageData{}
pageCacheKey := fmt.Sprintf("validators:%v:%v:%v", firstValIdx, pageSize, stateFilter)
pageCacheKey := fmt.Sprintf("validators:%v:%v:%v:%v", firstValIdx, pageSize, stateFilter, sortOrder)
pageData = services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} {
pageData, cacheTimeout := buildValidatorsPageData(firstValIdx, pageSize, stateFilter)
pageData, cacheTimeout := buildValidatorsPageData(firstValIdx, pageSize, stateFilter, sortOrder)
pageCall.CacheTimeout = cacheTimeout
return pageData
}).(*models.ValidatorsPageData)
return pageData
}

func buildValidatorsPageData(firstValIdx uint64, pageSize uint64, stateFilter string) (*models.ValidatorsPageData, time.Duration) {
logrus.Printf("validators page called: %v:%v:%v", firstValIdx, pageSize, stateFilter)
func buildValidatorsPageData(firstValIdx uint64, pageSize uint64, stateFilter string, sortOrder string) (*models.ValidatorsPageData, time.Duration) {
logrus.Printf("validators page called: %v:%v:%v:%v", firstValIdx, pageSize, stateFilter, sortOrder)
pageData := &models.ValidatorsPageData{}
cacheTime := 10 * time.Minute

// get latest validator set
var validatorSet []rpctypes.ValidatorEntry
var validatorSet []*rpctypes.ValidatorEntry
validatorSetRsp := services.GlobalBeaconService.GetCachedValidatorSet()
if validatorSetRsp == nil {
cacheTime = 5 * time.Minute
validatorSet = []rpctypes.ValidatorEntry{}
validatorSet = []*rpctypes.ValidatorEntry{}
} else {
validatorSet = validatorSetRsp.Data
}
Expand All @@ -77,7 +83,60 @@ func buildValidatorsPageData(firstValIdx uint64, pageSize uint64, stateFilter st
// TODO: apply filter
//}

totalValidatorCount := uint64(len(validatorSet))
// apply sort order
validatorSetLen := len(validatorSet)
if sortOrder == "" {
sortOrder = "index"
}
if sortOrder != "index" {
sortedValidatorSet := make([]*rpctypes.ValidatorEntry, validatorSetLen)
copy(sortedValidatorSet, validatorSet)

switch sortOrder {
case "index-d":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Index > sortedValidatorSet[b].Index
})
case "pubkey":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return bytes.Compare(sortedValidatorSet[a].Validator.PubKey, sortedValidatorSet[b].Validator.PubKey) < 0
})
case "pubkey-d":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return bytes.Compare(sortedValidatorSet[a].Validator.PubKey, sortedValidatorSet[b].Validator.PubKey) > 0
})
case "balance":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Balance < sortedValidatorSet[b].Balance
})
case "balance-d":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Balance > sortedValidatorSet[b].Balance
})
case "activation":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Validator.ActivationEpoch < sortedValidatorSet[b].Validator.ActivationEpoch
})
case "activation-d":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Validator.ActivationEpoch > sortedValidatorSet[b].Validator.ActivationEpoch
})
case "exit":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Validator.ExitEpoch < sortedValidatorSet[b].Validator.ExitEpoch
})
case "exit-d":
sort.Slice(sortedValidatorSet, func(a, b int) bool {
return sortedValidatorSet[a].Validator.ExitEpoch > sortedValidatorSet[b].Validator.ExitEpoch
})
}
validatorSet = sortedValidatorSet
} else {
pageData.IsDefaultSorting = true
}
pageData.Sorting = sortOrder

totalValidatorCount := uint64(validatorSetLen)
if firstValIdx == 0 {
pageData.IsDefaultPage = true
} else if firstValIdx > totalValidatorCount {
Expand Down
2 changes: 1 addition & 1 deletion rpctypes/beaconapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type EpochAssignments struct {
}

type StandardV1StateValidatorsResponse struct {
Data []ValidatorEntry `json:"data"`
Data []*ValidatorEntry `json:"data"`
}

type StandardV1GenesisResponse struct {
Expand Down
8 changes: 6 additions & 2 deletions services/beaconservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,8 +737,12 @@ func (bs *BeaconService) GetValidatorActivity() (map[uint64]uint8, uint64) {
for epochIdx := int64(idxHeadEpoch); epochIdx >= int64(idxMinEpoch); epochIdx-- {
epoch := uint64(epochIdx)
_, epochVotes := bs.indexer.GetEpochVotes(epoch)
for valIdx := range epochVotes.ActivityMap {
activityMap[valIdx]++
if epochVotes == nil {
epochLimit--
} else {
for valIdx := range epochVotes.ActivityMap {
activityMap[valIdx]++
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions static/css/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,18 @@ span.validator-label {
text-overflow: ellipsis;
overflow: hidden;
}

.table-sorting thead .col-sorting {
float: right;
}

.table-sorting thead .sort-link {
margin-left: -3px;
color: var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));
font-size: 14px;
opacity: 0.3;
}

.table-sorting thead .sort-link:hover, .table-sorting thead .sort-link.active {
opacity: 1;
}
53 changes: 43 additions & 10 deletions templates/validators/validators.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ <h1 class="h4 mb-1 mb-md-0"><i class="fas fa-table mx-2"></i> Validators Overvie
{{ if not .IsDefaultPage }}
<input name="s" type="hidden" value="{{ .CurrentPageIndex }}">
{{ end }}
{{ if not .IsDefaultSorting }}
<input name="o" type="hidden" value="{{ .Sorting }}">
{{ end }}
{{ if .StateFilter }}
<input name="q" type="hidden" value="{{ .StateFilter }}">
{{ end }}
Expand All @@ -49,16 +52,46 @@ <h1 class="h4 mb-1 mb-md-0"><i class="fas fa-table mx-2"></i> Validators Overvie
</div>
-->
</div>
<div class="table-responsive px-0 py-1">
<div class="table-responsive table-sorting px-0 py-1">
<table class="table table-nobr" id="validators">
<thead>
<tr>
<th>Index</th>
<th>Public Key</th>
<th>Balance</th>
<th>
Index
<div class="col-sorting">
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=index" class="sort-link {{ if eq .Sorting "index" }}active{{ end }}"><i class="fas fa-arrow-up"></i></a>
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=index-d" class="sort-link {{ if eq .Sorting "index-d" }}active{{ end }}"><i class="fas fa-arrow-down"></i></a>
</div>
</th>
<th>
Public Key
<div class="col-sorting">
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=pubkey" class="sort-link {{ if eq .Sorting "pubkey" }}active{{ end }}"><i class="fas fa-arrow-up"></i></a>
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=pubkey-d" class="sort-link {{ if eq .Sorting "pubkey-d" }}active{{ end }}"><i class="fas fa-arrow-down"></i></a>
</div>
</th>
<th>
Balance
<div class="col-sorting">
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=balance" class="sort-link {{ if eq .Sorting "balance" }}active{{ end }}"><i class="fas fa-arrow-up"></i></a>
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=balance-d" class="sort-link {{ if eq .Sorting "balance-d" }}active{{ end }}"><i class="fas fa-arrow-down"></i></a>
</div>
</th>
<th>State</th>
<th>Activation</th>
<th>Exit</th>
<th>
Activation
<div class="col-sorting">
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=activation" class="sort-link {{ if eq .Sorting "activation" }}active{{ end }}"><i class="fas fa-arrow-up"></i></a>
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=activation-d" class="sort-link {{ if eq .Sorting "activation-d" }}active{{ end }}"><i class="fas fa-arrow-down"></i></a>
</div>
</th>
<th>
Exit
<div class="col-sorting">
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=exit" class="sort-link {{ if eq .Sorting "exit" }}active{{ end }}"><i class="fas fa-arrow-up"></i></a>
<a href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}&o=exit-d" class="sort-link {{ if eq .Sorting "exit-d" }}active{{ end }}"><i class="fas fa-arrow-down"></i></a>
</div>
</th>
<th>W/address</th>
</tr>
</thead>
Expand Down Expand Up @@ -133,19 +166,19 @@ <h1 class="h4 mb-1 mb-md-0"><i class="fas fa-table mx-2"></i> Validators Overvie
<div class="d-inline-block px-2">
<ul class="pagination">
<li class="first paginate_button page-item {{ if le .PrevPageIndex 1 }}disabled{{ end }}" id="tpg_first">
<a tab-index="1" aria-controls="tpg_first" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}c={{ .PageSize }}">First</a>
<a tab-index="1" aria-controls="tpg_first" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}{{ if not .IsDefaultSorting }}o={{ .Sorting }}&{{ end }}c={{ .PageSize }}">First</a>
</li>
<li class="previous paginate_button page-item {{ if eq .PrevPageIndex 0 }}disabled{{ end }}" id="tpg_previous">
<a tab-index="1" aria-controls="tpg_previous" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}s={{ .PrevPageValIdx }}&c={{ .PageSize }}"><i class="fas fa-chevron-left"></i></a>
<a tab-index="1" aria-controls="tpg_previous" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}{{ if not .IsDefaultSorting }}o={{ .Sorting }}&{{ end }}s={{ .PrevPageValIdx }}&c={{ .PageSize }}"><i class="fas fa-chevron-left"></i></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="background-color: transparent;">{{ .CurrentPageIndex }} of {{ .TotalPages }}</a>
</li>
<li class="next paginate_button page-item {{ if eq .NextPageIndex 0 }}disabled{{ end }}" id="tpg_next">
<a tab-index="1" aria-controls="tpg_next" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}s={{ .NextPageValIdx }}&c={{ .PageSize }}"><i class="fas fa-chevron-right"></i></a>
<a tab-index="1" aria-controls="tpg_next" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}{{ if not .IsDefaultSorting }}o={{ .Sorting }}&{{ end }}s={{ .NextPageValIdx }}&c={{ .PageSize }}"><i class="fas fa-chevron-right"></i></a>
</li>
<li class="last paginate_button page-item {{ if not (gt .LastPageValIdx .NextPageValIdx) }}disabled{{ end }}" id="tpg_last">
<a tab-index="1" aria-controls="tpg_last" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}s={{ .LastPageValIdx }}&c={{ .PageSize }}">Last</a>
<a tab-index="1" aria-controls="tpg_last" class="page-link" href="/validators?{{ if .StateFilter }}q={{ .StateFilter }}&{{ end }}{{ if not .IsDefaultSorting }}o={{ .Sorting }}&{{ end }}s={{ .LastPageValIdx }}&c={{ .PageSize }}">Last</a>
</li>
</ul>
</div>
Expand Down
33 changes: 17 additions & 16 deletions types/models/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ import (

// ValidatorsPageData is a struct to hold info for the validators page
type ValidatorsPageData struct {
Validators []*ValidatorsPageDataValidator `json:"validators"`
ValidatorCount uint64 `json:"validator_count"`
FirstValidator uint64 `json:"first_validx"`
LastValidator uint64 `json:"last_validx"`
StateFilter string `json:"state_filter"`

IsDefaultPage bool `json:"default_page"`
TotalPages uint64 `json:"total_pages"`
PageSize uint64 `json:"page_size"`
CurrentPageIndex uint64 `json:"page_index"`
CurrentPageValIdx uint64 `json:"page_validx"`
PrevPageIndex uint64 `json:"prev_page_index"`
PrevPageValIdx uint64 `json:"prev_page_validx"`
NextPageIndex uint64 `json:"next_page_index"`
NextPageValIdx uint64 `json:"next_page_validx"`
LastPageValIdx uint64 `json:"last_page_validx"`
Validators []*ValidatorsPageDataValidator `json:"validators"`
ValidatorCount uint64 `json:"validator_count"`
FirstValidator uint64 `json:"first_validx"`
LastValidator uint64 `json:"last_validx"`
StateFilter string `json:"state_filter"`
Sorting string `json:"sorting"`
IsDefaultSorting bool `json:"default_sorting"`
IsDefaultPage bool `json:"default_page"`
TotalPages uint64 `json:"total_pages"`
PageSize uint64 `json:"page_size"`
CurrentPageIndex uint64 `json:"page_index"`
CurrentPageValIdx uint64 `json:"page_validx"`
PrevPageIndex uint64 `json:"prev_page_index"`
PrevPageValIdx uint64 `json:"prev_page_validx"`
NextPageIndex uint64 `json:"next_page_index"`
NextPageValIdx uint64 `json:"next_page_validx"`
LastPageValIdx uint64 `json:"last_page_validx"`
}

type ValidatorsPageDataValidator struct {
Expand Down

0 comments on commit abe43f3

Please sign in to comment.