2023-07-20 16:26:42 +09:00
|
|
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
2023-12-07 08:26:32 +00:00
|
|
|
import { SeoService } from '../../../services/seo.service';
|
|
|
|
import { WebsocketService } from '../../../services/websocket.service';
|
|
|
|
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
|
|
|
import { StateService } from '../../../services/state.service';
|
2023-12-07 11:12:20 +00:00
|
|
|
import { Observable, Subject, catchError, combineLatest, distinctUntilChanged, interval, map, of, share, startWith, switchMap, tap } from 'rxjs';
|
2023-12-07 08:26:32 +00:00
|
|
|
import { ApiService } from '../../../services/api.service';
|
2023-12-07 11:12:20 +00:00
|
|
|
import { Color } from '../../block-overview-graph/sprite-types';
|
|
|
|
import { hexToColor } from '../../block-overview-graph/utils';
|
|
|
|
import TxView from '../../block-overview-graph/tx-view';
|
|
|
|
import { feeLevels, mempoolFeeColors } from '../../../app.constants';
|
|
|
|
|
|
|
|
const acceleratedColor: Color = hexToColor('8F5FF6');
|
|
|
|
const normalColors = mempoolFeeColors.map(hex => hexToColor(hex + '5F'));
|
2023-07-21 14:13:18 +09:00
|
|
|
|
|
|
|
interface AccelerationBlock extends BlockExtended {
|
|
|
|
accelerationCount: number,
|
|
|
|
}
|
2023-07-20 16:26:42 +09:00
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'app-accelerator-dashboard',
|
|
|
|
templateUrl: './accelerator-dashboard.component.html',
|
|
|
|
styleUrls: ['./accelerator-dashboard.component.scss'],
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
|
})
|
|
|
|
export class AcceleratorDashboardComponent implements OnInit {
|
2023-07-21 14:13:18 +09:00
|
|
|
blocks$: Observable<AccelerationBlock[]>;
|
2023-12-07 08:26:32 +00:00
|
|
|
accelerations$: Observable<Acceleration[]>;
|
2023-12-07 11:12:20 +00:00
|
|
|
pendingAccelerations$: Observable<Acceleration[]>;
|
|
|
|
minedAccelerations$: Observable<Acceleration[]>;
|
2023-07-21 14:13:18 +09:00
|
|
|
loadingBlocks: boolean = true;
|
|
|
|
|
2023-07-20 16:26:42 +09:00
|
|
|
constructor(
|
|
|
|
private seoService: SeoService,
|
|
|
|
private websocketService: WebsocketService,
|
2023-07-21 14:13:18 +09:00
|
|
|
private apiService: ApiService,
|
|
|
|
private stateService: StateService,
|
2023-07-20 16:26:42 +09:00
|
|
|
) {
|
|
|
|
this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`);
|
|
|
|
}
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
|
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
|
2023-07-21 14:13:18 +09:00
|
|
|
|
2023-12-07 11:12:20 +00:00
|
|
|
this.pendingAccelerations$ = interval(30000).pipe(
|
|
|
|
startWith(true),
|
|
|
|
switchMap(() => {
|
2024-01-31 18:38:56 +00:00
|
|
|
return this.apiService.getAccelerations$().pipe(
|
|
|
|
catchError(() => {
|
|
|
|
return of([]);
|
|
|
|
}),
|
|
|
|
);
|
2023-12-07 11:12:20 +00:00
|
|
|
}),
|
|
|
|
share(),
|
|
|
|
);
|
|
|
|
|
2023-12-07 08:26:32 +00:00
|
|
|
this.accelerations$ = this.stateService.chainTip$.pipe(
|
|
|
|
distinctUntilChanged(),
|
2024-01-31 18:38:56 +00:00
|
|
|
switchMap(() => {
|
|
|
|
return this.apiService.getAccelerationHistory$({ timeframe: '1m' }).pipe(
|
|
|
|
catchError(() => {
|
|
|
|
return of([]);
|
|
|
|
}),
|
|
|
|
);
|
2023-12-07 08:26:32 +00:00
|
|
|
}),
|
|
|
|
share(),
|
|
|
|
);
|
|
|
|
|
2023-12-07 11:12:20 +00:00
|
|
|
this.minedAccelerations$ = this.accelerations$.pipe(
|
|
|
|
map(accelerations => {
|
2024-01-31 18:38:56 +00:00
|
|
|
return accelerations.filter(acc => ['mined', 'completed'].includes(acc.status));
|
2023-12-07 11:12:20 +00:00
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2023-07-21 14:13:18 +09:00
|
|
|
this.blocks$ = combineLatest([
|
2023-12-07 08:26:32 +00:00
|
|
|
this.accelerations$,
|
2023-07-21 14:13:18 +09:00
|
|
|
this.stateService.blocks$.pipe(
|
|
|
|
switchMap((blocks) => {
|
|
|
|
if (this.stateService.env.MINING_DASHBOARD === true) {
|
|
|
|
for (const block of blocks) {
|
|
|
|
// @ts-ignore: Need to add an extra field for the template
|
|
|
|
block.extras.pool.logo = `/resources/mining-pools/` +
|
|
|
|
block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return of(blocks as AccelerationBlock[]);
|
2023-12-07 08:26:32 +00:00
|
|
|
}),
|
|
|
|
tap(() => {
|
2023-07-21 14:13:18 +09:00
|
|
|
this.loadingBlocks = false;
|
|
|
|
})
|
|
|
|
)
|
|
|
|
]).pipe(
|
2023-12-07 08:26:32 +00:00
|
|
|
switchMap(([accelerations, blocks]) => {
|
2023-12-03 08:17:59 +00:00
|
|
|
const blockMap = {};
|
|
|
|
for (const block of blocks) {
|
|
|
|
blockMap[block.id] = block;
|
|
|
|
}
|
2023-07-21 14:13:18 +09:00
|
|
|
const accelerationsByBlock: { [ hash: string ]: Acceleration[] } = {};
|
|
|
|
for (const acceleration of accelerations) {
|
2023-12-03 08:17:59 +00:00
|
|
|
if (['mined', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHash]?.extras.pool.id)) {
|
|
|
|
if (!accelerationsByBlock[acceleration.blockHash]) {
|
|
|
|
accelerationsByBlock[acceleration.blockHash] = [];
|
|
|
|
}
|
2023-07-22 15:30:48 +09:00
|
|
|
accelerationsByBlock[acceleration.blockHash].push(acceleration);
|
2023-07-21 14:13:18 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return of(blocks.slice(0, 6).map(block => {
|
|
|
|
block.accelerationCount = (accelerationsByBlock[block.id] || []).length;
|
|
|
|
return block;
|
|
|
|
}));
|
|
|
|
})
|
|
|
|
);
|
2023-07-20 16:26:42 +09:00
|
|
|
}
|
2023-12-07 11:12:20 +00:00
|
|
|
|
|
|
|
getAcceleratorColor(tx: TxView): Color {
|
|
|
|
if (tx.status === 'accelerated' || tx.acc) {
|
|
|
|
return acceleratedColor;
|
|
|
|
} else {
|
|
|
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
|
|
|
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
|
|
|
return normalColors[feeLevelIndex] || normalColors[mempoolFeeColors.length - 1];
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 16:26:42 +09:00
|
|
|
}
|