mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 06:47:52 +01:00
Migrate audit dashboard to main Liquid page
This commit is contained in:
parent
99aa6c9ed3
commit
69747a0028
18 changed files with 279 additions and 884 deletions
|
@ -18,7 +18,7 @@ import { EChartsOption } from '../../graphs/echarts';
|
|||
})
|
||||
export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
@Input() data: any;
|
||||
@Input() height: number | string = '320';
|
||||
@Input() height: number | string = '270';
|
||||
pegsChartOptions: EChartsOption;
|
||||
|
||||
right: number | string = '10';
|
||||
|
|
|
@ -78,9 +78,6 @@
|
|||
<li class="nav-item" routerLinkActive="active" id="btn-assets">
|
||||
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-audit">
|
||||
<a class="nav-link" [routerLink]="['/audit']" (click)="collapse()"><fa-icon [icon]="['fas', 'scale-balanced']" [fixedWidth]="true" i18n-title="master-page.btc-reserves-audit" title="BTC Reserves Audit"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item mr-2" routerLinkActive="active" id="btn-docs">
|
||||
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
|
||||
</li>
|
||||
|
|
|
@ -23,21 +23,11 @@ li.nav-item {
|
|||
margin: auto 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
@media (max-width: 992px) {
|
||||
margin: auto 7px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
@media (max-width: 429px) {
|
||||
margin: auto 5px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
@media (max-width: 369px) {
|
||||
margin: auto 3px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
|
|
|
@ -20,6 +20,9 @@ export class FederationAddressesStatsComponent implements OnInit {
|
|||
this.federationUtxosNumber$ ?? of(undefined)
|
||||
]).pipe(
|
||||
map(([address_count, utxo_count]) => {
|
||||
if (address_count === undefined || utxo_count === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return { address_count, utxo_count}
|
||||
})
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ import { SeoService } from '../../../services/seo.service';
|
|||
})
|
||||
export class RecentPegsListComponent implements OnInit {
|
||||
@Input() widget: boolean = false;
|
||||
@Input() recentPegsList$: Observable<RecentPeg[]> = of([]);
|
||||
@Input() recentPegsList$: Observable<RecentPeg[]>;
|
||||
|
||||
env: Env;
|
||||
isLoading = true;
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
<div class="container-xl dashboard-container" *ngIf="(auditStatus$ | async)?.isAuditSynced; else auditInProgress">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
|
||||
<app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats>
|
||||
</div>
|
||||
<div class="card-body pl-0" style="padding-top: 10px;">
|
||||
<app-reserves-ratio-graph [data]="fullHistory$ | async" [height]="pegRatioGraphHeight"></app-reserves-ratio-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card smaller">
|
||||
<div class="card-body">
|
||||
<app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats>
|
||||
<app-recent-pegs-list [recentPegsList$]="recentPegsList$" [widget]="true"></app-recent-pegs-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card smaller">
|
||||
<div class="card-body">
|
||||
<app-federation-addresses-stats [federationAddressesNumber$]="federationAddressesNumber$" [federationUtxosNumber$]="federationUtxosNumber$"></app-federation-addresses-stats>
|
||||
<app-federation-addresses-list [federationAddresses$]="federationAddresses$" [widget]="true"></app-federation-addresses-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #loadingSkeleton>
|
||||
<div class="container-xl dashboard-container">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-reserves-supply-stats></app-reserves-supply-stats>
|
||||
<app-reserves-ratio></app-reserves-ratio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<app-reserves-ratio-stats></app-reserves-ratio-stats>
|
||||
</div>
|
||||
<div class="card-body pl-0" style="padding-top: 10px;">
|
||||
<app-reserves-ratio-graph></app-reserves-ratio-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-recent-pegs-stats></app-recent-pegs-stats>
|
||||
<app-recent-pegs-list [widget]="true"></app-recent-pegs-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-federation-addresses-stats></app-federation-addresses-stats>
|
||||
<app-federation-addresses-list [widget]="true"></app-federation-addresses-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #auditInProgress>
|
||||
<ng-container *ngIf="(auditStatus$ | async) as auditStatus; else loadingSkeleton">
|
||||
<div class="in-progress-message" *ngIf="auditStatus.lastBlockAudit && auditStatus.bitcoinHeaders; else loadingSkeleton">
|
||||
<span i18n="liquid.audit-in-progress">Audit in progress: Bitcoin block height #{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -1,135 +0,0 @@
|
|||
.dashboard-container {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
.col {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
height: 418px;
|
||||
@media (min-width: 992px) {
|
||||
height: 512px;
|
||||
}
|
||||
&.smaller {
|
||||
height: 408px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card-body.pool-ranking {
|
||||
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
|
||||
}
|
||||
.card-text {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#blockchain-container {
|
||||
position: relative;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
#blockchain-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fade-border {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 80%, transparent 100%)
|
||||
}
|
||||
|
||||
.in-progress-message {
|
||||
position: relative;
|
||||
color: #ffffff91;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.more-padding {
|
||||
padding: 24px 20px !important;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
.card {
|
||||
height: auto !important;
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex: inherit;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 22px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-loader {
|
||||
width: 100%;
|
||||
display: block;
|
||||
&:first-child {
|
||||
max-width: 90px;
|
||||
margin: 15px auto 3px;
|
||||
}
|
||||
&:last-child {
|
||||
margin: 10px auto 3px;
|
||||
max-width: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.lastest-blocks-table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
tr, td, th {
|
||||
border: 0px;
|
||||
padding-top: 0.65rem !important;
|
||||
padding-bottom: 0.8rem !important;
|
||||
}
|
||||
.table-cell-height {
|
||||
width: 25%;
|
||||
}
|
||||
.table-cell-fee {
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
}
|
||||
.table-cell-pool {
|
||||
text-align: left;
|
||||
width: 30%;
|
||||
|
||||
@media (max-width: 875px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pool-name {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
.table-cell-acceleration-count {
|
||||
text-align: right;
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.mempool-block-wrapper {
|
||||
max-height: 380px;
|
||||
max-width: 380px;
|
||||
margin: auto;
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
|
||||
import { SeoService } from '../../../services/seo.service';
|
||||
import { WebsocketService } from '../../../services/websocket.service';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { Observable, Subject, combineLatest, delayWhen, filter, interval, map, of, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime, timer } from 'rxjs';
|
||||
import { ApiService } from '../../../services/api.service';
|
||||
import { AuditStatus, CurrentPegs, FederationAddress, FederationUtxo, PegsVolume, RecentPeg } from '../../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reserves-audit-dashboard',
|
||||
templateUrl: './reserves-audit-dashboard.component.html',
|
||||
styleUrls: ['./reserves-audit-dashboard.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReservesAuditDashboardComponent implements OnInit {
|
||||
auditStatus$: Observable<AuditStatus>;
|
||||
auditUpdated$: Observable<boolean>;
|
||||
currentPeg$: Observable<CurrentPegs>;
|
||||
currentReserves$: Observable<CurrentPegs>;
|
||||
recentPegsList$: Observable<RecentPeg[]>;
|
||||
pegsVolume$: Observable<PegsVolume[]>;
|
||||
federationAddresses$: Observable<FederationAddress[]>;
|
||||
federationAddressesNumber$: Observable<number>;
|
||||
federationUtxosNumber$: Observable<number>;
|
||||
liquidPegsMonth$: Observable<any>;
|
||||
liquidReservesMonth$: Observable<any>;
|
||||
pegRatioGraphHeight: number = 320;
|
||||
fullHistory$: Observable<any>;
|
||||
isLoad: boolean = true;
|
||||
private lastPegBlockUpdate: number = 0;
|
||||
private lastPegAmount: string = '';
|
||||
private lastReservesBlockUpdate: number = 0;
|
||||
|
||||
private destroy$ = new Subject();
|
||||
|
||||
constructor(
|
||||
private seoService: SeoService,
|
||||
private websocketService: WebsocketService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.seoService.setTitle($localize`:@@liquid.reserves-audit:Reserves Audit Dashboard`);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
|
||||
this.auditStatus$ = this.stateService.blocks$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
throttleTime(40000),
|
||||
delayWhen(_ => this.isLoad ? timer(0) : timer(2000)),
|
||||
tap(() => this.isLoad = false),
|
||||
switchMap(() => this.apiService.federationAuditSynced$()),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
this.currentPeg$ = this.auditStatus$.pipe(
|
||||
filter(auditStatus => auditStatus.isAuditSynced === true),
|
||||
switchMap(_ =>
|
||||
this.apiService.liquidPegs$().pipe(
|
||||
filter((currentPegs) => currentPegs.lastBlockUpdate >= this.lastPegBlockUpdate),
|
||||
tap((currentPegs) => {
|
||||
this.lastPegBlockUpdate = currentPegs.lastBlockUpdate;
|
||||
})
|
||||
)
|
||||
),
|
||||
share()
|
||||
);
|
||||
|
||||
this.auditUpdated$ = combineLatest([
|
||||
this.auditStatus$,
|
||||
this.currentPeg$
|
||||
]).pipe(
|
||||
filter(([auditStatus, _]) => auditStatus.isAuditSynced === true),
|
||||
map(([auditStatus, currentPeg]) => ({
|
||||
lastBlockAudit: auditStatus.lastBlockAudit,
|
||||
currentPegAmount: currentPeg.amount
|
||||
})),
|
||||
switchMap(({ lastBlockAudit, currentPegAmount }) => {
|
||||
const blockAuditCheck = lastBlockAudit > this.lastReservesBlockUpdate;
|
||||
const amountCheck = currentPegAmount !== this.lastPegAmount;
|
||||
this.lastPegAmount = currentPegAmount;
|
||||
return of(blockAuditCheck || amountCheck);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.currentReserves$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ =>
|
||||
this.apiService.liquidReserves$().pipe(
|
||||
filter((currentReserves) => currentReserves.lastBlockUpdate >= this.lastReservesBlockUpdate),
|
||||
tap((currentReserves) => {
|
||||
this.lastReservesBlockUpdate = currentReserves.lastBlockUpdate;
|
||||
})
|
||||
)
|
||||
),
|
||||
share()
|
||||
);
|
||||
|
||||
this.recentPegsList$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.recentPegsList$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.pegsVolume$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.pegsVolume$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationAddresses$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationAddresses$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationAddressesNumber$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationAddressesNumber$()),
|
||||
map(count => count.address_count),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationUtxosNumber$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationUtxosNumber$()),
|
||||
map(count => count.utxo_count),
|
||||
share()
|
||||
);
|
||||
|
||||
this.liquidPegsMonth$ = interval(60 * 60 * 1000)
|
||||
.pipe(
|
||||
startWith(0),
|
||||
switchMap(() => this.apiService.listLiquidPegsMonth$()),
|
||||
map((pegs) => {
|
||||
const labels = pegs.map(stats => stats.date);
|
||||
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share(),
|
||||
);
|
||||
|
||||
this.liquidReservesMonth$ = interval(60 * 60 * 1000).pipe(
|
||||
startWith(0),
|
||||
switchMap(() => this.apiService.listLiquidReservesMonth$()),
|
||||
map(reserves => {
|
||||
const labels = reserves.map(stats => stats.date);
|
||||
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$, this.currentReserves$])
|
||||
.pipe(
|
||||
map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => {
|
||||
liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000;
|
||||
|
||||
if (liquidPegs.series.length === liquidReserves?.series.length) {
|
||||
liquidReserves.series[liquidReserves.series.length - 1] = parseFloat(currentReserves?.amount) / 100000000;
|
||||
} else if (liquidPegs.series.length === liquidReserves?.series.length + 1) {
|
||||
liquidReserves.series.push(parseFloat(currentReserves?.amount) / 100000000);
|
||||
liquidReserves.labels.push(liquidPegs.labels[liquidPegs.labels.length - 1]);
|
||||
} else {
|
||||
liquidReserves = {
|
||||
series: [],
|
||||
labels: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
liquidPegs,
|
||||
liquidReserves
|
||||
};
|
||||
}),
|
||||
share()
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(1);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
if (window.innerWidth >= 992) {
|
||||
this.pegRatioGraphHeight = 320;
|
||||
} else if (window.innerWidth >= 768) {
|
||||
this.pegRatioGraphHeight = 230;
|
||||
} else {
|
||||
this.pegRatioGraphHeight = 220;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<div class="echarts" echarts [initOpts]="ratioHistoryChartInitOptions" [options]="ratioHistoryChartOptions" (chartRendered)="rendered()"></div>
|
||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||
<div class="spinner-border text-light"></div>
|
||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||
.loadingGraphs {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(50% - 16px);
|
||||
z-index: 100;
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { formatDate, formatNumber } from '@angular/common';
|
||||
import { EChartsOption } from '../../../graphs/echarts';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reserves-ratio-graph',
|
||||
templateUrl: './reserves-ratio-graph.component.html',
|
||||
styleUrls: ['./reserves-ratio-graph.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReservesRatioGraphComponent implements OnInit, OnChanges {
|
||||
@Input() data: any;
|
||||
@Input() height: number | string = '320';
|
||||
ratioHistoryChartOptions: EChartsOption;
|
||||
ratioSeries: number[] = [];
|
||||
|
||||
right: number | string = '10';
|
||||
top: number | string = '20';
|
||||
left: number | string = '50';
|
||||
template: ('widget' | 'advanced') = 'widget';
|
||||
isLoading = true;
|
||||
|
||||
ratioHistoryChartInitOptions = {
|
||||
renderer: 'svg'
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
// Compute the ratio series: the ratio of the reserves to the pegs
|
||||
this.ratioSeries = this.data.liquidReserves.series.map((value: number, index: number) => value / this.data.liquidPegs.series[index]);
|
||||
// Truncate the ratio series and labels series to last 3 years
|
||||
this.ratioSeries = this.ratioSeries.slice(Math.max(this.ratioSeries.length - 36, 0));
|
||||
this.data.liquidPegs.labels = this.data.liquidPegs.labels.slice(Math.max(this.data.liquidPegs.labels.length - 36, 0));
|
||||
// Cut the values that are too high or too low
|
||||
this.ratioSeries = this.ratioSeries.map((value: number) => Math.min(Math.max(value, 0.995), 1.005));
|
||||
this.ratioHistoryChartOptions = this.createChartOptions(this.ratioSeries, this.data.liquidPegs.labels);
|
||||
}
|
||||
|
||||
rendered() {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
createChartOptions(ratioSeries: number[], labels: string[]): EChartsOption {
|
||||
return {
|
||||
grid: {
|
||||
height: this.height,
|
||||
right: this.right,
|
||||
top: this.top,
|
||||
left: this.left,
|
||||
},
|
||||
animation: false,
|
||||
dataZoom: [{
|
||||
type: 'inside',
|
||||
realtime: true,
|
||||
zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
|
||||
maxSpan: 100,
|
||||
minSpan: 10,
|
||||
}, {
|
||||
show: (this.template === 'advanced') ? true : false,
|
||||
type: 'slider',
|
||||
brushSelect: false,
|
||||
realtime: true,
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 0.45,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0,
|
||||
}
|
||||
}
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: (pos, params, el, elRect, size) => {
|
||||
const obj = { top: -20 };
|
||||
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
|
||||
return obj;
|
||||
},
|
||||
extraCssText: `width: ${(this.template === 'widget') ? '125px' : '135px'};
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;`,
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ${color};"></span>`;
|
||||
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
|
||||
const item = params[0];
|
||||
const formattedValue = formatNumber(item.value, this.locale, '1.5-5');
|
||||
const symbol = (item.value === 1.005) ? '≥ ' : (item.value === 0.995) ? '≤ ' : '';
|
||||
itemFormatted += `<div class="item">
|
||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
||||
<div style="margin-right: 5px"></div>
|
||||
<div class="value">${symbol}${formattedValue}</div>
|
||||
</div>`;
|
||||
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
align: 'center',
|
||||
fontSize: 11,
|
||||
lineHeight: 12
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: labels.map((value: any) => `${formatDate(value, 'MMM\ny', this.locale)}`),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
min: 0.995,
|
||||
max: 1.005,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: ratioSeries,
|
||||
name: '',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 1,
|
||||
width: 1,
|
||||
},
|
||||
data: [{
|
||||
yAxis: 1,
|
||||
label: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
}
|
||||
}],
|
||||
},
|
||||
},
|
||||
],
|
||||
visualMap: {
|
||||
show: false,
|
||||
top: 50,
|
||||
right: 10,
|
||||
pieces: [{
|
||||
gt: 0,
|
||||
lte: 0.999,
|
||||
color: '#D81B60'
|
||||
},
|
||||
{
|
||||
gt: 0.999,
|
||||
lte: 1.001,
|
||||
color: '#FDD835'
|
||||
},
|
||||
{
|
||||
gt: 1.001,
|
||||
lte: 2,
|
||||
color: '#7CB342'
|
||||
}
|
||||
],
|
||||
outOfRange: {
|
||||
color: '#999'
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
|
|||
},
|
||||
title: {
|
||||
show: true,
|
||||
offsetCenter: [0, '-117.5%'],
|
||||
offsetCenter: [0, '-127%'],
|
||||
fontSize: 18,
|
||||
color: '#4a68b9',
|
||||
fontFamily: 'inherit',
|
||||
|
|
|
@ -1,44 +1,29 @@
|
|||
<div *ngIf="(currentPeg$ | async) as currentPeg; else loadingData">
|
||||
<div *ngIf="(currentReserves$ | async) as currentReserves; else loadingData">
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
|
||||
<div class="card-text">
|
||||
<div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
|
||||
<span class="fiat">
|
||||
<span><ng-container i18n="shared.as-of-block">As of block</ng-container> <a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5>
|
||||
<div class="card-text">
|
||||
<div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div>
|
||||
<span class="fiat">
|
||||
<span><ng-container i18n="shared.as-of-block">As of block</ng-container> <a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
|
||||
<div *ngIf="(currentPeg$ | async) as currentPeg; else loadingData" class="card-text">
|
||||
<div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
|
||||
<span class="fiat">
|
||||
<span><ng-container i18n="shared.as-of-block">As of block</ng-container> <a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5>
|
||||
<div *ngIf="(currentReserves$ | async) as currentReserves; else loadingData" class="card-text">
|
||||
<div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div>
|
||||
<span class="fiat">
|
||||
<span><ng-container i18n="shared.as-of-block">As of block</ng-container> <a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #loadingData>
|
||||
<div class="fee-estimation-container loading-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
<div class="container-xl dashboard-container">
|
||||
<div class="container-xl dashboard-container" *ngIf="(network$ | async) !== 'liquid'">
|
||||
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
|
||||
<ng-container *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
|
||||
<ng-container *ngIf="(network$ | async) !== 'liquidtestnet'">
|
||||
<div class="col card-wrapper">
|
||||
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
|
||||
<div class="card">
|
||||
|
@ -17,35 +17,26 @@
|
|||
<div class="col">
|
||||
<div class="card graph-card">
|
||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
||||
<ng-template [ngIf]="(network$ | async) !== 'liquid'" [ngIfElse]="liquidPegs">
|
||||
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
||||
<h5 class="card-title d-inline"><span i18n="dashboard.mempool-goggles">Mempool Goggles</span>: {{ goggleCycle[goggleIndex].name }}</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: #4a68b9"></fa-icon>
|
||||
</a>
|
||||
<div class="quick-filter">
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex" *ngFor="let filter of goggleCycle">
|
||||
<input type="radio" [value]="'3m'" fragment="3m" (click)="goggleIndex = filter.index" [attr.data-cy]="'3m'"> {{ filter.name }}
|
||||
</label>
|
||||
</div>
|
||||
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
||||
<h5 class="card-title d-inline"><span i18n="dashboard.mempool-goggles">Mempool Goggles</span>: {{ goggleCycle[goggleIndex].name }}</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: #4a68b9"></fa-icon>
|
||||
</a>
|
||||
<div class="quick-filter">
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex" *ngFor="let filter of goggleCycle">
|
||||
<input type="radio" [value]="'3m'" fragment="3m" (click)="goggleIndex = filter.index" [attr.data-cy]="'3m'"> {{ filter.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="mempool-block-wrapper">
|
||||
<app-mempool-block-overview
|
||||
[index]="0"
|
||||
[resolution]="goggleResolution"
|
||||
[filterFlags]="goggleCycle[goggleIndex].flag"
|
||||
filterMode="or"
|
||||
></app-mempool-block-overview>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #liquidPegs>
|
||||
<div style="padding-left: 1.25rem;">
|
||||
<ng-container *ngTemplateOutlet="stateService.network === 'liquid' ? lbtcPegs : mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||
<hr>
|
||||
</div>
|
||||
<app-lbtc-pegs-graph [data]="fullHistory$ | async" [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="mempool-block-wrapper">
|
||||
<app-mempool-block-overview
|
||||
[index]="0"
|
||||
[resolution]="goggleResolution"
|
||||
[filterFlags]="goggleCycle[goggleIndex].flag"
|
||||
filterMode="or"
|
||||
></app-mempool-block-overview>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,7 +44,6 @@
|
|||
<div class="card graph-card">
|
||||
<div class="card-body">
|
||||
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||
<ng-container *ngIf="stateService.network !== 'liquid'">
|
||||
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
|
||||
<div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats">
|
||||
<app-incoming-transactions-graph
|
||||
|
@ -64,30 +54,11 @@
|
|||
[windowPreferenceOverride]="'2h'"
|
||||
></app-incoming-transactions-graph>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="mempool-graph" *ngIf="stateService.network === 'liquid'">
|
||||
<hr>
|
||||
<table class="table table-borderless table-striped" *ngIf="(featuredAssets$ | async) as featuredAssets else loadingAssetsTable">
|
||||
<tbody>
|
||||
<tr *ngFor="let group of featuredAssets">
|
||||
<td class="asset-icon">
|
||||
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">
|
||||
<img class="assetIcon" [src]="'/api/v1/asset/' + group.asset + '/icon'">
|
||||
</a>
|
||||
</td>
|
||||
<td class="asset-title">
|
||||
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">{{ group.name }}</a>
|
||||
</td>
|
||||
<td class="circulating-amount"><app-asset-circulation [assetId]="group.asset"></app-asset-circulation></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" style="max-height: 410px">
|
||||
<div class="card" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else latestBlocks">
|
||||
<div class="card" *ngIf="(network$ | async) !== 'liquidtestnet'; else latestBlocks">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.recent-rbf-replacements">Recent Replacements</h5>
|
||||
|
@ -171,7 +142,7 @@
|
|||
<app-truncate [text]="transaction.txid" [lastChars]="5"></app-truncate>
|
||||
</a>
|
||||
</td>
|
||||
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
|
||||
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
|
||||
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
|
||||
<td class="table-cell-fees"><app-fee-rate [fee]="transaction.fee" [weight]="transaction.vsize * 4"></app-fee-rate></td>
|
||||
</tr>
|
||||
|
@ -184,26 +155,52 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingAssetsTable>
|
||||
<table class="table table-borderless table-striped asset-table">
|
||||
<tbody>
|
||||
<tr *ngFor="let i of getArrayFromNumber(this.nbFeaturedAssets)">
|
||||
<td class="asset-icon">
|
||||
<div class="skeleton-loader skeleton-loader-transactions"></div>
|
||||
</td>
|
||||
<td class="asset-title">
|
||||
<div class="skeleton-loader skeleton-loader-transactions"></div>
|
||||
</td>
|
||||
<td class="asset-title d-none d-md-table-cell">
|
||||
<div class="skeleton-loader skeleton-loader-transactions"></div>
|
||||
</td>
|
||||
<td class="asset-title">
|
||||
<div class="skeleton-loader skeleton-loader-transactions"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="(network$ | async) === 'liquid'">
|
||||
<div class="container-xl dashboard-container" *ngIf="(auditStatus$ | async)?.isAuditSynced; else auditInProgress">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
|
||||
<div class="col">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-body">
|
||||
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
|
||||
<app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-title card-title-liquid">
|
||||
<app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats>
|
||||
</div>
|
||||
<h5 *ngIf="fullHistory$ | async" class="card-title peg-historical-data">Peg Historical Data</h5>
|
||||
<div class="card-body pl-0" style="padding-top: 10px;">
|
||||
<app-lbtc-pegs-graph [data]="fullHistory$ | async" [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-liquid smaller">
|
||||
<div class="card-body">
|
||||
<app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats>
|
||||
<app-recent-pegs-list [recentPegsList$]="recentPegsList$" [widget]="true"></app-recent-pegs-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card-liquid card smaller">
|
||||
<div class="card-body">
|
||||
<app-federation-addresses-stats [federationAddressesNumber$]="federationAddressesNumber$" [federationUtxosNumber$]="federationUtxosNumber$"></app-federation-addresses-stats>
|
||||
<app-federation-addresses-list [federationAddresses$]="federationAddresses$" [widget]="true"></app-federation-addresses-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #replacementsSkeleton>
|
||||
<tbody>
|
||||
|
@ -283,21 +280,56 @@
|
|||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #lbtcPegs let-mempoolInfoData>
|
||||
<div class="mempool-info-data lbtc-pegs-stats">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
|
||||
<ng-container *ngIf="(currentPeg$ | async) as currentPeg; else loadingTransactions">
|
||||
<div i18n-ngbTooltip="liquid.last-elements-audit-block" [ngbTooltip]="'L-BTC supply last updated at Liquid block ' + (currentPeg.lastBlockUpdate)" placement="top" class="card-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a class="title-link" [routerLink]="['/audit' | relativeUrl]">
|
||||
<h5 class="card-title"><ng-container i18n="dashboard.btc-reserves">BTC Reserves</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
|
||||
</a>
|
||||
<ng-container *ngIf="(currentReserves$ | async) as currentReserves; else loadingTransactions">
|
||||
<div i18n-ngbTooltip="liquid.last-bitcoin-audit-block" [ngbTooltip]="'BTC reserves last updated at Bitcoin block ' + (currentReserves.lastBlockUpdate)" placement="top" class="card-text">{{ +(currentReserves.amount) / 100000000 | number: '1.2-2' }} <span class="bitcoin-color">BTC</span></div>
|
||||
</ng-container>
|
||||
<ng-template #loadingSkeletonLiquid>
|
||||
<div class="container-xl dashboard-container">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
|
||||
<div class="col">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-body">
|
||||
<app-reserves-supply-stats></app-reserves-supply-stats>
|
||||
<app-reserves-ratio></app-reserves-ratio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-title card-title-liquid">
|
||||
<app-reserves-ratio-stats></app-reserves-ratio-stats>
|
||||
</div>
|
||||
<div class="card-body pl-0" style="padding-top: 10px;">
|
||||
<app-lbtc-pegs-graph></app-lbtc-pegs-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-liquid smaller">
|
||||
<div class="card-body">
|
||||
<app-recent-pegs-stats></app-recent-pegs-stats>
|
||||
<app-recent-pegs-list [widget]="true"></app-recent-pegs-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card-liquid card smaller">
|
||||
<div class="card-body">
|
||||
<app-federation-addresses-stats></app-federation-addresses-stats>
|
||||
<app-federation-addresses-list [widget]="true"></app-federation-addresses-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #auditInProgress>
|
||||
<ng-container *ngIf="(auditStatus$ | async) as auditStatus; else loadingSkeletonLiquid">
|
||||
<div class="in-progress-message" *ngIf="auditStatus.lastBlockAudit && auditStatus.bitcoinHeaders; else loadingSkeletonLiquid">
|
||||
<span i18n="liquid.audit-in-progress">Audit in progress: Bitcoin block height #{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -413,3 +413,39 @@
|
|||
margin-top: 5px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.card-liquid {
|
||||
background-color: #1d1f31;
|
||||
height: 418px;
|
||||
@media (min-width: 992px) {
|
||||
height: 512px;
|
||||
}
|
||||
&.smaller {
|
||||
height: 408px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title-liquid {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.peg-historical-data {
|
||||
font-size: 18px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
padding-top: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.in-progress-message {
|
||||
position: relative;
|
||||
color: #ffffff91;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
||||
import { combineLatest, EMPTY, fromEvent, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
||||
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
||||
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
||||
import { AuditStatus, BlockExtended, CurrentPegs, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
|
||||
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
|
||||
import { ApiService } from '../services/api.service';
|
||||
import { StateService } from '../services/state.service';
|
||||
|
@ -32,8 +32,6 @@ interface MempoolStatsData {
|
|||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
featuredAssets$: Observable<any>;
|
||||
nbFeaturedAssets = 6;
|
||||
network$: Observable<string>;
|
||||
mempoolBlocksData$: Observable<MempoolBlocksData>;
|
||||
mempoolInfoData$: Observable<MempoolInfoData>;
|
||||
|
@ -53,13 +51,18 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
auditUpdated$: Observable<boolean>;
|
||||
liquidReservesMonth$: Observable<any>;
|
||||
currentReserves$: Observable<CurrentPegs>;
|
||||
recentPegsList$: Observable<RecentPeg[]>;
|
||||
pegsVolume$: Observable<PegsVolume[]>;
|
||||
federationAddresses$: Observable<FederationAddress[]>;
|
||||
federationAddressesNumber$: Observable<number>;
|
||||
federationUtxosNumber$: Observable<number>;
|
||||
fullHistory$: Observable<any>;
|
||||
isLoad: boolean = true;
|
||||
mempoolInfoSubscription: Subscription;
|
||||
currencySubscription: Subscription;
|
||||
currency: string;
|
||||
incomingGraphHeight: number = 300;
|
||||
lbtcPegGraphHeight: number = 320;
|
||||
lbtcPegGraphHeight: number = 250;
|
||||
private lastPegBlockUpdate: number = 0;
|
||||
private lastPegAmount: string = '';
|
||||
private lastReservesBlockUpdate: number = 0;
|
||||
|
@ -155,26 +158,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
})
|
||||
);
|
||||
|
||||
const windowResize$ = fromEvent(window, 'resize').pipe(
|
||||
distinctUntilChanged(),
|
||||
startWith(null)
|
||||
);
|
||||
|
||||
this.featuredAssets$ = combineLatest([
|
||||
this.apiService.listFeaturedAssets$(),
|
||||
windowResize$
|
||||
]).pipe(
|
||||
map(([featured, _]) => {
|
||||
const newArray = [];
|
||||
for (const feature of featured) {
|
||||
if (feature.ticker !== 'L-BTC' && feature.asset) {
|
||||
newArray.push(feature);
|
||||
}
|
||||
}
|
||||
return newArray.slice(0, this.nbFeaturedAssets);
|
||||
}),
|
||||
);
|
||||
|
||||
this.transactions$ = this.stateService.transactions$
|
||||
.pipe(
|
||||
scan((acc, tx) => {
|
||||
|
@ -240,7 +223,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
share(),
|
||||
);
|
||||
|
||||
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
||||
if (this.stateService.network === 'liquid') {
|
||||
this.auditStatus$ = this.stateService.blocks$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
throttleTime(40000),
|
||||
|
@ -250,22 +233,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
shareReplay(1)
|
||||
);
|
||||
|
||||
////////// Pegs historical data //////////
|
||||
this.liquidPegsMonth$ = this.auditStatus$.pipe(
|
||||
throttleTime(60 * 60 * 1000),
|
||||
switchMap(() => this.apiService.listLiquidPegsMonth$()),
|
||||
map((pegs) => {
|
||||
const labels = pegs.map(stats => stats.date);
|
||||
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share(),
|
||||
);
|
||||
|
||||
this.currentPeg$ = this.auditStatus$.pipe(
|
||||
switchMap(_ =>
|
||||
this.apiService.liquidPegs$().pipe(
|
||||
|
@ -278,7 +245,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
share()
|
||||
);
|
||||
|
||||
////////// BTC Reserves historical data //////////
|
||||
this.auditUpdated$ = combineLatest([
|
||||
this.auditStatus$,
|
||||
this.currentPeg$
|
||||
|
@ -293,21 +259,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
const amountCheck = currentPegAmount !== this.lastPegAmount;
|
||||
this.lastPegAmount = currentPegAmount;
|
||||
return of(blockAuditCheck || amountCheck);
|
||||
})
|
||||
);
|
||||
|
||||
this.liquidReservesMonth$ = this.auditStatus$.pipe(
|
||||
throttleTime(60 * 60 * 1000),
|
||||
switchMap((auditStatus) => {
|
||||
return auditStatus.isAuditSynced ? this.apiService.listLiquidReservesMonth$() : EMPTY;
|
||||
}),
|
||||
map(reserves => {
|
||||
const labels = reserves.map(stats => stats.date);
|
||||
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
@ -326,11 +277,78 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
share()
|
||||
);
|
||||
|
||||
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$.pipe(startWith(null)), this.currentReserves$.pipe(startWith(null))])
|
||||
this.recentPegsList$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.recentPegsList$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.pegsVolume$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.pegsVolume$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationAddresses$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationAddresses$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationAddressesNumber$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationAddressesNumber$()),
|
||||
map(count => count.address_count),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationUtxosNumber$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationUtxosNumber$()),
|
||||
map(count => count.utxo_count),
|
||||
share()
|
||||
);
|
||||
|
||||
this.liquidPegsMonth$ = interval(60 * 60 * 1000)
|
||||
.pipe(
|
||||
startWith(0),
|
||||
switchMap(() => this.apiService.listLiquidPegsMonth$()),
|
||||
map((pegs) => {
|
||||
const labels = pegs.map(stats => stats.date);
|
||||
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share(),
|
||||
);
|
||||
|
||||
this.liquidReservesMonth$ = interval(60 * 60 * 1000).pipe(
|
||||
startWith(0),
|
||||
switchMap(() => this.apiService.listLiquidReservesMonth$()),
|
||||
map(reserves => {
|
||||
const labels = reserves.map(stats => stats.date);
|
||||
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
|
||||
return {
|
||||
series,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$, this.currentReserves$])
|
||||
.pipe(
|
||||
map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => {
|
||||
liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000;
|
||||
|
||||
|
||||
if (liquidPegs.series.length === liquidReserves?.series.length) {
|
||||
liquidReserves.series[liquidReserves.series.length - 1] = parseFloat(currentReserves?.amount) / 100000000;
|
||||
} else if (liquidPegs.series.length === liquidReserves?.series.length + 1) {
|
||||
|
@ -342,7 +360,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
labels: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
liquidPegs,
|
||||
liquidReserves
|
||||
|
@ -374,24 +392,21 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
getArrayFromNumber(num: number): number[] {
|
||||
return Array.from({ length: num }, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
if (window.innerWidth >= 992) {
|
||||
this.incomingGraphHeight = 300;
|
||||
this.goggleResolution = 82;
|
||||
this.lbtcPegGraphHeight = 320;
|
||||
this.nbFeaturedAssets = 6;
|
||||
this.lbtcPegGraphHeight = 270;
|
||||
} else if (window.innerWidth >= 768) {
|
||||
this.incomingGraphHeight = 215;
|
||||
this.goggleResolution = 80;
|
||||
this.lbtcPegGraphHeight = 230;
|
||||
this.nbFeaturedAssets = 4;
|
||||
this.lbtcPegGraphHeight = 190;
|
||||
} else {
|
||||
this.incomingGraphHeight = 180;
|
||||
this.goggleResolution = 86;
|
||||
this.lbtcPegGraphHeight = 220;
|
||||
this.nbFeaturedAssets = 4;
|
||||
this.lbtcPegGraphHeight = 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@ import { FeeDistributionGraphComponent } from '../components/fee-distribution-gr
|
|||
import { IncomingTransactionsGraphComponent } from '../components/incoming-transactions-graph/incoming-transactions-graph.component';
|
||||
import { MempoolGraphComponent } from '../components/mempool-graph/mempool-graph.component';
|
||||
import { LbtcPegsGraphComponent } from '../components/lbtc-pegs-graph/lbtc-pegs-graph.component';
|
||||
import { ReservesSupplyStatsComponent } from '../components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component';
|
||||
import { ReservesRatioStatsComponent } from '../components/liquid-reserves-audit/reserves-ratio-stats/reserves-ratio-stats.component';
|
||||
import { ReservesRatioComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component';
|
||||
import { RecentPegsStatsComponent } from '../components/liquid-reserves-audit/recent-pegs-stats/recent-pegs-stats.component';
|
||||
import { RecentPegsListComponent } from '../components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component';
|
||||
import { FederationAddressesStatsComponent } from '../components/liquid-reserves-audit/federation-addresses-stats/federation-addresses-stats.component';
|
||||
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
|
||||
import { GraphsComponent } from '../components/graphs/graphs.component';
|
||||
import { StatisticsComponent } from '../components/statistics/statistics.component';
|
||||
import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component';
|
||||
|
@ -48,6 +55,13 @@ import { CommonModule } from '@angular/common';
|
|||
IncomingTransactionsGraphComponent,
|
||||
MempoolGraphComponent,
|
||||
LbtcPegsGraphComponent,
|
||||
ReservesSupplyStatsComponent,
|
||||
ReservesRatioStatsComponent,
|
||||
ReservesRatioComponent,
|
||||
RecentPegsStatsComponent,
|
||||
RecentPegsListComponent,
|
||||
FederationAddressesStatsComponent,
|
||||
FederationAddressesListComponent,
|
||||
HashrateChartComponent,
|
||||
HashrateChartPoolsComponent,
|
||||
BlockHealthGraphComponent,
|
||||
|
|
|
@ -15,17 +15,10 @@ import { AssetsComponent } from '../components/assets/assets.component';
|
|||
import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component'
|
||||
import { AssetComponent } from '../components/asset/asset.component';
|
||||
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
|
||||
import { ReservesAuditDashboardComponent } from '../components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component';
|
||||
import { ReservesSupplyStatsComponent } from '../components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component';
|
||||
import { RecentPegsStatsComponent } from '../components/liquid-reserves-audit/recent-pegs-stats/recent-pegs-stats.component';
|
||||
import { RecentPegsListComponent } from '../components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component';
|
||||
import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component';
|
||||
import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component';
|
||||
import { FederationAddressesStatsComponent } from '../components/liquid-reserves-audit/federation-addresses-stats/federation-addresses-stats.component';
|
||||
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
|
||||
import { ReservesRatioComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component';
|
||||
import { ReservesRatioStatsComponent } from '../components/liquid-reserves-audit/reserves-ratio-stats/reserves-ratio-stats.component';
|
||||
import { ReservesRatioGraphComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio-graph.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -77,18 +70,6 @@ const routes: Routes = [
|
|||
data: { preload: true, networkSpecific: true },
|
||||
loadChildren: () => import('../components/block/block.module').then(m => m.BlockModule),
|
||||
},
|
||||
{
|
||||
path: 'audit',
|
||||
data: { networks: ['liquid'] },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: { networks: ['liquid'] },
|
||||
component: ReservesAuditDashboardComponent,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'audit/wallet',
|
||||
data: { networks: ['liquid'] },
|
||||
|
@ -180,17 +161,8 @@ export class LiquidRoutingModule { }
|
|||
],
|
||||
declarations: [
|
||||
LiquidMasterPageComponent,
|
||||
ReservesAuditDashboardComponent,
|
||||
ReservesSupplyStatsComponent,
|
||||
RecentPegsStatsComponent,
|
||||
RecentPegsListComponent,
|
||||
FederationWalletComponent,
|
||||
FederationUtxosListComponent,
|
||||
FederationAddressesStatsComponent,
|
||||
FederationAddressesListComponent,
|
||||
ReservesRatioComponent,
|
||||
ReservesRatioStatsComponent,
|
||||
ReservesRatioGraphComponent,
|
||||
]
|
||||
})
|
||||
export class LiquidMasterPageModule { }
|
Loading…
Add table
Reference in a new issue