Add block fees graph
This commit is contained in:
parent
15cc503387
commit
08e19a612c
@ -5,6 +5,7 @@ import HashratesRepository from '../repositories/HashratesRepository';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
import logger from '../logger';
|
||||
import blocks from './blocks';
|
||||
import { Common } from './common';
|
||||
|
||||
class Mining {
|
||||
hashrateIndexingStarted = false;
|
||||
@ -13,6 +14,28 @@ class Mining {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get historical block reward and total fee
|
||||
*/
|
||||
public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
|
||||
let timeRange: number;
|
||||
switch (interval) {
|
||||
case '3y': timeRange = 43200; break; // 12h
|
||||
case '2y': timeRange = 28800; break; // 8h
|
||||
case '1y': timeRange = 28800; break; // 8h
|
||||
case '6m': timeRange = 10800; break; // 3h
|
||||
case '3m': timeRange = 7200; break; // 2h
|
||||
case '1m': timeRange = 1800; break; // 30min
|
||||
case '1w': timeRange = 300; break; // 5min
|
||||
case '24h': timeRange = 1; break;
|
||||
default: timeRange = 86400; break; // 24h
|
||||
}
|
||||
|
||||
interval = Common.getSqlInterval(interval);
|
||||
|
||||
return await BlocksRepository.$getHistoricalBlockFees(timeRange, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate high level overview of the pool ranks and general stats
|
||||
*/
|
||||
|
@ -316,6 +316,7 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees)
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -439,6 +439,35 @@ class BlocksRepository {
|
||||
|
||||
connection.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the historical averaged block reward and total fees
|
||||
*/
|
||||
public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
|
||||
let connection;
|
||||
try {
|
||||
connection = await DB.getConnection();
|
||||
|
||||
let query = `SELECT CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
||||
CAST(AVG(fees) as INT) as avg_fees
|
||||
FROM blocks`;
|
||||
|
||||
if (interval !== null) {
|
||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||
}
|
||||
|
||||
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`;
|
||||
|
||||
const [rows]: any = await connection.query(query);
|
||||
connection.release();
|
||||
|
||||
return rows;
|
||||
} catch (e) {
|
||||
connection.release();
|
||||
logger.err('$getHistoricalBlockFees() error: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BlocksRepository();
|
||||
|
@ -638,6 +638,22 @@ class Routes {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getHistoricalBlockFees(req: Request, res: Response) {
|
||||
try {
|
||||
const blockFees = await mining.$getHistoricalBlockFees(req.params.interval ?? null);
|
||||
const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp();
|
||||
res.header('Pragma', 'public');
|
||||
res.header('Cache-control', 'public');
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
res.json({
|
||||
oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp,
|
||||
blockFees: blockFees,
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlock(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await bitcoinApi.$getBlock(req.params.hash);
|
||||
|
@ -33,6 +33,7 @@ import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/
|
||||
import { MiningStartComponent } from './components/mining-start/mining-start.component';
|
||||
import { GraphsComponent } from './components/graphs/graphs.component';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component';
|
||||
|
||||
let routes: Routes = [
|
||||
{
|
||||
@ -117,6 +118,10 @@ let routes: Routes = [
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/block-fees',
|
||||
component: BlockFeesGraphComponent,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -211,18 +216,6 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'hashrate',
|
||||
component: HashrateChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'hashrate/pools',
|
||||
component: HashrateChartPoolsComponent,
|
||||
},
|
||||
{
|
||||
path: 'pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'pool',
|
||||
children: [
|
||||
@ -259,6 +252,10 @@ let routes: Routes = [
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/block-fees',
|
||||
component: BlockFeesGraphComponent,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -347,18 +344,6 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'hashrate',
|
||||
component: HashrateChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'hashrate/pools',
|
||||
component: HashrateChartPoolsComponent,
|
||||
},
|
||||
{
|
||||
path: 'pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'pool',
|
||||
children: [
|
||||
@ -395,6 +380,10 @@ let routes: Routes = [
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/block-fees',
|
||||
component: BlockFeesGraphComponent,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -507,19 +496,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
{
|
||||
path: 'mempool',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/hashrate-difficulty',
|
||||
component: HashrateChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools-dominance',
|
||||
component: HashrateChartPoolsComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -639,19 +616,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
{
|
||||
path: 'mempool',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/hashrate-difficulty',
|
||||
component: HashrateChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools-dominance',
|
||||
component: HashrateChartPoolsComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -80,6 +80,7 @@ import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
import { RewardStatsComponent } from './components/reward-stats/reward-stats.component';
|
||||
import { DataCyDirective } from './data-cy.directive';
|
||||
import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -141,6 +142,7 @@ import { DataCyDirective } from './data-cy.directive';
|
||||
BlocksList,
|
||||
DataCyDirective,
|
||||
RewardStatsComponent,
|
||||
BlockFeesGraphComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
|
@ -0,0 +1,61 @@
|
||||
<div [class]="widget === false ? 'full-container' : ''">
|
||||
|
||||
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
|
||||
<span i18n="mining.block-fees">Block fees</span>
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as hashrates">
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1">
|
||||
<input ngbButton type="radio" [value]="'24h'"> 24h
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 7">
|
||||
<input ngbButton type="radio" [value]="'1w'"> 1W
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 30">
|
||||
<input ngbButton type="radio" [value]="'1m'"> 1M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90">
|
||||
<input ngbButton type="radio" [value]="'3m'"> 3M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180">
|
||||
<input ngbButton type="radio" [value]="'6m'"> 6M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365">
|
||||
<input ngbButton type="radio" [value]="'1y'"> 1Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730">
|
||||
<input ngbButton type="radio" [value]="'2y'"> 2Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095">
|
||||
<input ngbButton type="radio" [value]="'3y'"> 3Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'all'"> ALL
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
|
||||
</div>
|
||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||
<div class="spinner-border text-light"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #loadingStats>
|
||||
<div class="pool-distribution">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="mining.miners-luck">Hashrate</h5>
|
||||
<p class="card-text">
|
||||
<span class="skeleton-loader skeleton-loader-big"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="master-page.blocks">Difficulty</h5>
|
||||
<p class="card-text">
|
||||
<span class="skeleton-loader skeleton-loader-big"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
@ -0,0 +1,135 @@
|
||||
.card-header {
|
||||
border-bottom: 0;
|
||||
font-size: 18px;
|
||||
@media (min-width: 465px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-title {
|
||||
position: relative;
|
||||
color: #ffffff91;
|
||||
margin-top: -13px;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.full-container {
|
||||
padding: 0px 15px;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
height: calc(100% - 150px);
|
||||
@media (max-width: 992px) {
|
||||
height: 100%;
|
||||
padding-bottom: 100px;
|
||||
};
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 10px;
|
||||
@media (max-width: 992px) {
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
@media (max-width: 829px) {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
@media (max-width: 629px) {
|
||||
padding-bottom: 55px;
|
||||
}
|
||||
@media (max-width: 567px) {
|
||||
padding-bottom: 55px;
|
||||
}
|
||||
}
|
||||
.chart-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 270px;
|
||||
}
|
||||
|
||||
.formRadioGroup {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@media (min-width: 1130px) {
|
||||
position: relative;
|
||||
top: -65px;
|
||||
}
|
||||
@media (min-width: 830px) and (max-width: 1130px) {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
}
|
||||
@media (min-width: 830px) {
|
||||
flex-direction: row;
|
||||
float: right;
|
||||
margin-top: 0px;
|
||||
}
|
||||
.btn-sm {
|
||||
font-size: 9px;
|
||||
@media (min-width: 830px) {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pool-distribution {
|
||||
min-height: 56px;
|
||||
display: block;
|
||||
@media (min-width: 485px) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
h5 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.item {
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
margin: 0px auto 20px;
|
||||
&:nth-child(2) {
|
||||
order: 2;
|
||||
@media (min-width: 485px) {
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
&:nth-child(3) {
|
||||
order: 3;
|
||||
@media (min-width: 485px) {
|
||||
order: 2;
|
||||
display: block;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
}
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-loader {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-width: 80px;
|
||||
margin: 15px auto 3px;
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-fees-graph',
|
||||
templateUrl: './block-fees-graph.component.html',
|
||||
styleUrls: ['./block-fees-graph.component.scss'],
|
||||
styles: [`
|
||||
.loadingGraphs {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(50% - 15px);
|
||||
z-index: 100;
|
||||
}
|
||||
`],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockFeesGraphComponent implements OnInit {
|
||||
@Input() tableOnly = false;
|
||||
@Input() widget = false;
|
||||
@Input() right: number | string = 45;
|
||||
@Input() left: number | string = 75;
|
||||
|
||||
radioGroupForm: FormGroup;
|
||||
|
||||
chartOptions: EChartsOption = {};
|
||||
chartInitOptions = {
|
||||
renderer: 'svg',
|
||||
};
|
||||
|
||||
statsObservable$: Observable<any>;
|
||||
isLoading = true;
|
||||
formatNumber = formatNumber;
|
||||
timespan = '';
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
private seoService: SeoService,
|
||||
private apiService: ApiService,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`:@@mining.block-fees:Block Fees`);
|
||||
}
|
||||
|
||||
this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
|
||||
.pipe(
|
||||
startWith('1y'),
|
||||
switchMap((timespan) => {
|
||||
this.timespan = timespan;
|
||||
this.isLoading = true;
|
||||
return this.apiService.getHistoricalBlockFees$(timespan)
|
||||
.pipe(
|
||||
tap((data: any) => {
|
||||
this.prepareChartOptions({
|
||||
blockFees: data.blockFees.map(val => [val.timestamp * 1000, val.avg_fees / 100000000]),
|
||||
});
|
||||
this.isLoading = false;
|
||||
}),
|
||||
map((data: any) => {
|
||||
const availableTimespanDay = (
|
||||
(new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp)
|
||||
) / 3600 / 24;
|
||||
|
||||
return {
|
||||
availableTimespanDay: availableTimespanDay,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
}
|
||||
|
||||
prepareChartOptions(data) {
|
||||
this.chartOptions = {
|
||||
animation: false,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
{ offset: 0, color: '#F4511E' },
|
||||
{ offset: 0.25, color: '#FB8C00' },
|
||||
{ offset: 0.5, color: '#FFB300' },
|
||||
{ offset: 0.75, color: '#FDD835' },
|
||||
{ offset: 1, color: '#7CB342' }
|
||||
]),
|
||||
],
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 80,
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
},
|
||||
tooltip: {
|
||||
show: !this.isMobile() || !this.widget,
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line'
|
||||
},
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: (ticks) => {
|
||||
const tick = ticks[0];
|
||||
const feesString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC`;
|
||||
return `
|
||||
<b style="color: white; margin-left: 18px">${tick.axisValueLabel}</b><br>
|
||||
<span>${feesString}</span>
|
||||
`;
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
name: formatterXAxisLabel(this.locale, this.timespan),
|
||||
nameLocation: 'middle',
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
type: 'time',
|
||||
splitNumber: this.isMobile() ? 5 : 10,
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: (val) => {
|
||||
return `${val} BTC`;
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
zlevel: 0,
|
||||
name: 'Fees',
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
data: data.blockFees,
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
dataZoom: this.widget ? null : [{
|
||||
type: 'inside',
|
||||
realtime: true,
|
||||
zoomLock: true,
|
||||
maxSpan: 100,
|
||||
minSpan: 10,
|
||||
moveOnMouseMove: false,
|
||||
}, {
|
||||
showDetail: false,
|
||||
show: true,
|
||||
type: 'slider',
|
||||
brushSelect: false,
|
||||
realtime: true,
|
||||
left: 20,
|
||||
right: 15,
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 0.45,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0,
|
||||
}
|
||||
},
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
return (window.innerWidth <= 767.98);
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@
|
||||
[routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="mining.hashrate-difficulty">
|
||||
Hashrate & Difficulty
|
||||
</a>
|
||||
<a class="dropdown-item" routerLinkActive="active"
|
||||
[routerLink]="['/graphs/mining/block-fees' | relativeUrl]" i18n="mining.block-fees">
|
||||
Block Fees
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
|
||||
<span i18n="mining.mining-pool-share">Hashrate & Difficulty</span>
|
||||
<span i18n="mining.hashrate-difficulty">Hashrate & Difficulty</span>
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as hashrates">
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90">
|
||||
|
@ -168,6 +168,13 @@ export class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
getHistoricalBlockFees$(interval: string | undefined) : Observable<any> {
|
||||
return this.httpClient.get<any[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/fees` +
|
||||
(interval !== undefined ? `/${interval}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
getRewardStats$(blockCount: number = 144): Observable<RewardStats> {
|
||||
return this.httpClient.get<RewardStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user