Migrate audit dashboard to main Liquid page
This commit is contained in:
		
							parent
							
								
									99aa6c9ed3
								
							
						
					
					
						commit
						69747a0028
					
				| @ -18,7 +18,7 @@ import { EChartsOption } from '../../graphs/echarts'; | |||||||
| }) | }) | ||||||
| export class LbtcPegsGraphComponent implements OnInit, OnChanges { | export class LbtcPegsGraphComponent implements OnInit, OnChanges { | ||||||
|   @Input() data: any; |   @Input() data: any; | ||||||
|   @Input() height: number | string = '320'; |   @Input() height: number | string = '270'; | ||||||
|   pegsChartOptions: EChartsOption; |   pegsChartOptions: EChartsOption; | ||||||
| 
 | 
 | ||||||
|   right: number | string = '10'; |   right: number | string = '10'; | ||||||
|  | |||||||
| @ -78,9 +78,6 @@ | |||||||
|       <li class="nav-item" routerLinkActive="active" id="btn-assets"> |       <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> |         <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> | ||||||
|       <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"> |       <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> |         <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> |       </li> | ||||||
|  | |||||||
| @ -23,21 +23,11 @@ li.nav-item { | |||||||
|   margin: auto 10px; |   margin: auto 10px; | ||||||
|   padding-left: 10px; |   padding-left: 10px; | ||||||
|   padding-right: 10px; |   padding-right: 10px; | ||||||
|   @media (max-width: 992px) { |  | ||||||
|     margin: auto 7px; |  | ||||||
|     padding-left: 8px; |  | ||||||
|     padding-right: 8px; |  | ||||||
|   } |  | ||||||
|   @media (max-width: 429px) { |   @media (max-width: 429px) { | ||||||
|     margin: auto 5px; |     margin: auto 5px; | ||||||
|     padding-left: 6px; |     padding-left: 6px; | ||||||
|     padding-right: 6px; |     padding-right: 6px; | ||||||
|   } |   } | ||||||
|   @media (max-width: 369px) { |  | ||||||
|     margin: auto 3px; |  | ||||||
|     padding-left: 4px; |  | ||||||
|     padding-right: 4px; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @media (min-width: 992px) { | @media (min-width: 992px) { | ||||||
|  | |||||||
| @ -20,6 +20,9 @@ export class FederationAddressesStatsComponent implements OnInit { | |||||||
|       this.federationUtxosNumber$ ?? of(undefined) |       this.federationUtxosNumber$ ?? of(undefined) | ||||||
|     ]).pipe( |     ]).pipe( | ||||||
|       map(([address_count, utxo_count]) => { |       map(([address_count, utxo_count]) => { | ||||||
|  |         if (address_count === undefined || utxo_count === undefined) { | ||||||
|  |           return undefined; | ||||||
|  |         } | ||||||
|         return { address_count, utxo_count} |         return { address_count, utxo_count} | ||||||
|       }) |       }) | ||||||
|     ) |     ) | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ import { SeoService } from '../../../services/seo.service'; | |||||||
| }) | }) | ||||||
| export class RecentPegsListComponent implements OnInit { | export class RecentPegsListComponent implements OnInit { | ||||||
|   @Input() widget: boolean = false; |   @Input() widget: boolean = false; | ||||||
|   @Input() recentPegsList$: Observable<RecentPeg[]> = of([]); |   @Input() recentPegsList$: Observable<RecentPeg[]>; | ||||||
| 
 | 
 | ||||||
|   env: Env; |   env: Env; | ||||||
|   isLoading = true; |   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: { |           title: { | ||||||
|             show: true, |             show: true, | ||||||
|             offsetCenter: [0, '-117.5%'], |             offsetCenter: [0, '-127%'], | ||||||
|             fontSize: 18, |             fontSize: 18, | ||||||
|             color: '#4a68b9', |             color: '#4a68b9', | ||||||
|             fontFamily: 'inherit', |             fontFamily: 'inherit', | ||||||
|  | |||||||
| @ -1,9 +1,7 @@ | |||||||
| <div *ngIf="(currentPeg$ | async) as currentPeg; else loadingData"> | <div class="fee-estimation-container"> | ||||||
|   <div *ngIf="(currentReserves$ | async) as currentReserves; else loadingData"> |  | ||||||
|     <div class="fee-estimation-container"> |  | ||||||
|   <div class="item"> |   <div class="item"> | ||||||
|     <h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5> |     <h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5> | ||||||
|         <div class="card-text"> |     <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> |       <div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div> | ||||||
|       <span class="fiat"> |       <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><ng-container i18n="shared.as-of-block">As of block</ng-container> <a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span> | ||||||
| @ -12,33 +10,20 @@ | |||||||
|   </div> |   </div> | ||||||
|   <div class="item"> |   <div class="item"> | ||||||
|     <h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5> |     <h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5> | ||||||
|         <div class="card-text"> |     <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> |       <div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div> | ||||||
|       <span class="fiat"> |       <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><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> |       </span> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| <ng-template #loadingData> | <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="card-text"> | ||||||
|     <div class="skeleton-loader"></div> |     <div class="skeleton-loader"></div> | ||||||
|     <div class="skeleton-loader"></div> |     <div class="skeleton-loader"></div> | ||||||
|   </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> |  | ||||||
| </ng-template> | </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"> |   <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="col card-wrapper"> | ||||||
|         <div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div> |         <div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div> | ||||||
|         <div class="card"> |         <div class="card"> | ||||||
| @ -17,7 +17,6 @@ | |||||||
|     <div class="col"> |     <div class="col"> | ||||||
|       <div class="card graph-card"> |       <div class="card graph-card"> | ||||||
|         <div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2"> |         <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]"> |           <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> |             <h5 class="card-title d-inline"><span i18n="dashboard.mempool-goggles">Mempool Goggles</span>: {{ goggleCycle[goggleIndex].name }}</h5> | ||||||
|             <span> </span> |             <span> </span> | ||||||
| @ -38,14 +37,6 @@ | |||||||
|               filterMode="or" |               filterMode="or" | ||||||
|             ></app-mempool-block-overview> |             ></app-mempool-block-overview> | ||||||
|           </div> |           </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> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| @ -53,7 +44,6 @@ | |||||||
|       <div class="card graph-card"> |       <div class="card graph-card"> | ||||||
|         <div class="card-body"> |         <div class="card-body"> | ||||||
|           <ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container> |           <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> |             <h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5> | ||||||
|             <div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats"> |             <div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats"> | ||||||
|               <app-incoming-transactions-graph |               <app-incoming-transactions-graph | ||||||
| @ -64,30 +54,11 @@ | |||||||
|                 [windowPreferenceOverride]="'2h'" |                 [windowPreferenceOverride]="'2h'" | ||||||
|                 ></app-incoming-transactions-graph> |                 ></app-incoming-transactions-graph> | ||||||
|             </div> |             </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> | ||||||
|     </div> |     </div> | ||||||
|     <div class="col" style="max-height: 410px"> |     <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"> |         <div class="card-body"> | ||||||
|           <a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]"> |           <a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]"> | ||||||
|             <h5 class="card-title d-inline" i18n="dashboard.recent-rbf-replacements">Recent Replacements</h5> |             <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> |                     <app-truncate [text]="transaction.txid" [lastChars]="5"></app-truncate> | ||||||
|                   </a> |                   </a> | ||||||
|                 </td> |                 </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-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> |                 <td class="table-cell-fees"><app-fee-rate [fee]="transaction.fee" [weight]="transaction.vsize * 4"></app-fee-rate></td> | ||||||
|               </tr> |               </tr> | ||||||
| @ -184,26 +155,52 @@ | |||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <ng-template #loadingAssetsTable> | <ng-container *ngIf="(network$ | async) === 'liquid'"> | ||||||
|   <table class="table table-borderless table-striped asset-table"> |   <div class="container-xl dashboard-container" *ngIf="(auditStatus$ | async)?.isAuditSynced; else auditInProgress"> | ||||||
|     <tbody> | 
 | ||||||
|       <tr *ngFor="let i of getArrayFromNumber(this.nbFeaturedAssets)"> |     <div class="row row-cols-1 row-cols-md-2"> | ||||||
|         <td class="asset-icon"> |    | ||||||
|           <div class="skeleton-loader skeleton-loader-transactions"></div> |       <div class="col"> | ||||||
|         </td> |         <div class="card-liquid card"> | ||||||
|         <td class="asset-title"> |           <div class="card-body"> | ||||||
|           <div class="skeleton-loader skeleton-loader-transactions"></div> |             <app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats> | ||||||
|         </td> |             <app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio> | ||||||
|         <td class="asset-title d-none d-md-table-cell"> |           </div> | ||||||
|           <div class="skeleton-loader skeleton-loader-transactions"></div> |         </div> | ||||||
|         </td> |       </div> | ||||||
|         <td class="asset-title"> |    | ||||||
|           <div class="skeleton-loader skeleton-loader-transactions"></div> |       <div class="col" style="margin-bottom: 1.47rem"> | ||||||
|         </td> |         <div class="card-liquid card">  | ||||||
|       </tr> |           <div class="card-title card-title-liquid"> | ||||||
|     </tbody> |             <app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats> | ||||||
|   </table> |           </div> | ||||||
| </ng-template> |           <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> | <ng-template #replacementsSkeleton> | ||||||
|   <tbody> |   <tbody> | ||||||
| @ -283,21 +280,56 @@ | |||||||
|   </div> |   </div> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #lbtcPegs let-mempoolInfoData> | <ng-template #loadingSkeletonLiquid> | ||||||
|   <div class="mempool-info-data lbtc-pegs-stats"> |   <div class="container-xl dashboard-container"> | ||||||
|     <div class="item"> | 
 | ||||||
|       <h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5> |     <div class="row row-cols-1 row-cols-md-2"> | ||||||
|       <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> |       <div class="col"> | ||||||
|       </ng-container> |         <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 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> |  | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </ng-template> | </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-top: 5px; | ||||||
|   margin-bottom: 6px; |   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 { 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 { 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 { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; | ||||||
| import { ApiService } from '../services/api.service'; | import { ApiService } from '../services/api.service'; | ||||||
| import { StateService } from '../services/state.service'; | import { StateService } from '../services/state.service'; | ||||||
| @ -32,8 +32,6 @@ interface MempoolStatsData { | |||||||
|   changeDetection: ChangeDetectionStrategy.OnPush |   changeDetection: ChangeDetectionStrategy.OnPush | ||||||
| }) | }) | ||||||
| export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | ||||||
|   featuredAssets$: Observable<any>; |  | ||||||
|   nbFeaturedAssets = 6; |  | ||||||
|   network$: Observable<string>; |   network$: Observable<string>; | ||||||
|   mempoolBlocksData$: Observable<MempoolBlocksData>; |   mempoolBlocksData$: Observable<MempoolBlocksData>; | ||||||
|   mempoolInfoData$: Observable<MempoolInfoData>; |   mempoolInfoData$: Observable<MempoolInfoData>; | ||||||
| @ -53,13 +51,18 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|   auditUpdated$: Observable<boolean>; |   auditUpdated$: Observable<boolean>; | ||||||
|   liquidReservesMonth$: Observable<any>; |   liquidReservesMonth$: Observable<any>; | ||||||
|   currentReserves$: Observable<CurrentPegs>; |   currentReserves$: Observable<CurrentPegs>; | ||||||
|  |   recentPegsList$: Observable<RecentPeg[]>; | ||||||
|  |   pegsVolume$: Observable<PegsVolume[]>; | ||||||
|  |   federationAddresses$: Observable<FederationAddress[]>; | ||||||
|  |   federationAddressesNumber$: Observable<number>; | ||||||
|  |   federationUtxosNumber$: Observable<number>; | ||||||
|   fullHistory$: Observable<any>; |   fullHistory$: Observable<any>; | ||||||
|   isLoad: boolean = true; |   isLoad: boolean = true; | ||||||
|   mempoolInfoSubscription: Subscription; |   mempoolInfoSubscription: Subscription; | ||||||
|   currencySubscription: Subscription; |   currencySubscription: Subscription; | ||||||
|   currency: string; |   currency: string; | ||||||
|   incomingGraphHeight: number = 300; |   incomingGraphHeight: number = 300; | ||||||
|   lbtcPegGraphHeight: number = 320; |   lbtcPegGraphHeight: number = 250; | ||||||
|   private lastPegBlockUpdate: number = 0; |   private lastPegBlockUpdate: number = 0; | ||||||
|   private lastPegAmount: string = ''; |   private lastPegAmount: string = ''; | ||||||
|   private lastReservesBlockUpdate: number = 0; |   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$ |     this.transactions$ = this.stateService.transactions$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         scan((acc, tx) => { |         scan((acc, tx) => { | ||||||
| @ -240,7 +223,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|         share(), |         share(), | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') { |     if (this.stateService.network === 'liquid') { | ||||||
|       this.auditStatus$ = this.stateService.blocks$.pipe( |       this.auditStatus$ = this.stateService.blocks$.pipe( | ||||||
|         takeUntil(this.destroy$), |         takeUntil(this.destroy$), | ||||||
|         throttleTime(40000), |         throttleTime(40000), | ||||||
| @ -250,22 +233,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|         shareReplay(1) |         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( |       this.currentPeg$ = this.auditStatus$.pipe( | ||||||
|         switchMap(_ => |         switchMap(_ => | ||||||
|           this.apiService.liquidPegs$().pipe( |           this.apiService.liquidPegs$().pipe( | ||||||
| @ -278,7 +245,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|         share() |         share() | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       ////////// BTC Reserves historical data //////////
 |  | ||||||
|       this.auditUpdated$ = combineLatest([ |       this.auditUpdated$ = combineLatest([ | ||||||
|         this.auditStatus$, |         this.auditStatus$, | ||||||
|         this.currentPeg$ |         this.currentPeg$ | ||||||
| @ -293,21 +259,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|           const amountCheck = currentPegAmount !== this.lastPegAmount; |           const amountCheck = currentPegAmount !== this.lastPegAmount; | ||||||
|           this.lastPegAmount = currentPegAmount; |           this.lastPegAmount = currentPegAmount; | ||||||
|           return of(blockAuditCheck || amountCheck); |           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() |         share() | ||||||
|       ); |       ); | ||||||
| @ -326,7 +277,74 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|         share() |         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( |         .pipe( | ||||||
|           map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => { |           map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => { | ||||||
|             liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000; |             liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000; | ||||||
| @ -380,18 +398,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|     if (window.innerWidth >= 992) { |     if (window.innerWidth >= 992) { | ||||||
|       this.incomingGraphHeight = 300; |       this.incomingGraphHeight = 300; | ||||||
|       this.goggleResolution = 82; |       this.goggleResolution = 82; | ||||||
|       this.lbtcPegGraphHeight = 320; |       this.lbtcPegGraphHeight = 270; | ||||||
|       this.nbFeaturedAssets = 6; |  | ||||||
|     } else if (window.innerWidth >= 768) { |     } else if (window.innerWidth >= 768) { | ||||||
|       this.incomingGraphHeight = 215; |       this.incomingGraphHeight = 215; | ||||||
|       this.goggleResolution = 80; |       this.goggleResolution = 80; | ||||||
|       this.lbtcPegGraphHeight = 230; |       this.lbtcPegGraphHeight = 190; | ||||||
|       this.nbFeaturedAssets = 4; |  | ||||||
|     } else { |     } else { | ||||||
|       this.incomingGraphHeight = 180; |       this.incomingGraphHeight = 180; | ||||||
|       this.goggleResolution = 86; |       this.goggleResolution = 86; | ||||||
|       this.lbtcPegGraphHeight = 220; |       this.lbtcPegGraphHeight = 200; | ||||||
|       this.nbFeaturedAssets = 4; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,6 +12,13 @@ import { FeeDistributionGraphComponent } from '../components/fee-distribution-gr | |||||||
| import { IncomingTransactionsGraphComponent } from '../components/incoming-transactions-graph/incoming-transactions-graph.component'; | import { IncomingTransactionsGraphComponent } from '../components/incoming-transactions-graph/incoming-transactions-graph.component'; | ||||||
| import { MempoolGraphComponent } from '../components/mempool-graph/mempool-graph.component'; | import { MempoolGraphComponent } from '../components/mempool-graph/mempool-graph.component'; | ||||||
| import { LbtcPegsGraphComponent } from '../components/lbtc-pegs-graph/lbtc-pegs-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 { GraphsComponent } from '../components/graphs/graphs.component'; | ||||||
| import { StatisticsComponent } from '../components/statistics/statistics.component'; | import { StatisticsComponent } from '../components/statistics/statistics.component'; | ||||||
| import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component'; | import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component'; | ||||||
| @ -48,6 +55,13 @@ import { CommonModule } from '@angular/common'; | |||||||
|     IncomingTransactionsGraphComponent, |     IncomingTransactionsGraphComponent, | ||||||
|     MempoolGraphComponent, |     MempoolGraphComponent, | ||||||
|     LbtcPegsGraphComponent, |     LbtcPegsGraphComponent, | ||||||
|  |     ReservesSupplyStatsComponent, | ||||||
|  |     ReservesRatioStatsComponent, | ||||||
|  |     ReservesRatioComponent, | ||||||
|  |     RecentPegsStatsComponent, | ||||||
|  |     RecentPegsListComponent, | ||||||
|  |     FederationAddressesStatsComponent, | ||||||
|  |     FederationAddressesListComponent, | ||||||
|     HashrateChartComponent, |     HashrateChartComponent, | ||||||
|     HashrateChartPoolsComponent, |     HashrateChartPoolsComponent, | ||||||
|     BlockHealthGraphComponent, |     BlockHealthGraphComponent, | ||||||
|  | |||||||
| @ -15,17 +15,10 @@ import { AssetsComponent } from '../components/assets/assets.component'; | |||||||
| import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component' | import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component' | ||||||
| import { AssetComponent } from '../components/asset/asset.component'; | import { AssetComponent } from '../components/asset/asset.component'; | ||||||
| import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.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 { 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 { 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 { 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 { 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 = [ | const routes: Routes = [ | ||||||
|   { |   { | ||||||
| @ -77,18 +70,6 @@ const routes: Routes = [ | |||||||
|         data: { preload: true, networkSpecific: true }, |         data: { preload: true, networkSpecific: true }, | ||||||
|         loadChildren: () => import('../components/block/block.module').then(m => m.BlockModule), |         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', |         path: 'audit/wallet', | ||||||
|         data: { networks: ['liquid'] }, |         data: { networks: ['liquid'] }, | ||||||
| @ -180,17 +161,8 @@ export class LiquidRoutingModule { } | |||||||
|   ], |   ], | ||||||
|   declarations: [ |   declarations: [ | ||||||
|     LiquidMasterPageComponent, |     LiquidMasterPageComponent, | ||||||
|     ReservesAuditDashboardComponent, |  | ||||||
|     ReservesSupplyStatsComponent, |  | ||||||
|     RecentPegsStatsComponent, |  | ||||||
|     RecentPegsListComponent, |  | ||||||
|     FederationWalletComponent, |     FederationWalletComponent, | ||||||
|     FederationUtxosListComponent, |     FederationUtxosListComponent, | ||||||
|     FederationAddressesStatsComponent, |  | ||||||
|     FederationAddressesListComponent, |  | ||||||
|     ReservesRatioComponent, |  | ||||||
|     ReservesRatioStatsComponent, |  | ||||||
|     ReservesRatioGraphComponent, |  | ||||||
|   ] |   ] | ||||||
| }) | }) | ||||||
| export class LiquidMasterPageModule { } | export class LiquidMasterPageModule { } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user