Add expiry filter to Federation UTXOs table
Unify Liquid dashboard with mempool dashboard
This commit is contained in:
@@ -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 = '360';
|
||||
pegsChartOptions: EChartsOption;
|
||||
|
||||
right: number | string = '10';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<a class="title-link" [routerLink]="['/audit/wallet/utxos' | relativeUrl]" [fragment]="'expired'">
|
||||
<h5 class="card-title" i18n="liquid.non-dust-expired">Non-Dust Expired</h5>
|
||||
</a>
|
||||
<div *ngIf="(stats$ | async) as expiredStats; else loadingData" class="card-text">
|
||||
<div class="fee-text">{{ (+expiredStats.nonDust.total) / 100000000 | number: '1.5-5' }} <span style="color: #b86d12;">BTC</span></div>
|
||||
<div class="fiat">{{ expiredStats.nonDust.count }} <span i18n="shared.utxos">UTXOs</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<a class="title-link" [routerLink]="['/audit/wallet/utxos' | relativeUrl]" [fragment]="'expired'">
|
||||
<h5 class="card-title"><ng-container i18n="liquid.total-expired">Total Expired</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
|
||||
</a>
|
||||
<div *ngIf="(stats$ | async) as expiredStats; else loadingData" class="card-text">
|
||||
<div class="fee-text">{{ (+expiredStats.all.total) / 100000000 | number: '1.5-5' }} <span style="color: #b86d12;">BTC</span></div>
|
||||
<div class="fiat">{{ expiredStats.all.count }} <span i18n="shared.utxos">UTXOs</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #loadingData>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
.fee-estimation-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@media (min-width: 376px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
.item {
|
||||
max-width: 150px;
|
||||
margin: 0;
|
||||
width: -webkit-fill-available;
|
||||
@media (min-width: 376px) {
|
||||
margin: 0 auto 0px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 22px;
|
||||
span {
|
||||
font-size: 11px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
.fee-text{
|
||||
border-bottom: 1px solid #ffffff1c;
|
||||
color: #ffffff;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
line-height: 1.45;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.fiat {
|
||||
display: block;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container{
|
||||
min-height: 76px;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
.skeleton-loader {
|
||||
width: 100%;
|
||||
display: block;
|
||||
&:first-child {
|
||||
max-width: 90px;
|
||||
margin: 15px auto 3px;
|
||||
}
|
||||
&:last-child {
|
||||
margin: 10px auto 3px;
|
||||
max-width: 55px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable, map, of } from 'rxjs';
|
||||
import { FederationUtxo } from '../../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-expired-utxos-stats',
|
||||
templateUrl: './expired-utxos-stats.component.html',
|
||||
styleUrls: ['./expired-utxos-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ExpiredUtxosStatsComponent implements OnInit {
|
||||
@Input() expiredUtxos$: Observable<FederationUtxo[]>;
|
||||
|
||||
stats$: Observable<any>;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.stats$ = this.expiredUtxos$?.pipe(
|
||||
map((utxos: FederationUtxo[]) => {
|
||||
const stats = { nonDust: { count: 0, total: 0 }, all: { count: 0, total: 0 } };
|
||||
utxos.forEach((utxo: FederationUtxo) => {
|
||||
stats.all.count++;
|
||||
stats.all.total += utxo.amount;
|
||||
if (!utxo.isDust) {
|
||||
stats.nonDust.count++;
|
||||
stats.nonDust.total += utxo.amount;
|
||||
}
|
||||
});
|
||||
return stats;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
<div [ngClass]="{'widget': widget}">
|
||||
|
||||
<div *ngIf="!widget" class="form-check">
|
||||
<div style="padding-left: 0.75rem;">
|
||||
<input style="margin-top: 6px" class="form-check-input" type="checkbox" [checked]="showExpiredUtxosToggle$ | async" id="show-expired-utxos" (change)="onShowExpiredUtxosToggleChange($event)">
|
||||
<label class="form-check-label" for="show-expired-utxos">
|
||||
<small i18n="liquid.include-expired-utxos">Expired UTXOs</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="min-height: 295px">
|
||||
@@ -10,6 +19,9 @@
|
||||
<th class="amount text-right" [ngClass]="{'widget': widget}" i18n="shared.amount">Amount</th>
|
||||
<th class="pegin text-left" *ngIf="!widget" i18n="liquid.related-peg-in">Related Peg-In</th>
|
||||
<th class="timestamp text-left" i18n="shared.date" [ngClass]="{'widget': widget}">Date</th>
|
||||
<th class="expires-in text-left" *ngIf="!widget && showExpiredUtxos === false" i18n="liquid.expires-in">Expires in</th>
|
||||
<th class="expires-in text-left" *ngIf="!widget && showExpiredUtxos === true" i18n="liquid.expired-since">Expired since</th>
|
||||
<th class="is-dust text-right" *ngIf="!widget && showExpiredUtxos === true" i18n="liquid.is-dust">Is Dust</th>
|
||||
</thead>
|
||||
<tbody *ngIf="federationUtxos$ | async as utxos; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<ng-container *ngIf="widget; else regularRows">
|
||||
@@ -56,6 +68,13 @@
|
||||
‎{{ utxo.blocktime * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="symbol lg-inline relative-time"><i>(<app-time kind="since" [time]="utxo.blocktime"></app-time>)</i></div>
|
||||
</td>
|
||||
<td class="expires-in text-left" [ngStyle]="{ 'color': getGradientColor(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) }">
|
||||
{{ utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate < 0 ? -(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) : utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate }} <span i18n="shared.blocks" class="symbol">blocks</span>
|
||||
</td>
|
||||
<td *ngIf="!widget && showExpiredUtxos === true" class="is-dust text-right" [ngStyle]="{ 'color': !utxo.isDust ? '#D81B60' : '' }">
|
||||
<div i18n="shared.yes" *ngIf="utxo.isDust">Yes</div>
|
||||
<div i18n="shared.no" *ngIf="!utxo.isDust">No</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</tbody>
|
||||
@@ -90,6 +109,9 @@
|
||||
<td class="timestamp text-left">
|
||||
<span class="skeleton-loader" style="max-width: 140px"></span>
|
||||
</td>
|
||||
<td class="expires-in text-left">
|
||||
<span class="skeleton-loader" style="max-width: 140px"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
@@ -72,7 +72,7 @@ tr, td, th {
|
||||
@media (max-width: 800px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
@media (max-width: 1190px) {
|
||||
.relative-time {
|
||||
display: none;
|
||||
}
|
||||
@@ -92,3 +92,15 @@ tr, td, th {
|
||||
}
|
||||
}
|
||||
|
||||
.expires-in {
|
||||
@media (max-width: 987px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.is-dust {
|
||||
@media (max-width: 1090px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Observable, Subject, combineLatest, of, timer } from 'rxjs';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Observable, Subject, combineLatest, of, timer } from 'rxjs';
|
||||
import { delayWhen, filter, map, share, shareReplay, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
||||
import { ApiService } from '../../../services/api.service';
|
||||
import { Env, StateService } from '../../../services/state.service';
|
||||
@@ -24,6 +25,9 @@ export class FederationUtxosListComponent implements OnInit {
|
||||
skeletonLines: number[] = [];
|
||||
auditStatus$: Observable<AuditStatus>;
|
||||
auditUpdated$: Observable<boolean>;
|
||||
showExpiredUtxos: boolean = false;
|
||||
showExpiredUtxosToggleSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(this.showExpiredUtxos);
|
||||
showExpiredUtxosToggle$: Observable<boolean> = this.showExpiredUtxosToggleSubject.asObservable();
|
||||
lastReservesBlockUpdate: number = 0;
|
||||
currentPeg$: Observable<CurrentPegs>;
|
||||
lastPegBlockUpdate: number = 0;
|
||||
@@ -36,6 +40,8 @@ export class FederationUtxosListComponent implements OnInit {
|
||||
private apiService: ApiService,
|
||||
public stateService: StateService,
|
||||
private websocketService: WebsocketService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -45,7 +51,12 @@ export class FederationUtxosListComponent implements OnInit {
|
||||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||
|
||||
if (!this.widget) {
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
this.showExpiredUtxosToggleSubject.next(['expired'].indexOf(fragment) > -1);
|
||||
});
|
||||
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.auditStatus$ = this.stateService.blocks$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
throttleTime(40000),
|
||||
@@ -70,27 +81,30 @@ export class FederationUtxosListComponent implements OnInit {
|
||||
|
||||
this.auditUpdated$ = combineLatest([
|
||||
this.auditStatus$,
|
||||
this.currentPeg$
|
||||
this.currentPeg$,
|
||||
this.showExpiredUtxosToggle$
|
||||
]).pipe(
|
||||
filter(([auditStatus, _]) => auditStatus.isAuditSynced === true),
|
||||
map(([auditStatus, currentPeg]) => ({
|
||||
filter(([auditStatus, _, __]) => auditStatus.isAuditSynced === true),
|
||||
map(([auditStatus, currentPeg, showExpiredUtxos]) => ({
|
||||
lastBlockAudit: auditStatus.lastBlockAudit,
|
||||
currentPegAmount: currentPeg.amount
|
||||
currentPegAmount: currentPeg.amount,
|
||||
showExpiredUtxos: showExpiredUtxos
|
||||
})),
|
||||
switchMap(({ lastBlockAudit, currentPegAmount }) => {
|
||||
switchMap(({ lastBlockAudit, currentPegAmount, showExpiredUtxos }) => {
|
||||
const blockAuditCheck = lastBlockAudit > this.lastReservesBlockUpdate;
|
||||
const amountCheck = currentPegAmount !== this.lastPegAmount;
|
||||
const expiredCheck = showExpiredUtxos !== this.showExpiredUtxos;
|
||||
this.lastReservesBlockUpdate = lastBlockAudit;
|
||||
this.lastPegAmount = currentPegAmount;
|
||||
return of(blockAuditCheck || amountCheck);
|
||||
this.showExpiredUtxos = showExpiredUtxos;
|
||||
return of(blockAuditCheck || amountCheck || expiredCheck);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationUtxos$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.federationUtxos$()),
|
||||
switchMap(_ => this.showExpiredUtxos ? this.apiService.expiredUtxos$() : this.apiService.federationUtxos$()),
|
||||
tap(_ => this.isLoading = false),
|
||||
share()
|
||||
);
|
||||
@@ -106,4 +120,38 @@ export class FederationUtxosListComponent implements OnInit {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
onShowExpiredUtxosToggleChange(e): void {
|
||||
const fragment = e.target.checked ? 'expired' : null;
|
||||
this.router.navigate([], { fragment });
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
getGradientColor(value: number): string {
|
||||
const distanceToGreen = Math.abs(4032 - value);
|
||||
const green = '#7CB342';
|
||||
const red = '#D81B60';
|
||||
|
||||
if (value < 0) {
|
||||
return red;
|
||||
} else if (value >= 4032) {
|
||||
return green;
|
||||
} else {
|
||||
const scaleFactor = 1 - distanceToGreen / 4032;
|
||||
const r = parseInt(red.slice(1, 3), 16);
|
||||
const g = parseInt(green.slice(1, 3), 16);
|
||||
const b = parseInt(red.slice(5, 7), 16);
|
||||
|
||||
const newR = Math.floor(r + (g - r) * scaleFactor);
|
||||
const newG = Math.floor(g - (g - r) * scaleFactor);
|
||||
const newB = b;
|
||||
|
||||
return '#' + this.componentToHex(newR) + this.componentToHex(newG) + this.componentToHex(newB);
|
||||
}
|
||||
}
|
||||
|
||||
componentToHex(c: number): string {
|
||||
const hex = c.toString(16);
|
||||
return hex.length == 1 ? '0' + hex : hex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="container-xl">
|
||||
<div class="container-xl" style="max-width: 1400px;">
|
||||
<div>
|
||||
<h1 i18n="liquid.federation-wallet">Liquid Federation Wallet</h1>
|
||||
</div>
|
||||
@@ -7,7 +7,7 @@
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" [routerLink]="['/audit/wallet/utxos' | relativeUrl]" routerLinkActive="active">UTXOs</a>
|
||||
|
||||
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]" routerLinkActive="active"><ng-container i18n="mining.addresses">Addresses</ng-container></a>
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
<div *ngIf="(unbackedMonths$ | async) as unbackedMonths; else loadingData">
|
||||
<ng-container *ngIf="unbackedMonths.historyComplete; else loadingData">
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="liquid.unpeg">Unpeg</h5>
|
||||
<div class="card-text">
|
||||
<div class="fee-text" [ngClass]="{'danger' : unbackedMonths.total > 0, 'correct': unbackedMonths.total === 0}">
|
||||
{{ unbackedMonths.total }} <span i18n="liquid.unpeg-event">Unpeg Event</span>
|
||||
</div>
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="liquid.unpeg">Unpeg</h5>
|
||||
<div *ngIf="(unbackedMonths$ | async) as unbackedMonths; else loadingData" class="card-text">
|
||||
<ng-container *ngIf="unbackedMonths.historyComplete; else loadingData">
|
||||
<div class="fee-text" [ngClass]="{'danger' : unbackedMonths.total > 0, 'correct': unbackedMonths.total === 0}">
|
||||
{{ unbackedMonths.total }} <span i18n="liquid.unpeg-event">Unpeg Event</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="liquid.avg-peg-ratio">Avg Peg Ratio</h5>
|
||||
<div class="card-text">
|
||||
<div class="fee-text" [ngClass]="{'danger' : unbackedMonths.avg < 1, 'correct': unbackedMonths.avg >= 1}">
|
||||
{{ (unbackedMonths.avg * 100).toFixed(3) }} %
|
||||
</div>
|
||||
<div class="item avg-ratio">
|
||||
<h5 class="card-title" i18n="liquid.avg-peg-ratio">Avg Peg Ratio</h5>
|
||||
<div *ngIf="(unbackedMonths$ | async) as unbackedMonths; else loadingData" class="card-text">
|
||||
<ng-container *ngIf="unbackedMonths.historyComplete; else loadingData">
|
||||
<div class="fee-text" [ngClass]="{'danger' : unbackedMonths.avg < 1, 'correct': unbackedMonths.avg >= 1}">
|
||||
{{ (unbackedMonths.avg * 100).toFixed(3) }} %
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingData>
|
||||
<div class="fee-estimation-container loading-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="liquid.unpeg">Unpeg</h5>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="liquid.avg-peg-ratio">Avg Peg Ratio</h5>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="item">
|
||||
<!-- <a class="title-link" [routerLink]="['/audit/emergency-spends' | relativeUrl]">
|
||||
<h5 class="card-title"><ng-container i18n="liquid.forfeited-utxos">Forfeited UTXOs</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
|
||||
</a> -->
|
||||
<h5 class="card-title" i18n="liquid.emergency-keys">Emergency Keys</h5>
|
||||
<div *ngIf="(emergencyUtxosStats$ | async) as emergencyUtxosStats; else loadingData" class="card-text">
|
||||
<div class="fee-text" [ngClass]="{'danger' : emergencyUtxosStats.utxo_count > 0, 'correct': emergencyUtxosStats.utxo_count === 0}">
|
||||
{{ emergencyUtxosStats.utxo_count }} <span i18n="shared.usage">usage</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingData>
|
||||
<div class="card-text">
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
justify-content: space-between;
|
||||
@media (min-width: 376px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.avg-ratio {
|
||||
display: none;
|
||||
@media (min-width: 480px) and (max-width: 767px), (min-width: 915px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
max-width: 300px;
|
||||
margin: 0;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Observable, map } from 'rxjs';
|
||||
})
|
||||
export class ReservesRatioStatsComponent implements OnInit {
|
||||
@Input() fullHistory$: Observable<any>;
|
||||
@Input() emergencyUtxosStats$: Observable<any>;
|
||||
unbackedMonths$: Observable<any>
|
||||
|
||||
constructor() { }
|
||||
|
||||
@@ -159,12 +159,28 @@
|
||||
<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 card-wrapper liquid">
|
||||
<div class="main-title" i18n="liquid.federation-holdings">Liquid Federation Holdings</div>
|
||||
<div class="card">
|
||||
<div class="card-body liquid">
|
||||
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col card-wrapper liquid">
|
||||
<div class="main-title" i18n="liquid.federation-expired-utxos">Federation Expired UTXOs</div>
|
||||
<div class="card">
|
||||
<div class="card-body liquid">
|
||||
<app-expired-utxos-stats [expiredUtxos$]="expiredUtxos$"></app-expired-utxos-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-title card-title-liquid">
|
||||
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
|
||||
</div>
|
||||
<h5 class="card-title" style="padding-top: 20px;" i18n="dashboard.lbtc-supply-against-btc-holdings">L-BTC Supply Against BTC Holdings</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>
|
||||
@@ -174,7 +190,7 @@
|
||||
<div class="col" style="margin-bottom: 1.47rem">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-body">
|
||||
<app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats>
|
||||
<app-reserves-ratio-stats [fullHistory$]="fullHistory$" [emergencyUtxosStats$]="emergencySpentUtxosStats$"></app-reserves-ratio-stats>
|
||||
<app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio>
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,13 +299,29 @@
|
||||
<div class="container-xl dashboard-container">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
|
||||
<div class="col card-wrapper liquid">
|
||||
<div class="main-title" i18n="liquid.federation-holdings">Liquid Federation Holdings</div>
|
||||
<div class="card">
|
||||
<div class="card-body liquid">
|
||||
<app-reserves-supply-stats></app-reserves-supply-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col card-wrapper liquid">
|
||||
<div class="main-title" i18n="liquid.federation-expired-utxos">Federation Expired UTXOs</div>
|
||||
<div class="card">
|
||||
<div class="card-body liquid">
|
||||
<app-expired-utxos-stats></app-expired-utxos-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card-liquid card">
|
||||
<div class="card-title card-title-liquid">
|
||||
<app-reserves-supply-stats></app-reserves-supply-stats>
|
||||
</div>
|
||||
<div class="card-body pl-0" style="padding-top: 10px;">
|
||||
<h5 class="card-title" style="padding-top: 20px;" i18n="dashboard.lbtc-supply-against-btc-holdings">L-BTC Supply Against BTC Holdings</h5>
|
||||
<div class="card-body pl-0" style="padding-top: 25px;">
|
||||
<app-lbtc-pegs-graph [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -321,6 +321,9 @@
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 22px 20px;
|
||||
&.liquid {
|
||||
height: 124.5px;
|
||||
}
|
||||
}
|
||||
.less-padding {
|
||||
padding: 20px 20px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
||||
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, FederationAddress, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
|
||||
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, 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';
|
||||
@@ -57,6 +57,8 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
federationAddresses$: Observable<FederationAddress[]>;
|
||||
federationAddressesNumber$: Observable<number>;
|
||||
federationUtxosNumber$: Observable<number>;
|
||||
expiredUtxos$: Observable<FederationUtxo[]>;
|
||||
emergencySpentUtxosStats$: Observable<any>;
|
||||
fullHistory$: Observable<any>;
|
||||
isLoad: boolean = true;
|
||||
filterSubscription: Subscription;
|
||||
@@ -64,7 +66,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
currencySubscription: Subscription;
|
||||
currency: string;
|
||||
incomingGraphHeight: number = 300;
|
||||
lbtcPegGraphHeight: number = 320;
|
||||
lbtcPegGraphHeight: number = 360;
|
||||
private lastPegBlockUpdate: number = 0;
|
||||
private lastPegAmount: string = '';
|
||||
private lastReservesBlockUpdate: number = 0;
|
||||
@@ -342,6 +344,20 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
map(count => count.utxo_count),
|
||||
share()
|
||||
);
|
||||
|
||||
this.expiredUtxos$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.expiredUtxos$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.emergencySpentUtxosStats$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.emergencySpentUtxosStats$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.liquidPegsMonth$ = interval(60 * 60 * 1000)
|
||||
.pipe(
|
||||
@@ -432,15 +448,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
if (window.innerWidth >= 992) {
|
||||
this.incomingGraphHeight = 300;
|
||||
this.goggleResolution = 82;
|
||||
this.lbtcPegGraphHeight = 320;
|
||||
this.lbtcPegGraphHeight = 360;
|
||||
} else if (window.innerWidth >= 768) {
|
||||
this.incomingGraphHeight = 215;
|
||||
this.goggleResolution = 80;
|
||||
this.lbtcPegGraphHeight = 230;
|
||||
this.lbtcPegGraphHeight = 270;
|
||||
} else {
|
||||
this.incomingGraphHeight = 180;
|
||||
this.goggleResolution = 86;
|
||||
this.lbtcPegGraphHeight = 220;
|
||||
this.lbtcPegGraphHeight = 270;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { IncomingTransactionsGraphComponent } from '../components/incoming-trans
|
||||
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 { ExpiredUtxosStatsComponent } from '../components/liquid-reserves-audit/expired-utxos-stats/expired-utxos-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';
|
||||
@@ -56,6 +57,7 @@ import { CommonModule } from '@angular/common';
|
||||
MempoolGraphComponent,
|
||||
LbtcPegsGraphComponent,
|
||||
ReservesSupplyStatsComponent,
|
||||
ExpiredUtxosStatsComponent,
|
||||
ReservesRatioStatsComponent,
|
||||
ReservesRatioComponent,
|
||||
RecentPegsStatsComponent,
|
||||
|
||||
@@ -103,6 +103,9 @@ export interface FederationUtxo {
|
||||
pegtxid: string;
|
||||
pegindex: number;
|
||||
pegblocktime: number;
|
||||
timelock: number;
|
||||
expiredAt: number;
|
||||
isDust?: boolean;
|
||||
}
|
||||
|
||||
export interface RecentPeg {
|
||||
|
||||
@@ -200,6 +200,14 @@ export class ApiService {
|
||||
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos');
|
||||
}
|
||||
|
||||
expiredUtxos$(): Observable<FederationUtxo[]> {
|
||||
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/expired');
|
||||
}
|
||||
|
||||
emergencySpentUtxos$(): Observable<FederationUtxo[]> {
|
||||
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/emergency-spent');
|
||||
}
|
||||
|
||||
recentPegsList$(count: number = 0): Observable<RecentPeg[]> {
|
||||
return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/list/' + count);
|
||||
}
|
||||
@@ -216,6 +224,10 @@ export class ApiService {
|
||||
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/total');
|
||||
}
|
||||
|
||||
emergencySpentUtxosStats$(): Observable<any> {
|
||||
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/emergency-spent/stats');
|
||||
}
|
||||
|
||||
listFeaturedAssets$(): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user