diff --git a/frontend/src/app/components/acceleration-stats/acceleration-stats.component.html b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.html new file mode 100644 index 000000000..e8ded44fc --- /dev/null +++ b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.html @@ -0,0 +1,51 @@ +
+
+
+
Transactions
+
+
{{ stats.count }}
+
accelerated
+
+
+
+
Fee delta
+
+
{{ stats.totalFeeDelta | number }} sat
+
paid out of band
+
+
+
+
Success rate
+
+
{{ stats.successRate.toFixed(2) }} %
+
mined in the next block
+
+
+
+
+ + +
+
+
Miners Reward
+
+
+
+
+
+
+
Avg Block Fees
+
+
+
+
+
+
+
Avg Tx Fee
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/acceleration-stats/acceleration-stats.component.scss b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.scss new file mode 100644 index 000000000..fcc5564a8 --- /dev/null +++ b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.scss @@ -0,0 +1,88 @@ +.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; + display: inline-flex; + } + .green-color { + display: block; + } +} + +.stats-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; + } + &:first-child{ + display: none; + @media (min-width: 485px) { + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + &: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/acceleration-stats/acceleration-stats.component.ts b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.ts new file mode 100644 index 000000000..fa69b1a9d --- /dev/null +++ b/frontend/src/app/components/acceleration-stats/acceleration-stats.component.ts @@ -0,0 +1,43 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { ApiService } from '../../services/api.service'; +import { StateService } from '../../services/state.service'; + +@Component({ + selector: 'app-acceleration-stats', + templateUrl: './acceleration-stats.component.html', + styleUrls: ['./acceleration-stats.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AccelerationStatsComponent implements OnInit { + public accelerationStats$: Observable; + private lastBlockHeight: number; + + constructor(private apiService: ApiService, private stateService: StateService) { } + + ngOnInit(): void { + this.accelerationStats$ = this.apiService.getAccelerations$().pipe( + switchMap(accelerations => { + let totalFeeDelta = 0; + let totalMined = 0; + let totalCanceled = 0; + for (const acceleration of accelerations) { + if (acceleration.mined) { + totalMined++; + totalFeeDelta += acceleration.feeDelta; + } else if (acceleration.canceled) { + totalCanceled++; + } + } + return of({ + count: totalMined, + totalFeeDelta, + successRate: (totalMined + totalCanceled > 0) ? ((totalMined / (totalMined + totalCanceled)) * 100) : 0.0, + }); + }) + ); + } + + +} diff --git a/frontend/src/app/components/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/accelerations-list/accelerations-list.component.html new file mode 100644 index 000000000..367b6f33d --- /dev/null +++ b/frontend/src/app/components/accelerations-list/accelerations-list.component.html @@ -0,0 +1,62 @@ + + +
+

Accelerations

+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
TXIDFeeFee deltaStatus
+ + + + + {{ acceleration.fee | number }} sat + + {{ acceleration.feeDelta | number }} sat + + Mined + Canceled +
+ + + + + + + +
+ + +
+
+
+
+ +
diff --git a/frontend/src/app/components/accelerations-list/accelerations-list.component.scss b/frontend/src/app/components/accelerations-list/accelerations-list.component.scss new file mode 100644 index 000000000..9fd978341 --- /dev/null +++ b/frontend/src/app/components/accelerations-list/accelerations-list.component.scss @@ -0,0 +1,107 @@ +.spinner-border { + height: 25px; + width: 25px; + margin-top: 13px; +} + +.container-xl { + max-width: 1400px; +} +.container-xl.widget { + padding-left: 0px; + padding-bottom: 0px; +} +.container-xl.legacy { + max-width: 1140px; +} + +.container { + max-width: 100%; +} + +tr, td, th { + border: 0px; + padding-top: 0.65rem !important; + padding-bottom: 0.7rem !important; + + .difference { + margin-left: 0.5em; + + &.positive { + color: rgb(66, 183, 71); + } + &.negative { + color: rgb(183, 66, 66); + } + } +} + +.clear-link { + color: white; +} + +.disabled { + pointer-events: none; + opacity: 0.5; +} + +.progress { + background-color: #2d3348; +} + +.txid { + width: 30%; + @media (max-width: 1100px) { + padding-right: 10px; + } + @media (max-width: 875px) { + display: none; + } + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 30%; +} + +.fee { + width: 25%; +} + +.fee-delta { + width: 25%; +} + +.status { + width: 20% +} + +/* Tooltip text */ +.tooltip-custom { + position: relative; +} + +.tooltip-custom .tooltiptext { + visibility: hidden; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + position: absolute; + z-index: 1; + top: -40px; + left: 0; +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltip-custom:hover .tooltiptext { + visibility: visible; +} + +.scriptmessage { + overflow: hidden; + display: inline-block; + text-overflow: ellipsis; + vertical-align: middle; + max-width: 50vw; + text-align: left; +} diff --git a/frontend/src/app/components/accelerations-list/accelerations-list.component.ts b/frontend/src/app/components/accelerations-list/accelerations-list.component.ts new file mode 100644 index 000000000..10c14a2ad --- /dev/null +++ b/frontend/src/app/components/accelerations-list/accelerations-list.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core'; +import { Observable, catchError, of } from 'rxjs'; +import { Acceleration, BlockExtended } from '../../interfaces/node-api.interface'; +import { ApiService } from '../../services/api.service'; +import { StateService } from '../../services/state.service'; +import { WebsocketService } from '../../services/websocket.service'; + +@Component({ + selector: 'app-accelerations-list', + templateUrl: './accelerations-list.component.html', + styleUrls: ['./accelerations-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AccelerationsListComponent implements OnInit { + @Input() widget: boolean = false; + + accelerations$: Observable = undefined; + + isLoading = true; + paginationMaxSize: number; + page = 1; + lastPage = 1; + maxSize = window.innerWidth <= 767.98 ? 3 : 5; + skeletonLines: number[] = []; + + constructor( + private apiService: ApiService, + private websocketService: WebsocketService, + public stateService: StateService, + private cd: ChangeDetectorRef, + ) { + } + + ngOnInit(): void { + if (!this.widget) { + this.websocketService.want(['blocks']); + } + + this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; + this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; + + this.accelerations$ = this.apiService.getAccelerations$().pipe( + catchError((err) => { + this.isLoading = false; + return of([]); + }) + ); + } + + trackByBlock(index: number, block: BlockExtended): number { + return block.height; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.html b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.html new file mode 100644 index 000000000..13954fb95 --- /dev/null +++ b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.html @@ -0,0 +1,76 @@ + + +
+ +
+ +
+
+ Acceleration stats  + (144 blocks) +
+
+
+
+ +
+
+
+
+ + +
+
Difficulty Adjustment
+ +
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + + + + + + +
+
diff --git a/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.scss b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.scss new file mode 100644 index 000000000..4f01f7cad --- /dev/null +++ b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.scss @@ -0,0 +1,102 @@ +.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: 10px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} + +.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; +} diff --git a/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.ts b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.ts new file mode 100644 index 000000000..edae81aa1 --- /dev/null +++ b/frontend/src/app/components/accelerator-dashboard/accelerator-dashboard.component.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { SeoService } from '../../services/seo.service'; +import { WebsocketService } from '../../services/websocket.service'; + +@Component({ + selector: 'app-accelerator-dashboard', + templateUrl: './accelerator-dashboard.component.html', + styleUrls: ['./accelerator-dashboard.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AcceleratorDashboardComponent implements OnInit { + constructor( + private seoService: SeoService, + private websocketService: WebsocketService, + ) { + this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`); + } + + ngOnInit(): void { + this.websocketService.want(['blocks', 'mempool-blocks', 'stats']); + } +} diff --git a/frontend/src/app/graphs/graphs.module.ts b/frontend/src/app/graphs/graphs.module.ts index a2160977c..ce5775175 100644 --- a/frontend/src/app/graphs/graphs.module.ts +++ b/frontend/src/app/graphs/graphs.module.ts @@ -19,6 +19,7 @@ import { PoolComponent } from '../components/pool/pool.component'; import { TelevisionComponent } from '../components/television/television.component'; import { DashboardComponent } from '../dashboard/dashboard.component'; import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component'; +import { AcceleratorDashboardComponent } from '../components/accelerator-dashboard/accelerator-dashboard.component'; import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component'; import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component'; import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component'; @@ -30,6 +31,7 @@ import { CommonModule } from '@angular/common'; MempoolBlockComponent, MiningDashboardComponent, + AcceleratorDashboardComponent, PoolComponent, PoolRankingComponent, TelevisionComponent, diff --git a/frontend/src/app/graphs/graphs.routing.module.ts b/frontend/src/app/graphs/graphs.routing.module.ts index 346bcf7f1..e3d9897b2 100644 --- a/frontend/src/app/graphs/graphs.routing.module.ts +++ b/frontend/src/app/graphs/graphs.routing.module.ts @@ -10,6 +10,7 @@ import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-ch import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component'; import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component'; import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component'; +import { AcceleratorDashboardComponent } from '../components/accelerator-dashboard/accelerator-dashboard.component'; import { PoolRankingComponent } from '../components/pool-ranking/pool-ranking.component'; import { PoolComponent } from '../components/pool/pool.component'; import { StartComponent } from '../components/start/start.component'; @@ -37,6 +38,17 @@ const routes: Routes = [ }, ] }, + { + path: 'acceleration', + data: { networks: ['bitcoin'] }, + component: StartComponent, + children: [ + { + path: '', + component: AcceleratorDashboardComponent, + } + ] + }, { path: 'mempool-block/:id', data: { networks: ['bitcoin', 'liquid'] }, diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 82327c561..bd897ba99 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -85,6 +85,8 @@ import { GlobalFooterComponent } from './components/global-footer/global-footer. import { AcceleratePreviewComponent } from '../components/accelerate-preview/accelerate-preview.component'; import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/accelerate-fee-graph.component'; import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component'; +import { AccelerationsListComponent } from '../components/accelerations-list/accelerations-list.component'; +import { AccelerationStatsComponent } from '../components/acceleration-stats/acceleration-stats.component'; import { BlockViewComponent } from '../components/block-view/block-view.component'; import { EightBlocksComponent } from '../components/eight-blocks/eight-blocks.component'; @@ -190,6 +192,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir OnlyVsizeDirective, OnlyWeightDirective, MempoolErrorComponent, + AccelerationsListComponent, + AccelerationStatsComponent, ], imports: [ CommonModule, @@ -300,6 +304,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AcceleratePreviewComponent, AccelerateFeeGraphComponent, MempoolErrorComponent, + AccelerationsListComponent, + AccelerationStatsComponent, MempoolBlockOverviewComponent, ClockchainComponent,