Merge pull request #4715 from mempool/nymkappa/accel-dashboard-cleanup

[accelerator] improve acceleration list/dashboard
This commit is contained in:
softsimon 2024-03-01 10:17:22 +07:00 committed by GitHub
commit af6af2748c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 117 additions and 100 deletions

View file

@ -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>

View file

@ -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$();
}
}

View file

@ -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>

View file

@ -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) {
width: 20%;
.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 {

View file

@ -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,
@ -40,34 +41,47 @@ 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 => {
if (this.pending) {
for (const acceleration of accelerations) {
acceleration.status = acceleration.status || 'accelerating';
}
}
for (const acc of accelerations) {
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
}
if (this.widget) {
return of(accelerations.slice(-6).reverse());
} else {
return of(accelerations.reverse());
}
}),
catchError((err) => {
this.isLoading = false;
return of([]);
}),
tap(() => {
this.isLoading = false;
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';
}
}
for (const acc of accelerations) {
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
}
if (this.widget) {
return of(accelerations.slice(0, 6));
} else {
return of(accelerations);
}
}),
catchError((err) => {
this.isLoading = false;
return of([]);
}),
tap(() => {
this.isLoading = false;
})
);
})
);
}
pageChange(page: number): void {
this.pageSubject.next(page);
}
trackByBlock(index: number, block: BlockExtended): number {
return block.height;
}

View file

@ -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>

View file

@ -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));
})
);

View file

@ -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;
}

View file

@ -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`);
}
}