mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 06:47:52 +01:00
Merge pull request #4715 from mempool/nymkappa/accel-dashboard-cleanup
[accelerator] improve acceleration list/dashboard
This commit is contained in:
commit
af6af2748c
9 changed files with 117 additions and 100 deletions
|
@ -3,16 +3,16 @@
|
|||
<div class="item">
|
||||
<h5 class="card-title" i18n="accelerator.requests">Requests</h5>
|
||||
<div class="card-text">
|
||||
<div>{{ stats.count }}</div>
|
||||
<div>{{ stats.totalRequested }}</div>
|
||||
<div class="symbol" i18n="accelerator.total-accelerated">accelerated</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="accelerator.total-boost">Total Bid Boost</h5>
|
||||
<div class="card-text">
|
||||
<div>{{ stats.totalFeesPaid / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
||||
<div>{{ stats.totalBidBoost / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="stats.totalFeesPaid"></app-fiat>
|
||||
<app-fiat [value]="stats.totalBidBoost"></app-fiat>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../../services/api.service';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||
|
||||
export type AccelerationStats = {
|
||||
totalRequested: number;
|
||||
totalBidBoost: number;
|
||||
successRate: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-acceleration-stats',
|
||||
|
@ -12,35 +15,13 @@ import { Acceleration } from '../../../interfaces/node-api.interface';
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccelerationStatsComponent implements OnInit {
|
||||
@Input() timespan: '24h' | '1w' | '1m' = '24h';
|
||||
@Input() accelerations$: Observable<Acceleration[]>;
|
||||
public accelerationStats$: Observable<any>;
|
||||
accelerationStats$: Observable<AccelerationStats>;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
private servicesApiService: ServicesApiServices
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.accelerationStats$ = this.accelerations$.pipe(
|
||||
switchMap(accelerations => {
|
||||
let totalFeesPaid = 0;
|
||||
let totalSucceeded = 0;
|
||||
let totalCanceled = 0;
|
||||
for (const acc of accelerations) {
|
||||
if (acc.status === 'completed') {
|
||||
totalSucceeded++;
|
||||
totalFeesPaid += (acc.feePaid - acc.baseFee - acc.vsizeFee) || 0;
|
||||
} else if (acc.status === 'failed') {
|
||||
totalCanceled++;
|
||||
}
|
||||
}
|
||||
return of({
|
||||
count: totalSucceeded,
|
||||
totalFeesPaid,
|
||||
successRate: (totalSucceeded + totalCanceled > 0) ? ((totalSucceeded / (totalSucceeded + totalCanceled)) * 100) : 0.0,
|
||||
});
|
||||
})
|
||||
);
|
||||
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container-xl widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Accelerations</h1>
|
||||
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
|||
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
||||
<th class="block text-right" i18n="accelerator.block">Block</th>
|
||||
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
||||
<th class="date text-right" i18n="" *ngIf="!this.widget">Requested</th>
|
||||
</ng-container>
|
||||
</thead>
|
||||
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
|
@ -49,9 +50,13 @@
|
|||
</td>
|
||||
<td class="status text-right">
|
||||
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
||||
<span *ngIf="acceleration.status === 'mined' || acceleration.status === 'completed'" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
||||
<span *ngIf="acceleration.status === 'mined'" class="badge badge-info" i18n="transaction.rbf.mined">Mined</span>
|
||||
<span *ngIf="acceleration.status === 'completed'" class="badge badge-success" i18n="">Completed</span>
|
||||
<span *ngIf="acceleration.status === 'failed'" class="badge badge-danger" i18n="accelerator.canceled">Canceled</span>
|
||||
</td>
|
||||
<td class="date text-right" *ngIf="!this.widget">
|
||||
<app-time kind="since" [time]="acceleration.added" [fastRender]="true"></app-time>
|
||||
</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -75,6 +80,11 @@
|
|||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="this.accelerationCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
|
|
|
@ -63,16 +63,28 @@ tr, td, th {
|
|||
}
|
||||
|
||||
.txid {
|
||||
@media (max-width: 500px) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.fee {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 15%;
|
||||
@media (max-width: 700px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee, .block, .status {
|
||||
width: 15%;
|
||||
.status {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.date {
|
||||
width: 20%;
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,23 +95,12 @@ tr, td, th {
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee-rate {
|
||||
width: 20%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: end !important;
|
||||
@media (max-width: 975px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
|
@ -108,32 +109,31 @@ tr, td, th {
|
|||
}
|
||||
|
||||
.bid {
|
||||
text-align: end !important;
|
||||
width: 30%;
|
||||
min-width: 150px;
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
width: 25%;
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1200px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee {
|
||||
width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
text-align: end !important;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 20%;
|
||||
@media (max-width: 1200px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
||||
import { Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||
import { combineLatest, BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { WebsocketService } from '../../../services/websocket.service';
|
||||
|
@ -21,9 +21,10 @@ export class AccelerationsListComponent implements OnInit {
|
|||
isLoading = true;
|
||||
paginationMaxSize: number;
|
||||
page = 1;
|
||||
lastPage = 1;
|
||||
accelerationCount: number;
|
||||
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||
skeletonLines: number[] = [];
|
||||
pageSubject: BehaviorSubject<number> = new BehaviorSubject(this.page);
|
||||
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
|
@ -41,9 +42,16 @@ export class AccelerationsListComponent implements OnInit {
|
|||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
|
||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistory$({ timeframe: '1m' }));
|
||||
this.accelerationList$ = accelerationObservable$.pipe(
|
||||
switchMap(accelerations => {
|
||||
this.accelerationList$ = this.pageSubject.pipe(
|
||||
switchMap((page) => {
|
||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ timeframe: '1m', page: page }));
|
||||
return accelerationObservable$.pipe(
|
||||
switchMap(response => {
|
||||
let accelerations = response;
|
||||
if (response.body) {
|
||||
accelerations = response.body;
|
||||
this.accelerationCount = parseInt(response.headers.get('x-total-count'), 10);
|
||||
}
|
||||
if (this.pending) {
|
||||
for (const acceleration of accelerations) {
|
||||
acceleration.status = acceleration.status || 'accelerating';
|
||||
|
@ -53,9 +61,9 @@ export class AccelerationsListComponent implements OnInit {
|
|||
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
||||
}
|
||||
if (this.widget) {
|
||||
return of(accelerations.slice(-6).reverse());
|
||||
return of(accelerations.slice(0, 6));
|
||||
} else {
|
||||
return of(accelerations.reverse());
|
||||
return of(accelerations);
|
||||
}
|
||||
}),
|
||||
catchError((err) => {
|
||||
|
@ -66,6 +74,12 @@ export class AccelerationsListComponent implements OnInit {
|
|||
this.isLoading = false;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.pageSubject.next(page);
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: BlockExtended): number {
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div class="card-wrapper">
|
||||
<div class="card">
|
||||
<div class="card-body more-padding">
|
||||
<app-acceleration-stats timespan="1m" [accelerations$]="minedAccelerations$"></app-acceleration-stats>
|
||||
<app-acceleration-stats timespan="1m"></app-acceleration-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -84,7 +84,7 @@
|
|||
<div class="title-link">
|
||||
<h5 class="card-title d-inline" i18n="accelerator.pending-accelerations">Active Accelerations</h5>
|
||||
</div>
|
||||
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]="true" [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
||||
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]=true [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -60,7 +60,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||
this.accelerations$ = this.stateService.chainTip$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap(() => {
|
||||
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '1m' }).pipe(
|
||||
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '1m', page: 1, pageLength: 100}).pipe(
|
||||
catchError(() => {
|
||||
return of([]);
|
||||
}),
|
||||
|
@ -71,7 +71,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||
|
||||
this.minedAccelerations$ = this.accelerations$.pipe(
|
||||
map(accelerations => {
|
||||
return accelerations.filter(acc => ['mined', 'completed', 'failed'].includes(acc.status));
|
||||
return accelerations.filter(acc => ['mined', 'completed'].includes(acc.status));
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -393,8 +393,11 @@ export interface Acceleration {
|
|||
}
|
||||
|
||||
export interface AccelerationHistoryParams {
|
||||
timeframe?: string,
|
||||
status?: string,
|
||||
pool?: string,
|
||||
blockHash?: string,
|
||||
status?: string;
|
||||
timeframe?: string;
|
||||
poolUniqueId?: number;
|
||||
blockHash?: string;
|
||||
blockHeight?: number;
|
||||
page?: number;
|
||||
pageLength?: number;
|
||||
}
|
|
@ -7,6 +7,7 @@ import { MenuGroup } from '../interfaces/services.interface';
|
|||
import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
|
||||
import { IBackendInfo } from '../interfaces/websocket.interface';
|
||||
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
||||
import { AccelerationStats } from '../components/acceleration/acceleration-stats/acceleration-stats.component';
|
||||
|
||||
export type ProductType = 'enterprise' | 'community' | 'mining_pool' | 'custom';
|
||||
export interface IUser {
|
||||
|
@ -147,4 +148,12 @@ export class ServicesApiServices {
|
|||
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params } });
|
||||
}
|
||||
|
||||
getAccelerationHistoryObserveResponse$(params: AccelerationHistoryParams): Observable<any> {
|
||||
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params }, observe: 'response'});
|
||||
}
|
||||
|
||||
getAccelerationStats$(): Observable<AccelerationStats> {
|
||||
return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue