diff --git a/frontend/src/app/components/amount/amount.component.html b/frontend/src/app/components/amount/amount.component.html index 29f61ca41..34f9be8ae 100644 --- a/frontend/src/app/components/amount/amount.component.html +++ b/frontend/src/app/components/amount/amount.component.html @@ -19,7 +19,7 @@ ‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }} - L- + L- tL- t sBTC diff --git a/frontend/src/app/components/amount/amount.component.ts b/frontend/src/app/components/amount/amount.component.ts index 479ae4791..9c779265c 100644 --- a/frontend/src/app/components/amount/amount.component.ts +++ b/frontend/src/app/components/amount/amount.component.ts @@ -23,6 +23,7 @@ export class AmountComponent implements OnInit, OnDestroy { @Input() noFiat = false; @Input() addPlus = false; @Input() blockConversion: Price; + @Input() forceBtc: boolean = false; constructor( private stateService: StateService, diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html index 49f05c3a2..30fc153c7 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html @@ -78,6 +78,9 @@ + diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html new file mode 100644 index 000000000..0c5981be6 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html @@ -0,0 +1,84 @@ +
+

Liquid Federation UTXOs

+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OutputAddressAmountDate
+ + + + + + + +
+ + + + + + + + + + + ‎{{ utxo.blocktime * 1000 | date:'yyyy-MM-dd HH:mm' }} +
()
+
+ + + + + + + +
+ + + + + +
+
+
+
+ +
diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.scss b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.scss new file mode 100644 index 000000000..4208fd167 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.scss @@ -0,0 +1,91 @@ +.spinner-border { + height: 25px; + width: 25px; + margin-top: 13px; +} + +.container-xl { + max-width: 1400px; +} +.container-xl.widget { + padding-left: 0px; + padding-bottom: 0px; +} + +tr, td, th { + border: 0px; + padding-top: 0.65rem !important; + padding-bottom: 0.6rem !important; + padding-right: 2rem !important; + .widget { + padding-right: 1rem !important; + } +} + +.clear-link { + color: white; +} + +.disabled { + pointer-events: none; + opacity: 0.5; +} + +.progress { + background-color: #2d3348; +} + +.txid { + width: 35%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 160px; +} +.txid.widget { + width: 50%; + +} + +.address { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 160px; + @media (max-width: 527px) { + display: none; + } +} + +.amount { + width: 12%; +} +.amount.widget { + width: 25%; +} + +.timestamp { + width: 18%; + @media (max-width: 800px) { + display: none; + } + @media (max-width: 1000px) { + .relative-time { + display: none; + } + } +} +.timestamp.widget { + width: 25%; + @media (min-width: 768px) AND (max-width: 1050px) { + display: none; + } + @media (max-width: 767px) { + display: block; + } + + @media (max-width: 500px) { + display: none; + } +} + diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.ts b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.ts new file mode 100644 index 000000000..19122b658 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core'; +import { BehaviorSubject, combineLatest, Observable, timer, of, concat } from 'rxjs'; +import { delay, delayWhen, filter, map, retryWhen, scan, share, skip, switchMap, tap } from 'rxjs/operators'; +import { ApiService } from '../../../services/api.service'; +import { Env, StateService } from '../../../services/state.service'; +import { AuditStatus, FederationUtxo } from '../../../interfaces/node-api.interface'; +import { WebsocketService } from '../../../services/websocket.service'; + +@Component({ + selector: 'app-federation-utxos-list', + templateUrl: './federation-utxos-list.component.html', + styleUrls: ['./federation-utxos-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FederationUtxosListComponent implements OnInit { + @Input() widget: boolean = false; + @Input() federationUtxos$: Observable; + + env: Env; + isLoading = true; + page = 1; + pageSize = 15; + maxSize = window.innerWidth <= 767.98 ? 3 : 5; + skeletonLines: number[] = []; + auditStatus$: Observable; + + constructor( + private apiService: ApiService, + public stateService: StateService, + private websocketService: WebsocketService, + private cd: ChangeDetectorRef, + ) { + } + + ngOnInit(): void { + this.isLoading = !this.widget; + this.env = this.stateService.env; + this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()]; + if (!this.widget) { + this.websocketService.want(['blocks']); + this.auditStatus$ = concat( + this.apiService.federationAuditSynced$(), + this.stateService.blocks$.pipe( + skip(1), + delay(2000), + switchMap(() => this.apiService.federationAuditSynced$()), + share() + ) + ); + + this.federationUtxos$ = this.auditStatus$.pipe( + filter(auditStatus => auditStatus.isAuditSynced === true), + switchMap(_ => this.apiService.federationUtxos$()), + tap(_ => this.isLoading = false), + share() + ); + } + + + } + + pageChange(page: number): void { + this.page = page; + } + +} diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.html b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.html new file mode 100644 index 000000000..84078b201 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.html @@ -0,0 +1,46 @@ +
+
+
+
+ +
Outputs
+
+
+
{{ federationUtxos.length }} UTXOs
+ + + +
+
+
+
Addresses
+
+
{{ federationAddresses.length }} addresses
+ + + +
+
+
+
+
+ + +
+
+
Outputs
+
+
+
+
+
+
+
Addresses
+
+
+
+
+
+
+
+ diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.scss b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.scss new file mode 100644 index 000000000..50ab123b6 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.scss @@ -0,0 +1,80 @@ +.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; + 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: 10px; + text-decoration: none; + color: inherit; +} diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.ts b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.ts new file mode 100644 index 000000000..5f35b582e --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component.ts @@ -0,0 +1,42 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { concat, interval, Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { ApiService } from '../../../services/api.service'; +import { StateService } from '../../../services/state.service'; +import { FederationAddress, FederationUtxo } from '../../../interfaces/node-api.interface'; + +@Component({ + selector: 'app-federation-utxos-stats', + templateUrl: './federation-utxos-stats.component.html', + styleUrls: ['./federation-utxos-stats.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FederationUtxosStatsComponent implements OnInit { + @Input() federationUtxos$: Observable; + @Input() federationAddresses$: Observable; + + federationUtxosOneMonthAgo$: Observable; + federationAddressesOneMonthAgo$: Observable; + + constructor(private apiService: ApiService, private stateService: StateService) { } + + ngOnInit(): void { + + // Calls this.apiService.federationUtxosOneMonthAgo$ at load and then every day + this.federationUtxosOneMonthAgo$ = concat( + this.apiService.federationUtxosOneMonthAgo$(), + interval(24 * 60 * 60 * 1000).pipe( + switchMap(() => this.apiService.federationUtxosOneMonthAgo$()) + ) + ); + + // Calls this.apiService.federationAddressesOneMonthAgo$ at load and then every day + this.federationAddressesOneMonthAgo$ = concat( + this.apiService.federationAddressesOneMonthAgo$(), + interval(24 * 60 * 60 * 1000).pipe( + switchMap(() => this.apiService.federationAddressesOneMonthAgo$()) + ) + ); + } + +} diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.html b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.html new file mode 100644 index 000000000..ddac8dd1c --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.html @@ -0,0 +1,58 @@ +
+ +
+ +
+
+ BTC Reserves +
+
+
+ + +
+
+
+ +
+
+ Liquid Federation UTXOs +
+
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ + + +
+
+ + + +
+ Audit in progress: block {{ auditStatus.lastBlockAudit }} / {{ auditStatus.bitcoinHeaders }} +
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.scss b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.scss new file mode 100644 index 000000000..46f9ffa4d --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.scss @@ -0,0 +1,160 @@ +.dashboard-container { + text-align: center; + margin-top: 0.5rem; + .col { + margin-bottom: 1.5rem; + } +} + +.card { + background-color: #1d1f31; +} + +.graph-card { + height: 100%; + @media (min-width: 992px) { + height: 385px; + } +} + +.card-title { + font-size: 1rem; + color: #4a68b9; +} +.card-title > a { + color: #4a68b9; +} + +.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%) +} + +.main-title { + position: relative; + color: #ffffff91; + margin-top: -13px; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} + +.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%; + } +} + +.card { + height: 385px; +} +.list-card { + height: 410px; + @media (max-width: 767px) { + height: auto; + } +} + +.mempool-block-wrapper { + max-height: 380px; + max-width: 380px; + margin: auto; +} \ No newline at end of file diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.ts b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.ts new file mode 100644 index 000000000..e71ff8e80 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component.ts @@ -0,0 +1,86 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { SeoService } from '../../../services/seo.service'; +import { WebsocketService } from '../../../services/websocket.service'; +import { StateService } from '../../../services/state.service'; +import { Observable, concat, delay, filter, share, skip, switchMap, tap } from 'rxjs'; +import { ApiService } from '../../../services/api.service'; +import { AuditStatus, CurrentPegs, FederationAddress, FederationUtxo } 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; + currentPeg$: Observable; + currentReserves$: Observable; + federationUtxos$: Observable; + federationAddresses$: Observable; + private lastPegBlockUpdate: number = 0; + private lastReservesBlockUpdate: number = 0; + + + 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$ = concat( + this.apiService.federationAuditSynced$().pipe(share()), + this.stateService.blocks$.pipe( + skip(1), + delay(2000), + switchMap(() => this.apiService.federationAuditSynced$()), + share() + ) + ); + + this.currentReserves$ = this.auditStatus$.pipe( + filter(auditStatus => auditStatus.isAuditSynced === true), + switchMap(_ => + this.apiService.liquidReserves$().pipe( + filter((currentReserves) => currentReserves.lastBlockUpdate > this.lastReservesBlockUpdate), + tap((currentReserves) => { + this.lastReservesBlockUpdate = currentReserves.lastBlockUpdate; + }) + ) + ), + share() + ); + + 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.federationUtxos$ = this.auditStatus$.pipe( + filter(auditStatus => auditStatus.isAuditSynced === true), + switchMap(_ => this.apiService.federationUtxos$()), + share() + ); + + this.federationAddresses$ = this.auditStatus$.pipe( + filter(auditStatus => auditStatus.isAuditSynced === true), + switchMap(_ => this.apiService.federationAddresses$()), + share() + ); + } + +} diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.html b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.html new file mode 100644 index 000000000..561656760 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.scss b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.scss new file mode 100644 index 000000000..9881148fc --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.scss @@ -0,0 +1,6 @@ +.loadingGraphs { + position: absolute; + top: 50%; + left: calc(50% - 16px); + z-index: 100; +} \ No newline at end of file diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.ts b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.ts new file mode 100644 index 000000000..0a6363257 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component.ts @@ -0,0 +1,127 @@ +import { Component, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core'; +import { EChartsOption } from '../../../graphs/echarts'; +import { CurrentPegs } from '../../../interfaces/node-api.interface'; + + +@Component({ + selector: 'app-reserves-ratio', + templateUrl: './reserves-ratio.component.html', + styleUrls: ['./reserves-ratio.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReservesRatioComponent implements OnInit, OnChanges { + @Input() currentPeg: CurrentPegs; + @Input() currentReserves: CurrentPegs; + pegsChartOptions: EChartsOption; + + height: number | string = '200'; + right: number | string = '10'; + top: number | string = '20'; + left: number | string = '50'; + template: ('widget' | 'advanced') = 'widget'; + isLoading = true; + + pegsChartOption: EChartsOption = {}; + pegsChartInitOption = { + renderer: 'svg' + }; + + constructor() { } + + ngOnInit() { + this.isLoading = true; + } + + ngOnChanges() { + if (!this.currentPeg || !this.currentReserves || this.currentPeg.amount === '0') { + return; + } + this.pegsChartOptions = this.createChartOptions(this.currentPeg, this.currentReserves); + } + + rendered() { + if (!this.currentPeg || !this.currentReserves) { + return; + } + this.isLoading = false; + } + + createChartOptions(currentPeg: CurrentPegs, currentReserves: CurrentPegs): EChartsOption { + return { + series: [ + { + type: 'gauge', + startAngle: 180, + endAngle: 0, + center: ['50%', '70%'], + radius: '100%', + min: 0.999, + max: 1.001, + splitNumber: 2, + axisLine: { + lineStyle: { + width: 6, + color: [ + [0.49, '#D81B60'], + [1, '#7CB342'] + ] + } + }, + axisLabel: { + color: 'inherit', + fontFamily: 'inherit', + }, + pointer: { + icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z', + length: '50%', + width: 16, + offsetCenter: [0, '-27%'], + itemStyle: { + color: 'auto' + } + }, + axisTick: { + length: 12, + lineStyle: { + color: 'auto', + width: 2 + } + }, + splitLine: { + length: 20, + lineStyle: { + color: 'auto', + width: 5 + } + }, + title: { + show: true, + offsetCenter: [0, '-117.5%'], + fontSize: 18, + color: '#4a68b9', + fontFamily: 'inherit', + fontWeight: 500, + }, + detail: { + fontSize: 25, + offsetCenter: [0, '-0%'], + valueAnimation: true, + fontFamily: 'inherit', + fontWeight: 500, + formatter: function (value) { + return (value).toFixed(5); + }, + color: 'inherit' + }, + data: [ + { + value: parseFloat(currentReserves.amount) / parseFloat(currentPeg.amount), + name: 'Peg-O-Meter' + } + ] + } + ] + }; + } +} + diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.html b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.html new file mode 100644 index 000000000..d0a624a4f --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.html @@ -0,0 +1,44 @@ +
+
+
+
+
L-BTC in circulation
+
+
{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} L-BTC
+ + As of block {{ currentPeg.lastBlockUpdate }} + +
+
+
+
BTC Reserves
+
+
{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} BTC
+ + As of block {{ currentReserves.lastBlockUpdate }} + +
+
+
+
+
+ + +
+
+
L-BTC in circulation
+
+
+
+
+
+
+
BTC Reserves
+
+
+
+
+
+
+
+ diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.scss b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.scss new file mode 100644 index 000000000..3a8a83f26 --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.scss @@ -0,0 +1,73 @@ +.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; + 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; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.ts b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.ts new file mode 100644 index 000000000..61f2deb8c --- /dev/null +++ b/frontend/src/app/components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Env, StateService } from '../../../services/state.service'; +import { CurrentPegs } from '../../../interfaces/node-api.interface'; + +@Component({ + selector: 'app-reserves-supply-stats', + templateUrl: './reserves-supply-stats.component.html', + styleUrls: ['./reserves-supply-stats.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReservesSupplyStatsComponent implements OnInit { + @Input() currentReserves$: Observable; + @Input() currentPeg$: Observable; + + env: Env; + + constructor(private stateService: StateService) { } + + ngOnInit(): void { + this.env = this.stateService.env; + } + +} diff --git a/frontend/src/app/graphs/echarts.ts b/frontend/src/app/graphs/echarts.ts index 342867168..74fec1e71 100644 --- a/frontend/src/app/graphs/echarts.ts +++ b/frontend/src/app/graphs/echarts.ts @@ -1,6 +1,6 @@ // Import tree-shakeable echarts import * as echarts from 'echarts/core'; -import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart } from 'echarts/charts'; +import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart } from 'echarts/charts'; import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent } from 'echarts/components'; import { SVGRenderer, CanvasRenderer } from 'echarts/renderers'; // Typescript interfaces @@ -12,6 +12,6 @@ echarts.use([ TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent, - LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart + LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart ]); export { echarts, EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption }; \ No newline at end of file diff --git a/frontend/src/app/liquid/liquid-master-page.module.ts b/frontend/src/app/liquid/liquid-master-page.module.ts index bb6e4cff8..90c50e0df 100644 --- a/frontend/src/app/liquid/liquid-master-page.module.ts +++ b/frontend/src/app/liquid/liquid-master-page.module.ts @@ -2,8 +2,10 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Routes, RouterModule } from '@angular/router'; import { SharedModule } from '../shared/shared.module'; +import { NgxEchartsModule } from 'ngx-echarts'; import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component'; + import { StartComponent } from '../components/start/start.component'; import { AddressComponent } from '../components/address/address.component'; import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; @@ -13,6 +15,11 @@ import { AssetsComponent } from '../components/assets/assets.component'; import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component' import { AssetComponent } from '../components/asset/asset.component'; import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component'; +import { ReservesAuditDashboardComponent } from '../components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component'; +import { ReservesSupplyStatsComponent } from '../components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component'; +import { FederationUtxosStatsComponent } from '../components/liquid-reserves-audit/federation-utxos-stats/federation-utxos-stats.component'; +import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component'; +import { ReservesRatioComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component'; const routes: Routes = [ { @@ -64,6 +71,22 @@ const routes: Routes = [ data: { preload: true, networkSpecific: true }, loadChildren: () => import('../components/block/block.module').then(m => m.BlockModule), }, + { + path: 'audit', + data: { networks: ['liquid'] }, + component: StartComponent, + children: [ + { + path: '', + data: { networks: ['liquid'] }, + component: ReservesAuditDashboardComponent, + } + ] + }, + { + path: 'audit/utxos', + component: FederationUtxosListComponent, + }, { path: 'assets', data: { networks: ['liquid'] }, @@ -123,9 +146,17 @@ export class LiquidRoutingModule { } CommonModule, LiquidRoutingModule, SharedModule, + NgxEchartsModule.forRoot({ + echarts: () => import('../graphs/echarts').then(m => m.echarts), + }) ], declarations: [ LiquidMasterPageComponent, + ReservesAuditDashboardComponent, + ReservesSupplyStatsComponent, + FederationUtxosStatsComponent, + FederationUtxosListComponent, + ReservesRatioComponent, ] }) export class LiquidMasterPageModule { } \ No newline at end of file