Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live frontpage #18

Merged
merged 10 commits into from
Sep 8, 2023
2 changes: 2 additions & 0 deletions cmd/explorer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func startFrontend() {
router := mux.NewRouter()

router.HandleFunc("/", handlers.Index).Methods("GET")
router.HandleFunc("/index", handlers.Index).Methods("GET")
router.HandleFunc("/index/data", handlers.IndexData).Methods("GET")
router.HandleFunc("/clients", handlers.Clients).Methods("GET")
router.HandleFunc("/epochs", handlers.Epochs).Methods("GET")
router.HandleFunc("/epoch/{epoch}", handlers.Epoch).Methods("GET")
Expand Down
2 changes: 1 addition & 1 deletion handlers/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func getClientsPageData() *models.ClientsPageData {
}

func buildClientsPageData() (*models.ClientsPageData, time.Duration) {
logrus.Printf("clients page called")
logrus.Debugf("clients page called")
pageData := &models.ClientsPageData{
Clients: []*models.ClientsPageDataClient{},
}
Expand Down
2 changes: 1 addition & 1 deletion handlers/epoch.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func getEpochPageData(epoch uint64) *models.EpochPageData {
}

func buildEpochPageData(epoch uint64) (*models.EpochPageData, time.Duration) {
logrus.Printf("epoch page called: %v", epoch)
logrus.Debugf("epoch page called: %v", epoch)

now := time.Now()
currentSlot := utils.TimeToSlot(uint64(now.Unix()))
Expand Down
2 changes: 1 addition & 1 deletion handlers/epochs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func getEpochsPageData(firstEpoch uint64, pageSize uint64) *models.EpochsPageDat
}

func buildEpochsPageData(firstEpoch uint64, pageSize uint64) (*models.EpochsPageData, time.Duration) {
logrus.Printf("epochs page called: %v:%v", firstEpoch, pageSize)
logrus.Debugf("epochs page called: %v:%v", firstEpoch, pageSize)
pageData := &models.EpochsPageData{}

now := time.Now()
Expand Down
22 changes: 18 additions & 4 deletions handlers/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package handlers

import (
"bytes"
"encoding/json"
"fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -39,6 +42,16 @@ func Index(w http.ResponseWriter, r *http.Request) {
}
}

func IndexData(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
pageData := getIndexPageData()
err := json.NewEncoder(w).Encode(pageData)
if err != nil {
logrus.WithError(err).Error("error encoding index data")
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
}
}

func getIndexPageData() *models.IndexPageData {
pageData := &models.IndexPageData{}
pageCacheKey := "index"
Expand All @@ -51,7 +64,7 @@ func getIndexPageData() *models.IndexPageData {
}

func buildIndexPageData() (*models.IndexPageData, time.Duration) {
logrus.Printf("index page called")
logrus.Debugf("index page called")

recentEpochCount := 7
recentBlockCount := 7
Expand Down Expand Up @@ -81,10 +94,10 @@ func buildIndexPageData() (*models.IndexPageData, time.Duration) {
NetworkName: utils.Config.Chain.Name,
DepositContract: utils.Config.Chain.Config.DepositContractAddress,
ShowSyncingMessage: !isSynced,
SlotsPerEpoch: utils.Config.Chain.Config.SlotsPerEpoch,
CurrentEpoch: uint64(currentEpoch),
CurrentFinalizedEpoch: finalizedEpoch,
CurrentSlot: currentSlot,
CurrentSlotIndex: currentSlotIndex,
CurrentScheduledCount: utils.Config.Chain.Config.SlotsPerEpoch - currentSlotIndex,
CurrentEpochProgress: float64(100) * float64(currentSlotIndex) / float64(utils.Config.Chain.Config.SlotsPerEpoch),
}
Expand Down Expand Up @@ -191,8 +204,6 @@ func buildIndexPageRecentEpochsData(pageData *models.IndexPageData, currentEpoch
Finalized: finalizedEpoch >= int64(epochData.Epoch),
EligibleEther: epochData.Eligible,
TargetVoted: epochData.VotedTarget,
HeadVoted: epochData.VotedHead,
TotalVoted: epochData.VotedTotal,
VoteParticipation: voteParticipation,
})
}
Expand Down Expand Up @@ -223,6 +234,9 @@ func buildIndexPageRecentBlocksData(pageData *models.IndexPageData, currentSlot
if blockData.EthBlockNumber != nil {
blockModel.WithEthBlock = true
blockModel.EthBlock = *blockData.EthBlockNumber
if utils.Config.Frontend.EthExplorerLink != "" {
blockModel.EthBlockLink, _ = url.JoinPath(utils.Config.Frontend.EthExplorerLink, "block", strconv.FormatUint(blockModel.EthBlock, 10))
}
}
pageData.RecentBlocks = append(pageData.RecentBlocks, blockModel)
}
Expand Down
2 changes: 1 addition & 1 deletion handlers/slot.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func buildSlotPageData(blockSlot int64, blockRoot []byte, loadDuties bool) (*mod
} else {
return nil, -1
}
logrus.Printf("slot page called: %v", slot)
logrus.Debugf("slot page called: %v", slot)

pageData := &models.SlotPageData{
Slot: slot,
Expand Down
2 changes: 1 addition & 1 deletion handlers/slots.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func getSlotsPageData(firstSlot uint64, pageSize uint64) *models.SlotsPageData {
}

func buildSlotsPageData(firstSlot uint64, pageSize uint64) (*models.SlotsPageData, time.Duration) {
logrus.Printf("slots page called: %v:%v", firstSlot, pageSize)
logrus.Debugf("slots page called: %v:%v", firstSlot, pageSize)
pageData := &models.SlotsPageData{}

now := time.Now()
Expand Down
2 changes: 1 addition & 1 deletion handlers/slots_filtered.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func buildFilteredSlotsPageData(pageIdx uint64, pageSize uint64, graffiti string
FilterWithOrphaned: withOrphaned,
FilterWithMissing: withMissing,
}
logrus.Printf("slots_filtered page called: %v:%v [%v]", pageIdx, pageSize, graffiti)
logrus.Debugf("slots_filtered page called: %v:%v [%v]", pageIdx, pageSize, graffiti)
if pageIdx == 0 {
pageData.IsDefaultPage = true
}
Expand Down
2 changes: 1 addition & 1 deletion handlers/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func getValidatorPageData(validatorIndex uint64) *models.ValidatorPageData {
}

func buildValidatorPageData(validatorIndex uint64) (*models.ValidatorPageData, time.Duration) {
logrus.Printf("validator page called: %v", validatorIndex)
logrus.Debugf("validator page called: %v", validatorIndex)

validatorSetRsp := services.GlobalBeaconService.GetCachedValidatorSet()
validator := validatorSetRsp.Data[validatorIndex]
Expand Down
2 changes: 1 addition & 1 deletion handlers/validator_slots.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func buildValidatorSlotsPageData(validator uint64, pageIdx uint64, pageSize uint
Index: validator,
Name: services.GlobalBeaconService.GetValidatorName(validator),
}
logrus.Printf("validator slots page called (%v): %v:%v", validator, pageIdx, pageSize)
logrus.Debugf("validator slots page called (%v): %v:%v", validator, pageIdx, pageSize)
if pageIdx == 0 {
pageData.IsDefaultPage = true
}
Expand Down
2 changes: 1 addition & 1 deletion handlers/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func getValidatorsPageData(firstValIdx uint64, pageSize uint64, sortOrder string
}

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

Expand Down
9 changes: 9 additions & 0 deletions indexer/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type indexerCache struct {
epochStatsMap map[uint64][]*EpochStats
lastValidatorsEpoch int64
lastValidatorsResp *rpctypes.StandardV1StateValidatorsResponse
genesisResp *rpctypes.StandardV1GenesisResponse
validatorLoadingLimiter chan int
}

Expand Down Expand Up @@ -79,6 +80,14 @@ func (cache *indexerCache) setFinalizedHead(epoch int64, root []byte) {
}
}

func (cache *indexerCache) setGenesis(genesis *rpctypes.StandardV1GenesisResponse) {
cache.cacheMutex.Lock()
defer cache.cacheMutex.Unlock()
if cache.genesisResp == nil {
cache.genesisResp = genesis
}
}

func (cache *indexerCache) getFinalizedHead() (int64, []byte) {
cache.cacheMutex.RLock()
defer cache.cacheMutex.RUnlock()
Expand Down
1 change: 1 addition & 0 deletions indexer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func (client *IndexerClient) checkIndexerClient() error {
if genesis.Data.GenesisForkVersion.String() != utils.Config.Chain.Config.GenesisForkVersion {
return fmt.Errorf("genesis fork version from RPC does not match the genesis fork version explorer configuration")
}
client.indexerCache.setGenesis(genesis)

// check syncronization state
syncStatus, err := client.rpcClient.GetNodeSyncing()
Expand Down
4 changes: 4 additions & 0 deletions indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ func (indexer *Indexer) GetRpcClient(archive bool, head []byte) *rpc.BeaconClien
return readyClient.rpcClient
}

func (indexer *Indexer) GetCachedGenesis() *rpctypes.StandardV1GenesisResponse {
return indexer.indexerCache.genesisResp
}

func (indexer *Indexer) GetFinalizedEpoch() (int64, []byte) {
return indexer.indexerCache.getFinalizedHead()
}
Expand Down
2 changes: 1 addition & 1 deletion services/beaconservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (bs *BeaconService) GetCachedEpochStats(epoch uint64) *indexer.EpochStats {
}

func (bs *BeaconService) GetGenesis() (*rpctypes.StandardV1GenesisResponse, error) {
return bs.indexer.GetRpcClient(false, nil).GetGenesis()
return bs.indexer.GetCachedGenesis(), nil
}

func (bs *BeaconService) GetSlotDetailsByBlockroot(blockroot []byte) (*rpctypes.CombinedBlockResponse, error) {
Expand Down
4 changes: 2 additions & 2 deletions services/frontendcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (fc *FrontendCacheService) ProcessCachedPage(pageKey string, caching bool,
processingPage := fc.processingDict[pageKey]
if processingPage != nil {
fc.processingMutex.Unlock()
logrus.Printf("page already processing: %v", pageKey)
logrus.Debugf("page already processing: %v", pageKey)

processingPage.modelMutex.RLock()
defer processingPage.modelMutex.RUnlock()
Expand Down Expand Up @@ -88,7 +88,7 @@ func (fc *FrontendCacheService) completePageLoad(pageKey string, processingPage
func (fc *FrontendCacheService) processCachedPageData(pageKey string, caching bool, pageData interface{}, buildFn func(pageCall *FrontendCacheProcessingPage) interface{}, pageCall *FrontendCacheProcessingPage) interface{} {
// check cache
if !utils.Config.Frontend.Debug && caching && fc.GetFrontendCache(pageKey, pageData) == nil {
logrus.Printf("page served from cache: %v", pageKey)
logrus.Debugf("page served from cache: %v", pageKey)
return pageData
}

Expand Down
4 changes: 4 additions & 0 deletions static/css/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ header nav .nav-item:focus-within .dropdown-menu {
white-space: nowrap;
}

.table .template-row {
display: none;
}

span.validator-label {
white-space: nowrap;
}
Expand Down
80 changes: 58 additions & 22 deletions static/js/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,75 @@
window.setInterval(updateTimers, 1000);
initHeaderSearch();
});
var tooltipDict = {};
var tooltipIdx = 1;
window.explorer = {
initControls: initControls,
renderRecentTime: renderRecentTime,
tooltipDict: tooltipDict,
};

function initControls() {
// init tooltips
var tooltipEls = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.prototype.forEach.call(tooltipEls, function(tooltipEl) {
new bootstrap.Tooltip(tooltipEl)
});
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(initTooltip);
cleanupTooltips();

// init clipboard buttons
var clipboard = new ClipboardJS("[data-clipboard-text]");
clipboard.on("success", function (e) {
var title = e.trigger.getAttribute("data-bs-original-title");
var tooltip = bootstrap.Tooltip.getInstance(e.trigger);
tooltip.setContent({ '.tooltip-inner': 'Copied!' });
tooltip.show();
setTimeout(function () {
tooltip.setContent({ '.tooltip-inner': title });
}, 1000);
});
clipboard.on("error", function (e) {
var title = e.trigger.getAttribute("data-bs-original-title");
var tooltip = bootstrap.Tooltip.getInstance(e.trigger);
tooltip.setContent({ '.tooltip-inner': 'Failed to Copy!' });
tooltip.show();
setTimeout(function () {
tooltip.setContent({ '.tooltip-inner': title });
}, 1000);
document.querySelectorAll("[data-clipboard-text]").forEach(initCopyBtn);
}

function initTooltip(el) {
if($(el).data("tooltip-init"))
return;
//console.log("init tooltip", el);
var idx = tooltipIdx++;
$(el).data("tooltip-init", idx).attr("data-tooltip-idx", idx.toString());
$(el).tooltip();
var tooltip = bootstrap.Tooltip.getInstance(el);
tooltipDict[idx] = {
element: el,
tooltip: tooltip,
};
}

function cleanupTooltips() {
Object.keys(explorer.tooltipDict).forEach(function(idx) {
var ref = explorer.tooltipDict[idx];
if(document.body.contains(ref.element)) return;
ref.tooltip.dispose();
delete explorer.tooltipDict[idx];
});
}

function initCopyBtn(el) {
if($(el).data("clipboard-init"))
return;
$(el).data("clipboard-init", true);
var clipboard = new ClipboardJS(el);
clipboard.on("success", onClipboardSuccess);
clipboard.on("error", onClipboardError);
}

function onClipboardSuccess(e) {
var title = e.trigger.getAttribute("data-bs-original-title");
var tooltip = bootstrap.Tooltip.getInstance(e.trigger);
tooltip.setContent({ '.tooltip-inner': 'Copied!' });
tooltip.show();
setTimeout(function () {
tooltip.setContent({ '.tooltip-inner': title });
}, 1000);
}

function onClipboardError(e) {
var title = e.trigger.getAttribute("data-bs-original-title");
var tooltip = bootstrap.Tooltip.getInstance(e.trigger);
tooltip.setContent({ '.tooltip-inner': 'Failed to Copy!' });
tooltip.show();
setTimeout(function () {
tooltip.setContent({ '.tooltip-inner': title });
}, 1000);
}

function updateTimers() {
var timerEls = document.querySelectorAll("[data-timer]");
timerEls.forEach(function(timerEl) {
Expand Down
Loading