mirror of
https://github.com/mempool/mempool.git
synced 2024-12-29 09:44:26 +01:00
Adding mini-graphs on dashboard.
This commit is contained in:
parent
43314c2283
commit
3dedf1e3e1
@ -15,8 +15,8 @@ class Routes {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!config.DB_DISABLED) {
|
if (!config.DB_DISABLED) {
|
||||||
this.createCache();
|
// this.createCache();
|
||||||
setInterval(this.createCache.bind(this), 600000);
|
// setInterval(this.createCache.bind(this), 600000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
|
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||||
import { faChartArea, faCube, faDatabase, faInfo, faInfoCircle, faList, faQuestion, faQuestionCircle, faSearch, faTachometerAlt, faThList, faTv } from '@fortawesome/free-solid-svg-icons';
|
import { faChartArea, faCube, faCubes, faDatabase, faInfo, faInfoCircle, faList, faQuestion, faQuestionCircle, faSearch, faTachometerAlt, faThList, faTv } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -101,7 +101,8 @@ export class AppModule {
|
|||||||
library.addIcons(faInfoCircle);
|
library.addIcons(faInfoCircle);
|
||||||
library.addIcons(faChartArea);
|
library.addIcons(faChartArea);
|
||||||
library.addIcons(faTv);
|
library.addIcons(faTv);
|
||||||
library.addIcons(faCube);
|
library.addIcons(faTachometerAlt);
|
||||||
|
library.addIcons(faCubes);
|
||||||
library.addIcons(faThList);
|
library.addIcons(faThList);
|
||||||
library.addIcons(faList);
|
library.addIcons(faList);
|
||||||
library.addIcons(faTachometerAlt);
|
library.addIcons(faTachometerAlt);
|
||||||
|
@ -8,6 +8,7 @@ import { of, Subscription } from 'rxjs';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { env } from 'src/app/app.constants';
|
import { env } from 'src/app/app.constants';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block',
|
selector: 'app-block',
|
||||||
@ -39,9 +40,11 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.paginationMaxSize = window.matchMedia('(max-width: 700px)').matches ? 3 : 5;
|
this.paginationMaxSize = window.matchMedia('(max-width: 700px)').matches ? 3 : 5;
|
||||||
this.network = this.stateService.network;
|
this.network = this.stateService.network;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<a class="nav-link" [routerLink]="['/bisq']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" title="Transactions"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/bisq']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" title="Transactions"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'th-list']" [fixedWidth]="true" title="Blocks"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" title="Blocks"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" title="Stats"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" title="Stats"></fa-icon></a>
|
||||||
@ -37,10 +37,10 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #notBisq>
|
<ng-template #notBisq>
|
||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
|
||||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cube']" [fixedWidth]="true" title="Dashboard"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" title="Dashboard"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'th-list']" [fixedWidth]="true" title="Blocks"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" title="Blocks"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" title="Graphs"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" title="Graphs"></fa-icon></a>
|
||||||
|
@ -6,6 +6,7 @@ import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
|
|||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { env } from 'src/app/app.constants';
|
import { env } from 'src/app/app.constants';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-block',
|
selector: 'app-mempool-block',
|
||||||
@ -23,9 +24,12 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
|
|
||||||
this.mempoolBlock$ = this.route.paramMap
|
this.mempoolBlock$ = this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
|
@ -13,6 +13,9 @@ import { StateService } from 'src/app/services/state.service';
|
|||||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() data;
|
@Input() data;
|
||||||
@Input() dateSpan = '2h';
|
@Input() dateSpan = '2h';
|
||||||
|
@Input() showLegend = true;
|
||||||
|
@Input() offsetX = 40;
|
||||||
|
@Input() offsetY = 40;
|
||||||
|
|
||||||
mempoolVsizeFeesOptions: any;
|
mempoolVsizeFeesOptions: any;
|
||||||
mempoolVsizeFeesData: any;
|
mempoolVsizeFeesData: any;
|
||||||
@ -26,6 +29,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
const showLegend = !this.isMobile && this.showLegend;
|
||||||
|
const labelHops = this.isMobile || !this.showLegend ? 12 : 6;
|
||||||
|
|
||||||
const labelInterpolationFnc = (value: any, index: any) => {
|
const labelInterpolationFnc = (value: any, index: any) => {
|
||||||
switch (this.dateSpan) {
|
switch (this.dateSpan) {
|
||||||
case '2h':
|
case '2h':
|
||||||
@ -41,7 +47,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
case '1y':
|
case '1y':
|
||||||
value = formatDate(value, 'dd/MM', this.locale);
|
value = formatDate(value, 'dd/MM', this.locale);
|
||||||
}
|
}
|
||||||
return index % 6 === 0 ? value : null;
|
return index % labelHops === 0 ? value : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.mempoolVsizeFeesOptions = {
|
this.mempoolVsizeFeesOptions = {
|
||||||
@ -52,18 +58,23 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
low: 0,
|
low: 0,
|
||||||
axisX: {
|
axisX: {
|
||||||
labelInterpolationFnc: labelInterpolationFnc,
|
labelInterpolationFnc: labelInterpolationFnc,
|
||||||
offset: 40
|
offset: this.offsetX,
|
||||||
},
|
},
|
||||||
axisY: {
|
axisY: {
|
||||||
labelInterpolationFnc: (value: number): any => {
|
labelInterpolationFnc: (value: number): any => {
|
||||||
return this.vbytesPipe.transform(value, 2);
|
return this.vbytesPipe.transform(value, 2);
|
||||||
},
|
},
|
||||||
offset: this.isMobile ? 60 : 160
|
offset: showLegend ? 160 : 60,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
Chartist.plugins.ctTargetLine({
|
Chartist.plugins.ctTargetLine({
|
||||||
value: 1000000
|
value: 1000000
|
||||||
}),
|
}),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showLegend) {
|
||||||
|
this.mempoolVsizeFeesOptions.plugins.push(
|
||||||
Chartist.plugins.legend({
|
Chartist.plugins.legend({
|
||||||
legendNames: [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
legendNames: [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
||||||
250, 300, 350, 400].map((sat, i, arr) => {
|
250, 300, 350, 400].map((sat, i, arr) => {
|
||||||
@ -79,8 +90,8 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
return arr[i - 1] + ' - ' + sat;
|
return arr[i - 1] + ' - ' + sat;
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
]
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-start',
|
selector: 'app-start',
|
||||||
templateUrl: './start.component.html',
|
templateUrl: './start.component.html',
|
||||||
styleUrls: ['./start.component.scss'],
|
styleUrls: ['./start.component.scss'],
|
||||||
})
|
})
|
||||||
export class StartComponent implements OnInit {
|
export class StartComponent {
|
||||||
constructor(
|
constructor() { }
|
||||||
private websocketService: WebsocketService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,10 @@ export class StatisticsComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.seoService.setTitle('Graphs');
|
this.seoService.setTitle('Graphs');
|
||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
const isMobile = window.innerWidth <= 767.98;
|
||||||
|
const labelHops = isMobile ? 12 : 6;
|
||||||
|
|
||||||
const labelInterpolationFnc = (value: any, index: any) => {
|
const labelInterpolationFnc = (value: any, index: any) => {
|
||||||
const nr = 6;
|
|
||||||
|
|
||||||
switch (this.radioGroupForm.controls.dateSpan.value) {
|
switch (this.radioGroupForm.controls.dateSpan.value) {
|
||||||
case '2h':
|
case '2h':
|
||||||
case '24h':
|
case '24h':
|
||||||
@ -71,7 +71,7 @@ export class StatisticsComponent implements OnInit {
|
|||||||
value = formatDate(value, 'dd/MM', this.locale);
|
value = formatDate(value, 'dd/MM', this.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
return index % nr === 0 ? value : null;
|
return index % labelHops === 0 ? value : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.transactionsWeightPerSecondOptions = {
|
this.transactionsWeightPerSecondOptions = {
|
||||||
|
@ -39,6 +39,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
this.subscription = this.route.paramMap.pipe(
|
this.subscription = this.route.paramMap.pipe(
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col mb-4">
|
<div class="col mb-4">
|
||||||
<div class="card text-center">
|
<div class="card text-center">
|
||||||
<div class="card-body">
|
<div class="card-body more-padding">
|
||||||
<h5 class="card-title">Tx weight per second</h5>
|
<h5 class="card-title">Tx weight per second</h5>
|
||||||
<ng-template [ngIf]="mempoolInfoData.value" [ngIfElse]="loading">
|
<ng-template [ngIf]="mempoolInfoData.value" [ngIfElse]="loading">
|
||||||
<span *ngIf="mempoolInfoData.value.vBytesPerSecond === 0; else inSync">
|
<span *ngIf="mempoolInfoData.value.vBytesPerSecond === 0; else inSync">
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col mb-4" *ngIf="(network$ | async) !== 'liquid'; else emptyBlock">
|
<div class="col mb-4" *ngIf="(network$ | async) !== 'liquid'; else emptyBlock">
|
||||||
<div class="card text-center">
|
<div class="card text-center">
|
||||||
<div class="card-body">
|
<div class="card-body more-padding">
|
||||||
<h5 class="card-title">Difficulty adjustment</h5>
|
<h5 class="card-title">Difficulty adjustment</h5>
|
||||||
<div class="progress" *ngIf="(difficultyEpoch$ | async) as epochData; else loading">
|
<div class="progress" *ngIf="(difficultyEpoch$ | async) as epochData; else loading">
|
||||||
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"><ng-template [ngIf]="epochData.change > 0">+</ng-template>{{ epochData.change | number: '1.0-2' }}%</div>
|
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"><ng-template [ngIf]="epochData.change > 0">+</ng-template>{{ epochData.change | number: '1.0-2' }}%</div>
|
||||||
@ -48,6 +48,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<div style="height: 250px;" *ngIf="(mempoolStats$ | async) as mempoolStats">
|
||||||
|
<app-mempool-graph [data]="mempoolStats.mempool" [showLegend]="false" [offsetX]="10"></app-mempool-graph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<div style="height: 250px;" *ngIf="(mempoolStats$ | async) as mempoolStats">
|
||||||
|
<app-chartist
|
||||||
|
[data]="mempoolStats.weightPerSecond"
|
||||||
|
[type]="'Line'"
|
||||||
|
[options]="transactionsWeightPerSecondOptions">
|
||||||
|
</app-chartist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col mb-4">
|
<div class="col mb-4">
|
||||||
<div class="card text-center">
|
<div class="card text-center">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -56,14 +81,14 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<th style="width: 15%;">Height</th>
|
<th style="width: 15%;">Height</th>
|
||||||
<th style="width: 35%;">Mined</th>
|
<th style="width: 35%;">Mined</th>
|
||||||
<th style="width: 20%;" class="d-none d-lg-block">Txs</th>
|
<th style="width: 20%;" class="d-none d-lg-table-cell">Txs</th>
|
||||||
<th style="width: 30%;">Filled</th>
|
<th style="width: 30%;">Filled</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock">
|
<tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock">
|
||||||
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
||||||
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
|
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
|
||||||
<td class="d-none d-lg-block">{{ block.tx_count | number }}</td>
|
<td class="d-none d-lg-table-cell">{{ block.tx_count | number }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="progress position-relative">
|
<div class="progress position-relative">
|
||||||
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / 4000000)*100 + '%' }"></div>
|
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / 4000000)*100 + '%' }"></div>
|
||||||
@ -84,13 +109,13 @@
|
|||||||
<table class="table table-borderless text-left">
|
<table class="table table-borderless text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<th style="width: 25%;">TXID</th>
|
<th style="width: 25%;">TXID</th>
|
||||||
<th style="width: 50%;" class="text-right d-none d-lg-block">Amount (BTC)</th>
|
<th style="width: 50%;" class="text-right d-none d-lg-table-cell">Amount (BTC)</th>
|
||||||
<th style="width: 25%;" class="text-right">Fee</th>
|
<th style="width: 25%;" class="text-right">Fee</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
|
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
|
||||||
<td><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 12 }}</a></td>
|
<td><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 12 }}</a></td>
|
||||||
<td class="text-right d-none d-lg-block"><app-amount [satoshis]="transaction.value" digitsInfo="1.2-2" [noFiat]="true"></app-amount> (<app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat>)</td>
|
<td class="text-right d-none d-lg-table-cell"><app-amount [satoshis]="transaction.value" digitsInfo="1.2-2" [noFiat]="true"></app-amount> (<app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat>)</td>
|
||||||
<td class="text-right">{{ transaction.fee / (transaction.weight / 4) | number : '1.1-1' }} sat/vB</td>
|
<td class="text-right">{{ transaction.fee / (transaction.weight / 4) | number : '1.1-1' }} sat/vB</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -27,6 +27,6 @@
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.more-padding {
|
||||||
padding: 1.5rem;
|
padding: 1.25rem 2rem 1.25rem 2rem;
|
||||||
}
|
}
|
@ -1,9 +1,14 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { combineLatest, merge, Observable, of } from 'rxjs';
|
import { combineLatest, merge, Observable, of } from 'rxjs';
|
||||||
import { map, reduce, scan, tap } from 'rxjs/operators';
|
import { map, scan, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import { Block } from '../interfaces/electrs.interface';
|
import { Block } from '../interfaces/electrs.interface';
|
||||||
|
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
|
import { ApiService } from '../services/api.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
|
import * as Chartist from 'chartist';
|
||||||
|
import { formatDate } from '@angular/common';
|
||||||
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
|
|
||||||
interface MempoolBlocksData {
|
interface MempoolBlocksData {
|
||||||
blocks: number;
|
blocks: number;
|
||||||
@ -24,6 +29,11 @@ interface MempoolInfoData {
|
|||||||
progressClass: string;
|
progressClass: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MempoolStatsData {
|
||||||
|
mempool: OptimizedMempoolStats[];
|
||||||
|
weightPerSecond: any;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
@ -39,12 +49,19 @@ export class DashboardComponent implements OnInit {
|
|||||||
blocks$: Observable<Block[]>;
|
blocks$: Observable<Block[]>;
|
||||||
transactions$: Observable<TransactionStripped[]>;
|
transactions$: Observable<TransactionStripped[]>;
|
||||||
latestBlockHeight: number;
|
latestBlockHeight: number;
|
||||||
|
mempoolTransactionsWeightPerSecondData: any;
|
||||||
|
mempoolStats$: Observable<MempoolStatsData>;
|
||||||
|
transactionsWeightPerSecondOptions: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
|
||||||
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
||||||
|
|
||||||
this.mempoolInfoData$ = combineLatest([
|
this.mempoolInfoData$ = combineLatest([
|
||||||
@ -137,6 +154,59 @@ export class DashboardComponent implements OnInit {
|
|||||||
}, []),
|
}, []),
|
||||||
map((txs) => txs.slice(0, 6)),
|
map((txs) => txs.slice(0, 6)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.mempoolStats$ = this.apiService.list2HStatistics$()
|
||||||
|
.pipe(
|
||||||
|
switchMap((mempoolStats) => {
|
||||||
|
return merge(
|
||||||
|
this.stateService.live2Chart$
|
||||||
|
.pipe(
|
||||||
|
scan((acc, stats) => {
|
||||||
|
acc.unshift(stats);
|
||||||
|
acc = acc.slice(0, acc.length - 1);
|
||||||
|
return acc;
|
||||||
|
}, mempoolStats)
|
||||||
|
),
|
||||||
|
of(mempoolStats)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map((mempoolStats) => {
|
||||||
|
return {
|
||||||
|
mempool: mempoolStats,
|
||||||
|
weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.transactionsWeightPerSecondOptions = {
|
||||||
|
showArea: false,
|
||||||
|
showLine: true,
|
||||||
|
fullWidth: true,
|
||||||
|
showPoint: false,
|
||||||
|
low: 0,
|
||||||
|
axisY: {
|
||||||
|
offset: 40
|
||||||
|
},
|
||||||
|
axisX: {
|
||||||
|
labelInterpolationFnc: (value: any, index: any) => index % 12 === 0 ? formatDate(value, 'HH:mm', this.locale) : null,
|
||||||
|
offset: 10
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.ctTargetLine({
|
||||||
|
value: 1667
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
|
mempoolStats.reverse();
|
||||||
|
const labels = mempoolStats.map(stats => stats.added);
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels: labels,
|
||||||
|
series: [mempoolStats.map((stats) => stats.vbytes_per_second)],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByBlock(index: number, block: Block) {
|
trackByBlock(index: number, block: Block) {
|
||||||
|
@ -216,6 +216,9 @@ export class WebsocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
want(data: string[]) {
|
want(data: string[]) {
|
||||||
|
if (data === this.lastWant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.websocketSubject.next({action: 'want', data: data});
|
this.websocketSubject.next({action: 'want', data: data});
|
||||||
this.lastWant = data;
|
this.lastWant = data;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user