Small improvements on the mining page UX
- INDEXING_BLOCKS_AMOUNT = 0 disable indexing, INDEXING_BLOCKS_AMOUNT = -1 indexes everything - Show only available timespan in the mining page according to available datas - Change default INDEXING_BLOCKS_AMOUNT to 1100 Don't use unfiltered mysql user input Enable http cache header for mining pools (1 min)
This commit is contained in:
@@ -60,7 +60,7 @@ let routes: Routes = [
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'pools',
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
@@ -147,6 +147,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
@@ -225,6 +229,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-pools">
|
||||
<a class="nav-link" [routerLink]="['/mining/pools' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="master-page.pools" title="Pools"></fa-icon></a>
|
||||
<a class="nav-link" [routerLink]="['/mining/pools' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="master-page.mining-pools" title="Mining Pools"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
||||
|
||||
@@ -7,37 +7,37 @@
|
||||
</div>
|
||||
|
||||
<div class="card-header mb-0 mb-lg-4">
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup">
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(miningStatsObservable$ | async) as miningStats">
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1">
|
||||
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="24h"> 24h
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 3">
|
||||
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3d"> 3D
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 7">
|
||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1w"> 1W
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 30">
|
||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1m"> 1M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 90">
|
||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3m"> 3M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 180">
|
||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="6m"> 6M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 365">
|
||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1y"> 1Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 730">
|
||||
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="2y"> 2Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095">
|
||||
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3y"> 3Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/pools' | relativeUrl]" fragment="3d"> 3D
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/pools' | relativeUrl]" fragment="1w"> 1W
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/pools' | relativeUrl]" fragment="1m"> 1M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/pools' | relativeUrl]" fragment="3m"> 3M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/pools' | relativeUrl]" fragment="6m"> 6M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/pools' | relativeUrl]" fragment="1y"> 1Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/pools' | relativeUrl]" fragment="2y"> 2Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/pools' | relativeUrl]" fragment="3y"> 3Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/pools' | relativeUrl]" fragment="all"> ALL
|
||||
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="all"> ALL
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -46,31 +46,31 @@
|
||||
<table class="table table-borderless text-center pools-table" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="d-none d-md-block" i18n="latest-blocks.height">Rank</th>
|
||||
<th class="d-none d-md-block" i18n="mining.rank">Rank</th>
|
||||
<th class=""></th>
|
||||
<th class="" i18n="latest-blocks.poolName">Name</th>
|
||||
<th class="" *ngIf="this.poolsWindowPreference === '1d'" i18n="latest-blocks.timestamp">Hashrate</th>
|
||||
<th class="" i18n="latest-blocks.mined">Blocks</th>
|
||||
<th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks</th>
|
||||
<th class="" i18n="mining.pool-name">Name</th>
|
||||
<th class="" *ngIf="this.poolsWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
|
||||
<th class="" i18n="master-page.blocks">Blocks</th>
|
||||
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody *ngIf="(miningStatsObservable$ | async) as miningStats">
|
||||
<tr>
|
||||
<td class="d-none d-md-block">-</td>
|
||||
<td class="text-right"><img width="25" height="25" src="./resources/mining-pools/default.svg"></td>
|
||||
<td class="">All miners</td>
|
||||
<td class="" *ngIf="this.poolsWindowPreference === '1d'">{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
||||
<td class="">{{ miningStats.blockCount }}</td>
|
||||
<td class="d-none d-md-block">{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</td>
|
||||
</tr>
|
||||
<tr *ngFor="let pool of miningStats.pools">
|
||||
<td class="d-none d-md-block">{{ pool.rank }}</td>
|
||||
<td class="text-right"><img width="25" height="25" src="{{ pool.logo }}" onError="this.src = './resources/mining-pools/default.svg'"></td>
|
||||
<td class=""><a target="#" href="{{ pool.link }}">{{ pool.name }}</a></td>
|
||||
<td class="" *ngIf="this.poolsWindowPreference === '1d'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
||||
<td class="">{{ pool.name }}</td>
|
||||
<td class="" *ngIf="this.poolsWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
||||
<td class="">{{ pool['blockText'] }}</td>
|
||||
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
||||
</tr>
|
||||
<tr style="border-top: 1px solid #555">
|
||||
<td class="d-none d-md-block">-</td>
|
||||
<td class="text-right"><img width="25" height="25" src="./resources/mining-pools/default.svg"></td>
|
||||
<td class="" i18n="mining.all-miners"><b>All miners</b></td>
|
||||
<td class="" *ngIf="this.poolsWindowPreference === '24h'"><b>{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</b></td>
|
||||
<td class=""><b>{{ miningStats.blockCount }}</b></td>
|
||||
<td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import { catchError, map, skip, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, map, share, skip, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { SinglePoolStats } from 'src/app/interfaces/node-api.interface';
|
||||
import { StorageService } from '../..//services/storage.service';
|
||||
import { MiningService, MiningStats } from '../../services/mining.service';
|
||||
@@ -39,7 +39,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
private formBuilder: FormBuilder,
|
||||
private miningService: MiningService,
|
||||
) {
|
||||
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1d';
|
||||
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '24h';
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
switchMap(() => {
|
||||
this.isLoading = true;
|
||||
return this.miningService.getMiningStats(this.getSQLInterval(this.poolsWindowPreference))
|
||||
return this.miningService.getMiningStats(this.poolsWindowPreference)
|
||||
.pipe(
|
||||
catchError((e) => of(this.getEmptyMiningStat()))
|
||||
);
|
||||
@@ -79,7 +79,8 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
tap(data => {
|
||||
this.isLoading = false;
|
||||
this.prepareChartOptions(data);
|
||||
})
|
||||
}),
|
||||
share()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,7 +117,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
formatter: () => {
|
||||
if (this.poolsWindowPreference === '1d') {
|
||||
if (this.poolsWindowPreference === '24h') {
|
||||
return `<u><b>${pool.name} (${pool.share}%)</b></u><br>` +
|
||||
pool.lastEstimatedHashrate.toString() + ' PH/s' +
|
||||
`<br>` + pool.blockCount.toString() + ` blocks`;
|
||||
@@ -132,10 +133,16 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
prepareChartOptions(miningStats) {
|
||||
let network = this.stateService.network;
|
||||
if (network === '') {
|
||||
network = 'bitcoin';
|
||||
}
|
||||
network = network.charAt(0).toUpperCase() + network.slice(1);
|
||||
|
||||
this.chartOptions = {
|
||||
title: {
|
||||
text: (this.poolsWindowPreference === '1d') ? 'Hashrate distribution' : 'Block distribution',
|
||||
subtext: (this.poolsWindowPreference === '1d') ? 'Estimated from the # of blocks mined' : null,
|
||||
text: $localize`:@@mining.pool-chart-title:${network}:NETWORK: mining pools share`,
|
||||
subtext: $localize`:@@mining.pool-chart-sub-title:Estimated from the # of blocks mined`,
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
color: '#FFF',
|
||||
@@ -187,21 +194,6 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
getSQLInterval(uiInterval: string) {
|
||||
switch (uiInterval) {
|
||||
case '1d': return '1 DAY';
|
||||
case '3d': return '3 DAY';
|
||||
case '1w': return '1 WEEK';
|
||||
case '1m': return '1 MONTH';
|
||||
case '3m': return '3 MONTH';
|
||||
case '6m': return '6 MONTH';
|
||||
case '1y': return '1 YEAR';
|
||||
case '2y': return '2 YEAR';
|
||||
case '3y': return '3 YEAR';
|
||||
default: return '1000 YEAR';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default mining stats if something goes wrong
|
||||
*/
|
||||
@@ -212,6 +204,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
totalEmptyBlock: 0,
|
||||
totalEmptyBlockRatio: '',
|
||||
pools: [],
|
||||
availableTimespanDay: 0,
|
||||
miningUnits: {
|
||||
hashrateDivider: 1,
|
||||
hashrateUnit: '',
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface SinglePoolStats {
|
||||
export interface PoolsStats {
|
||||
blockCount: number;
|
||||
lastEstimatedHashrate: number;
|
||||
oldestIndexedBlockTimestamp: number;
|
||||
pools: SinglePoolStats[];
|
||||
}
|
||||
|
||||
|
||||
@@ -121,8 +121,11 @@ export class ApiService {
|
||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||
}
|
||||
|
||||
listPools$(interval: string) : Observable<PoolsStats> {
|
||||
const params = new HttpParams().set('interval', interval);
|
||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/pools', {params});
|
||||
listPools$(interval: string | null) : Observable<PoolsStats> {
|
||||
let params = {};
|
||||
if (interval) {
|
||||
params = new HttpParams().set('interval', interval);
|
||||
}
|
||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/mining/pools', {params});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface MiningStats {
|
||||
totalEmptyBlockRatio: string;
|
||||
pools: SinglePoolStats[];
|
||||
miningUnits: MiningUnits;
|
||||
availableTimespanDay: number;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@@ -80,6 +81,10 @@ export class MiningService {
|
||||
};
|
||||
});
|
||||
|
||||
const availableTimespanDay = (
|
||||
(new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp / 1000)
|
||||
) / 3600 / 24;
|
||||
|
||||
return {
|
||||
lastEstimatedHashrate: (stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
|
||||
blockCount: stats.blockCount,
|
||||
@@ -87,6 +92,7 @@ export class MiningService {
|
||||
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
||||
pools: poolsStats,
|
||||
miningUnits: miningUnits,
|
||||
availableTimespanDay: availableTimespanDay,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
export class StorageService {
|
||||
constructor(private router: Router, private route: ActivatedRoute) {
|
||||
this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
|
||||
this.setDefaultValueIfNeeded('poolsWindowPreference', '1d');
|
||||
this.setDefaultValueIfNeeded('poolsWindowPreference', '1w');
|
||||
}
|
||||
|
||||
setDefaultValueIfNeeded(key: string, defaultValue: string) {
|
||||
|
||||
Reference in New Issue
Block a user