Add pie chart and rewrite the pool ranking component
This commit is contained in:
parent
18a63933fa
commit
0a267affaf
@ -43,8 +43,8 @@ class Mining {
|
|||||||
})
|
})
|
||||||
|
|
||||||
poolsStatistics["blockCount"] = blockCount;
|
poolsStatistics["blockCount"] = blockCount;
|
||||||
poolsStatistics["poolsStats"] = poolsStats;
|
|
||||||
poolsStatistics["lastEstimatedHashrate"] = lastBlockHashrate;
|
poolsStatistics["lastEstimatedHashrate"] = lastBlockHashrate;
|
||||||
|
poolsStatistics["pools"] = poolsStats;
|
||||||
|
|
||||||
return poolsStatistics;
|
return poolsStatistics;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
|
|||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
import { DifficultyComponent } from './components/difficulty/difficulty.component';
|
import { DifficultyComponent } from './components/difficulty/difficulty.component';
|
||||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
|
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
|
||||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
|
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { ApiDocsComponent } from './components/docs/api-docs.component';
|
import { ApiDocsComponent } from './components/docs/api-docs.component';
|
||||||
import { DocsComponent } from './components/docs/docs.component';
|
import { DocsComponent } from './components/docs/docs.component';
|
||||||
@ -145,6 +145,7 @@ export class AppModule {
|
|||||||
library.addIcons(faTv);
|
library.addIcons(faTv);
|
||||||
library.addIcons(faTachometerAlt);
|
library.addIcons(faTachometerAlt);
|
||||||
library.addIcons(faCubes);
|
library.addIcons(faCubes);
|
||||||
|
library.addIcons(faHammer);
|
||||||
library.addIcons(faCogs);
|
library.addIcons(faCogs);
|
||||||
library.addIcons(faThList);
|
library.addIcons(faThList);
|
||||||
library.addIcons(faList);
|
library.addIcons(faList);
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
|
||||||
<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>
|
<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>
|
||||||
<li class="nav-item" routerLinkActive="active" id="btn-blocks">
|
<li class="nav-item" routerLinkActive="active" id="btn-pools">
|
||||||
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
|
<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>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
<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>
|
<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>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<i class="fa fa-area-chart"></i> <span i18n="mining.pools-by-vBytes">Pools</span>
|
<!-- <i class="fa fa-area-chart"></i> <span i18n="mining.pools-by-vBytes">Pools</span> -->
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" (click)="savePoolsPreference()">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup">
|
||||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan"
|
||||||
|
(change)="onChangeWindowPreference($event)">
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
|
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
|
||||||
</label>
|
</label>
|
||||||
@ -37,6 +38,11 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="height: 500px;" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
|
||||||
|
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
||||||
<thead>
|
<thead>
|
||||||
<th i18n="latest-blocks.height">Rank</th>
|
<th i18n="latest-blocks.height">Rank</th>
|
||||||
@ -45,23 +51,21 @@
|
|||||||
<th class="d-none d-md-block" i18n="latest-blocks.mined">Block Count (share)</th>
|
<th class="d-none d-md-block" i18n="latest-blocks.mined">Block Count (share)</th>
|
||||||
<th i18n="latest-blocks.transactions">Empty Blocks (ratio)</th>
|
<th i18n="latest-blocks.transactions">Empty Blocks (ratio)</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="pools$ | async as pools">
|
<tbody *ngIf="(miningStatsEmitter$ | async) as miningStats">
|
||||||
<tr>
|
<tr>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
<td>All miners</td>
|
<td>All miners</td>
|
||||||
<td>{{ pools["lastEstimatedHashrate"]}} PH/s</td>
|
<td>{{ miningStats.lastEstimatedHashrate}} PH/s</td>
|
||||||
<td>{{ pools["blockCount"] }}</td>
|
<td>{{ miningStats.blockCount }}</td>
|
||||||
<td>{{ pools["totalEmptyBlock"] }} ({{ pools["totalEmptyBlockRatio"] }}%)</td>
|
<td>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let pool of miningStats.pools">
|
||||||
|
<td>{{ pool.rank }}</td>
|
||||||
|
<td><a href="{{ pool.link }}">{{ pool.name }}</a></td>
|
||||||
|
<td>{{ pool.lastEstimatedHashrate }} PH/s</td>
|
||||||
|
<td>{{ pool.blockCount }} ({{ pool.share }}%)</td>
|
||||||
|
<td>{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template ngFor let-pool [ngForOf]="pools['poolsStats']">
|
|
||||||
<tr>
|
|
||||||
<td>{{ pool.rank }}</td>
|
|
||||||
<td><a href="{{ pool.link }}">{{ pool.name }}</a></td>
|
|
||||||
<td>{{ pool.lastEstimatedHashrate }} PH/s</td>
|
|
||||||
<td>{{ pool.blockCount }} ({{ pool.share }}%)</td>
|
|
||||||
<td>{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -1,103 +1,169 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { EChartsOption } from 'echarts';
|
||||||
import { merge, Observable, ObservableInput, of } from 'rxjs';
|
import { BehaviorSubject, merge, of } from 'rxjs';
|
||||||
import { map, share, switchMap, tap } from 'rxjs/operators';
|
import { skip } from 'rxjs/operators';
|
||||||
import { PoolsStats } from 'src/app/interfaces/node-api.interface';
|
import { MiningStats } from 'src/app/interfaces/node-api.interface';
|
||||||
import { StorageService } from 'src/app/services/storage.service';
|
import { StorageService } from 'src/app/services/storage.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pool-ranking',
|
selector: 'app-pool-ranking',
|
||||||
templateUrl: './pool-ranking.component.html',
|
templateUrl: './pool-ranking.component.html',
|
||||||
|
styles: [`
|
||||||
|
.loadingGraphs {
|
||||||
|
position: absolute;
|
||||||
|
top: 52%;
|
||||||
|
left: calc(50% - 16px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class PoolRankingComponent implements OnInit {
|
export class PoolRankingComponent implements OnInit, OnChanges {
|
||||||
pools$: Observable<object>
|
|
||||||
|
|
||||||
radioGroupForm: FormGroup;
|
|
||||||
poolsWindowPreference: string;
|
poolsWindowPreference: string;
|
||||||
|
radioGroupForm: FormGroup;
|
||||||
|
|
||||||
|
miningStats!: MiningStats;
|
||||||
|
miningStatsEmitter$ = new BehaviorSubject<MiningStats>(this.miningStats);
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
chartOptions: EChartsOption = {};
|
||||||
|
chartInitOptions = {
|
||||||
|
renderer: 'svg'
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private apiService: ApiService,
|
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
) { }
|
private formBuilder: FormBuilder,
|
||||||
|
private miningService: MiningService,
|
||||||
ngOnInit(): void {
|
) {
|
||||||
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference').trim() : '2h';
|
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference').trim() : '2h';
|
||||||
this.radioGroupForm = this.formBuilder.group({
|
|
||||||
dateSpan: this.poolsWindowPreference
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup datespan triggers
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
|
||||||
this.route.fragment.subscribe((fragment) => {
|
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
|
||||||
if (['1d', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
|
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
this.refreshMiningStats();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
merge(of(''), this.radioGroupForm.controls.dateSpan.valueChanges)
|
ngOnInit() {
|
||||||
.pipe(switchMap(() => this.onDateSpanChanged()))
|
}
|
||||||
.subscribe((pools: any) => {
|
|
||||||
console.log(pools);
|
sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
|
||||||
|
|
||||||
|
refreshMiningStats() {
|
||||||
|
return this.miningService.getMiningStats(this.getSQLInterval(this.poolsWindowPreference))
|
||||||
|
.subscribe(async data => {
|
||||||
|
this.miningStats = data;
|
||||||
|
this.miningStatsEmitter$.next(this.miningStats);
|
||||||
|
this.prepareChartOptions();
|
||||||
|
this.isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch initial mining pool data
|
|
||||||
this.onDateSpanChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
}
|
}
|
||||||
|
|
||||||
rendered() {
|
onChangeWindowPreference(e) {
|
||||||
|
this.poolsWindowPreference = e.target.value;
|
||||||
|
this.isLoading = true;
|
||||||
|
this.refreshMiningStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
savePoolsPreference() {
|
generatePoolsChartSerieData() {
|
||||||
this.storageService.setValue('poolsWindowPreference', this.radioGroupForm.controls.dateSpan.value);
|
const poolShareThreshold = 0.5; // Do not draw pools which hashrate share is lower than that
|
||||||
this.poolsWindowPreference = this.radioGroupForm.controls.dateSpan.value;
|
const data: object[] = [];
|
||||||
}
|
|
||||||
|
|
||||||
onDateSpanChanged(): ObservableInput<any> {
|
this.miningStats.pools.forEach((pool) => {
|
||||||
let interval: string;
|
if (parseFloat(pool.share) < poolShareThreshold) {
|
||||||
console.log(this.poolsWindowPreference);
|
return;
|
||||||
switch (this.poolsWindowPreference) {
|
|
||||||
case '1d': interval = '1 DAY'; break;
|
|
||||||
case '3d': interval = '3 DAY'; break;
|
|
||||||
case '1w': interval = '1 WEEK'; break;
|
|
||||||
case '1m': interval = '1 MONTH'; break;
|
|
||||||
case '3m': interval = '3 MONTH'; break;
|
|
||||||
case '6m': interval = '6 MONTH'; break;
|
|
||||||
case '1y': interval = '1 YEAR'; break;
|
|
||||||
case '2y': interval = '2 YEAR'; break;
|
|
||||||
case '3y': interval = '3 YEAR'; break;
|
|
||||||
case 'all': interval = '1000 YEAR'; break;
|
|
||||||
}
|
|
||||||
this.pools$ = this.apiService.listPools$(interval).pipe(map(res => this.computeMiningStats(res)));
|
|
||||||
return this.pools$;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeMiningStats(stats: PoolsStats) {
|
|
||||||
const totalEmptyBlock = Object.values(stats.poolsStats).reduce((prev, cur) => {
|
|
||||||
return prev + cur.emptyBlocks;
|
|
||||||
}, 0);
|
|
||||||
const totalEmptyBlockRatio = (totalEmptyBlock / stats.blockCount * 100).toFixed(2);
|
|
||||||
const poolsStats = stats.poolsStats.map((poolStat) => {
|
|
||||||
return {
|
|
||||||
share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
|
|
||||||
lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
|
||||||
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
|
|
||||||
...poolStat
|
|
||||||
}
|
}
|
||||||
|
data.push({
|
||||||
|
value: pool.lastEstimatedHashrate,
|
||||||
|
name: pool.name,
|
||||||
|
label: { color: '#FFFFFF' },
|
||||||
|
tooltip: {
|
||||||
|
formatter: () => {
|
||||||
|
return `<u><b>${pool.name}</b></u><br>` +
|
||||||
|
pool.lastEstimatedHashrate.toString() + ' PH/s (' + pool.share + `%)
|
||||||
|
<br>(` + pool.blockCount.toString() + ` blocks)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
prepareChartOptions() {
|
||||||
lastEstimatedHashrate: (stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
this.chartOptions = {
|
||||||
blockCount: stats.blockCount,
|
title: {
|
||||||
totalEmptyBlock: totalEmptyBlock,
|
text: 'Hashrate distribution',
|
||||||
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
subtext: 'Estimated from the # of blocks mined',
|
||||||
poolsStats: poolsStats,
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
subtextStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Mining pool',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['30%', '70%'],
|
||||||
|
data: this.generatePoolsChartSerieData(),
|
||||||
|
labelLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 5,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#000',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 5,
|
||||||
|
borderColor: '#000',
|
||||||
|
borderRadius: 20,
|
||||||
|
shadowBlur: 40,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.75)'
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,15 +52,29 @@ export interface LiquidPegs {
|
|||||||
|
|
||||||
export interface ITranslators { [language: string]: string; }
|
export interface ITranslators { [language: string]: string; }
|
||||||
|
|
||||||
|
export interface SinglePoolStats {
|
||||||
|
pooldId: number,
|
||||||
|
name: string,
|
||||||
|
link: string,
|
||||||
|
blockCount: number,
|
||||||
|
emptyBlocks: number,
|
||||||
|
rank: number,
|
||||||
|
share: string,
|
||||||
|
lastEstimatedHashrate: string,
|
||||||
|
emptyBlockRatio: string,
|
||||||
|
}
|
||||||
|
|
||||||
export interface PoolsStats {
|
export interface PoolsStats {
|
||||||
poolsStats: {
|
|
||||||
pooldId: number,
|
|
||||||
name: string,
|
|
||||||
link: string,
|
|
||||||
blockCount: number,
|
|
||||||
emptyBlocks: number,
|
|
||||||
rank: number,
|
|
||||||
}[],
|
|
||||||
blockCount: number,
|
blockCount: number,
|
||||||
lastEstimatedHashrate: number,
|
lastEstimatedHashrate: number,
|
||||||
|
pools: SinglePoolStats[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MiningStats {
|
||||||
|
lastEstimatedHashrate: string,
|
||||||
|
blockCount: number,
|
||||||
|
totalEmptyBlock: number,
|
||||||
|
totalEmptyBlockRatio: string,
|
||||||
|
pools: SinglePoolStats[],
|
||||||
|
}
|
||||||
|
|
||||||
|
44
frontend/src/app/services/mining.service.ts
Normal file
44
frontend/src/app/services/mining.service.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { MiningStats, PoolsStats } from '../interfaces/node-api.interface';
|
||||||
|
import { ApiService } from '../services/api.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MiningService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public getMiningStats(interval: string): Observable<MiningStats> {
|
||||||
|
return this.apiService.listPools$(interval).pipe(
|
||||||
|
map(pools => this.generateMiningStats(pools))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateMiningStats(stats: PoolsStats) : MiningStats {
|
||||||
|
const totalEmptyBlock = Object.values(stats.pools).reduce((prev, cur) => {
|
||||||
|
return prev + cur.emptyBlocks;
|
||||||
|
}, 0);
|
||||||
|
const totalEmptyBlockRatio = (totalEmptyBlock / stats.blockCount * 100).toFixed(2);
|
||||||
|
const poolsStats = stats.pools.map((poolStat) => {
|
||||||
|
return {
|
||||||
|
share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
|
||||||
|
lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
||||||
|
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
|
||||||
|
...poolStat
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastEstimatedHashrate: (stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
||||||
|
blockCount: stats.blockCount,
|
||||||
|
totalEmptyBlock: totalEmptyBlock,
|
||||||
|
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
||||||
|
pools: poolsStats,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user