From 0e0331d8ab02d5fa6f83f51ff663ed5a67069324 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 10 Mar 2022 18:35:37 +0100 Subject: [PATCH 1/6] Create working template for the new blocks page --- backend/src/api/blocks.ts | 4 +- backend/src/routes.ts | 2 +- frontend/src/app/app-routing.module.ts | 15 ++- frontend/src/app/app.module.ts | 2 + .../blocks-list/blocks-list.component.html | 100 ++++++++++++++++++ .../blocks-list/blocks-list.component.scss | 74 +++++++++++++ .../blocks-list/blocks-list.component.ts | 48 +++++++++ frontend/src/app/services/api.service.ts | 7 ++ 8 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 frontend/src/app/components/blocks-list/blocks-list.component.html create mode 100644 frontend/src/app/components/blocks-list/blocks-list.component.scss create mode 100644 frontend/src/app/components/blocks-list/blocks-list.component.ts diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 40687060f..aed2d0004 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -339,7 +339,7 @@ class Blocks { return blockExtended; } - public async $getBlocksExtras(fromHeight: number): Promise { + public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise { try { loadingIndicators.setProgress('blocks', 0); @@ -360,7 +360,7 @@ class Blocks { } let nextHash = startFromHash; - for (let i = 0; i < 10 && currentHeight >= 0; i++) { + for (let i = 0; i < limit && currentHeight >= 0; i++) { let block = this.getBlocks().find((b) => b.height === currentHeight); if (!block && Common.indexingEnabled()) { block = this.prepareBlock(await this.$indexBlock(currentHeight)); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 710cd8378..c6b3656e7 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -658,7 +658,7 @@ class Routes { public async getBlocksExtras(req: Request, res: Response) { try { - res.json(await blocks.$getBlocksExtras(parseInt(req.params.height, 10))) + res.json(await blocks.$getBlocksExtras(parseInt(req.params.height, 10), 15)); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 19285fc8f..1ef7e5fe0 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -31,6 +31,7 @@ import { MiningDashboardComponent } from './components/mining-dashboard/mining-d import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component'; import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component'; import { MiningStartComponent } from './components/mining-start/mining-start.component'; +import { BlocksList } from './components/blocks-list/blocks-list.component'; let routes: Routes = [ { @@ -75,6 +76,10 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ + { + path: 'blocks', + component: BlocksList, + }, { path: 'hashrate', component: HashrateChartComponent, @@ -190,6 +195,10 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ + { + path: 'blocks', + component: BlocksList, + }, { path: 'hashrate', component: HashrateChartComponent, @@ -299,6 +308,10 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ + { + path: 'blocks', + component: BlocksList, + }, { path: 'hashrate', component: HashrateChartComponent, @@ -630,7 +643,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { initialNavigation: 'enabled', scrollPositionRestoration: 'enabled', anchorScrolling: 'enabled' -})], + })], exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 0dfc853cc..3affdc7ba 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -76,6 +76,7 @@ import { MiningStartComponent } from './components/mining-start/mining-start.com import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe'; import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components'; +import { BlocksList } from './components/blocks-list/blocks-list.component'; @NgModule({ declarations: [ @@ -133,6 +134,7 @@ import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments- MiningStartComponent, AmountShortenerPipe, DifficultyAdjustmentsTable, + BlocksList, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html new file mode 100644 index 000000000..480df9f3f --- /dev/null +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -0,0 +1,100 @@ +
+

Blocks

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HeightTimestampMinedPoolRewardFeesTxsSize
+ {{ block.height + }} + + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + + + + {{ + block.extras.pool.name }} + + + + + + {{ block.tx_count | number }} + +
+
+
+
+
+ + + + + + + + + + + + + + + +
+ + + +
\ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss new file mode 100644 index 000000000..3d7ffe631 --- /dev/null +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -0,0 +1,74 @@ +.container-xl { + max-width: 1400px; +} + +.container { + max-width: 100%; +} + +.row { + padding-top: 15px; + padding-bottom: 15px; +} + +.pool-name { + display: inline-block; + vertical-align: text-top; + padding-left: 10px; +} + +.height { + width: 12%; + @media (max-width: 1100px) { + width: 10%; + } +} + +.timestamp { + @media (max-width: 900px) { + display: none; + } +} + +.mined { + @media (max-width: 576px) { + display: none; + } +} + +.txs { + padding-right: 40px; + @media (max-width: 1100px) { + padding-right: 10px; + } + @media (max-width: 800px) { + display: none; + } +} + +.fees { + @media (max-width: 650px) { + display: none; + } +} + +.pool { + width: 12%; +} + +.reward { + @media (max-width: 576px) { + width: 7%; + padding-right: 30px; + } +} + +.size { + width: 12%; + @media (max-width: 1000px) { + width: 15%; + } + @media (max-width: 650px) { + width: 20%; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts new file mode 100644 index 000000000..56563d7e6 --- /dev/null +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, repeat, tap } from 'rxjs/operators'; +import { BlockExtended } from 'src/app/interfaces/node-api.interface'; +import { ApiService } from 'src/app/services/api.service'; +import { StateService } from 'src/app/services/state.service'; + +@Component({ + selector: 'app-blocks-list', + templateUrl: './blocks-list.component.html', + styleUrls: ['./blocks-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BlocksList implements OnInit { + blocks$: Observable = undefined + isLoading = true; + oldestBlockHeight = undefined; + + constructor( + private apiService: ApiService, + public stateService: StateService + ) { + + } + + ngOnInit(): void { + this.blocks$ = this.apiService.getBlocks$(this.oldestBlockHeight) + .pipe( + tap(blocks => { + this.isLoading = false; + }), + map(blocks => { + for (const block of blocks) { + // @ts-ignore + block.extras.pool.logo = `./resources/mining-pools/` + + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + this.oldestBlockHeight = block.height; + } + return blocks; + }), + repeat(2), + ); + } + + trackByBlock(index: number, block: BlockExtended) { + return block.height; + } +} \ No newline at end of file diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 858da3273..142f26807 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -151,6 +151,13 @@ export class ApiService { ); } + getBlocks$(from: number): Observable { + return this.httpClient.get( + this.apiBasePath + this.apiBasePath + `/api/v1/blocks-extras` + + (from !== undefined ? `/${from}` : ``) + ); + } + getHistoricalDifficulty$(interval: string | undefined): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` + From d8e986996f0a01ca84c32cbd21bb52ca9dbaa1cb Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Mar 2022 14:54:34 +0100 Subject: [PATCH 2/6] Add pagination on /mining/blocks --- backend/src/api/blocks.ts | 61 +++++++----- backend/src/repositories/BlocksRepository.ts | 5 +- .../blocks-list/blocks-list.component.html | 92 +++++++++---------- .../blocks-list/blocks-list.component.scss | 17 +++- .../blocks-list/blocks-list.component.ts | 59 ++++++++---- 5 files changed, 135 insertions(+), 99 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index aed2d0004..7a9589348 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -108,14 +108,23 @@ class Blocks { blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); - const stats = await bitcoinClient.getBlockStats(block.id); const coinbaseRaw: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); blockExtended.extras.coinbaseRaw = coinbaseRaw.hex; - blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles - blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(); - blockExtended.extras.totalFees = stats.totalfee; - blockExtended.extras.avgFee = stats.avgfee; - blockExtended.extras.avgFeeRate = stats.avgfeerate; + + if (block.height === 0) { + blockExtended.extras.medianFee = 0; // 50th percentiles + blockExtended.extras.feeRange = [0, 0, 0, 0, 0, 0, 0]; + blockExtended.extras.totalFees = 0; + blockExtended.extras.avgFee = 0; + blockExtended.extras.avgFeeRate = 0; + } else { + const stats = await bitcoinClient.getBlockStats(block.id); + blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles + blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(); + blockExtended.extras.totalFees = stats.totalfee; + blockExtended.extras.avgFee = stats.avgfee; + blockExtended.extras.avgFeeRate = stats.avgfeerate; + } if (Common.indexingEnabled()) { let pool: PoolTag; @@ -336,10 +345,13 @@ class Blocks { await blocksRepository.$saveBlockInDatabase(blockExtended); - return blockExtended; + return this.prepareBlock(blockExtended); } public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise { + // Note - This API is breaking if indexing is not available. For now it is okay because we only + // use it for the mining pages, and mining pages should not be available if indexing is turned off. + // I'll need to fix it before we refactor the block(s) related pages try { loadingIndicators.setProgress('blocks', 0); @@ -363,7 +375,7 @@ class Blocks { for (let i = 0; i < limit && currentHeight >= 0; i++) { let block = this.getBlocks().find((b) => b.height === currentHeight); if (!block && Common.indexingEnabled()) { - block = this.prepareBlock(await this.$indexBlock(currentHeight)); + block = await this.$indexBlock(currentHeight); } else if (!block) { block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash)); } @@ -383,24 +395,25 @@ class Blocks { private prepareBlock(block: any): BlockExtended { return { id: block.id ?? block.hash, // hash for indexed block - timestamp: block?.timestamp ?? block?.blockTimestamp, // blockTimestamp for indexed block - height: block?.height, - version: block?.version, - bits: block?.bits, - nonce: block?.nonce, - difficulty: block?.difficulty, - merkle_root: block?.merkle_root, - tx_count: block?.tx_count, - size: block?.size, - weight: block?.weight, - previousblockhash: block?.previousblockhash, + timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block + height: block.height, + version: block.version, + bits: block.bits, + nonce: block.nonce, + difficulty: block.difficulty, + merkle_root: block.merkle_root, + tx_count: block.tx_count, + size: block.size, + weight: block.weight, + previousblockhash: block.previousblockhash, extras: { - medianFee: block?.medianFee, - feeRange: block?.feeRange ?? [], // TODO - reward: block?.reward, + medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee, + feeRange: block.feeRange ?? block.fee_range ?? block?.extras?.feeSpan, + reward: block.reward ?? block?.extras?.reward, + totalFees: block.totalFees ?? block?.fees ?? block?.extras.totalFees, pool: block?.extras?.pool ?? (block?.pool_id ? { - id: block?.pool_id, - name: block?.pool_name, + id: block.pool_id, + name: block.pool_name, } : undefined), } }; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 0cab3c0db..041086f73 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -277,7 +277,10 @@ class BlocksRepository { const connection = await DB.pool.getConnection(); try { const [rows]: any[] = await connection.query(` - SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.addresses as pool_addresses, pools.regexes as pool_regexes + SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, + pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, + pools.addresses as pool_addresses, pools.regexes as pool_regexes, + previous_block_hash as previousblockhash FROM blocks JOIN pools ON blocks.pool_id = pools.id WHERE height = ${height}; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 480df9f3f..2a4818c4f 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -7,32 +7,32 @@ + - - + - - + + @@ -50,51 +50,41 @@ - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - + +
HeightPool Timestamp MinedPool Reward Fees Txs Size
{{ block.height }} - ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} - - - {{ block.extras.pool.name }} + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + +
+ + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - -
- + + \ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 3d7ffe631..71d96323c 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -1,5 +1,6 @@ .container-xl { max-width: 1400px; + padding-bottom: 0; } .container { @@ -11,6 +12,15 @@ padding-bottom: 15px; } +.disabled { + pointer-events: none; + opacity: 0.5; +} + +.progress { + background-color: #2d3348; +} + .pool-name { display: inline-block; vertical-align: text-top; @@ -18,7 +28,7 @@ } .height { - width: 12%; + width: 10%; @media (max-width: 1100px) { width: 10%; } @@ -31,6 +41,7 @@ } .mined { + width: 13%; @media (max-width: 576px) { display: none; } @@ -53,7 +64,7 @@ } .pool { - width: 12%; + width: 17%; } .reward { @@ -71,4 +82,4 @@ @media (max-width: 650px) { width: 20%; } -} \ No newline at end of file +} diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 56563d7e6..3143b4f61 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map, repeat, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, timer } from 'rxjs'; +import { delayWhen, map, retryWhen, switchMap, tap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -13,33 +13,52 @@ import { StateService } from 'src/app/services/state.service'; }) export class BlocksList implements OnInit { blocks$: Observable = undefined + isLoading = true; - oldestBlockHeight = undefined; + fromBlockHeight = undefined; + paginationMaxSize: number; + page = 1; + blocksCount: number; + fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromBlockHeight); constructor( private apiService: ApiService, - public stateService: StateService + public stateService: StateService, ) { } ngOnInit(): void { - this.blocks$ = this.apiService.getBlocks$(this.oldestBlockHeight) - .pipe( - tap(blocks => { - this.isLoading = false; - }), - map(blocks => { - for (const block of blocks) { - // @ts-ignore - block.extras.pool.logo = `./resources/mining-pools/` + - block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; - this.oldestBlockHeight = block.height; - } - return blocks; - }), - repeat(2), - ); + this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; + + this.blocks$ = this.fromHeightSubject.pipe( + switchMap(() => { + this.isLoading = true; + return this.apiService.getBlocks$(this.fromBlockHeight) + .pipe( + tap(blocks => { + if (this.blocksCount === undefined) { + this.blocksCount = blocks[0].height; + } + this.isLoading = false; + }), + map(blocks => { + for (const block of blocks) { + // @ts-ignore: Need to add an extra field for the template + block.extras.pool.logo = `./resources/mining-pools/` + + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } + return blocks; + }), + retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) + ) + }) + ); + } + + pageChange(page: number) { + this.fromBlockHeight = this.blocksCount - (page - 1) * 15; + this.fromHeightSubject.next(this.fromBlockHeight); } trackByBlock(index: number, block: BlockExtended) { From 123af53de246bcf3a18f382cff63fb57838180aa Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Mar 2022 17:09:13 +0100 Subject: [PATCH 3/6] Added latest block on mining dashboard --- .../blocks-list/blocks-list.component.html | 66 ++++++++++--------- .../blocks-list/blocks-list.component.scss | 49 ++++++++++++-- .../blocks-list/blocks-list.component.ts | 12 +++- .../mining-dashboard.component.html | 18 ++++- 4 files changed, 102 insertions(+), 43 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 2a4818c4f..d1327653b 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,48 +1,50 @@ -
-

Blocks

-
+
+

Blocks

- - - - - - - - + + + + + + + + - - - - - - - - - - + - - - - - - - @@ -83,8 +86,9 @@
HeightPoolTimestampMinedRewardFeesTxsSizeHeight + PoolTimestampMined + RewardFeesTxsSize
+ {{ block.height }} + - {{ + {{ block.extras.pool.name }} + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + + + {{ block.tx_count | number }} +
@@ -53,29 +55,30 @@
+
+ + + + + + + +
- +
\ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 71d96323c..ba64fd6ed 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -2,14 +2,21 @@ max-width: 1400px; padding-bottom: 0; } +.container-xl.widget { + padding-left: 0px; +} .container { max-width: 100%; } -.row { - padding-top: 15px; - padding-bottom: 15px; +td { + padding-top: 0.7rem !important; + padding-bottom: 0.7rem !important; +} + +.clear-link { + color: white; } .disabled { @@ -21,6 +28,16 @@ background-color: #2d3348; } +.pool { + width: 17%; +} +.pool.widget { + width: 40%; + @media (max-width: 576px) { + padding-left: 30px; + width: 60%; + } +} .pool-name { display: inline-block; vertical-align: text-top; @@ -33,6 +50,12 @@ width: 10%; } } +.height.widget { + width: 20%; + @media (max-width: 576px) { + width: 10%; + } +} .timestamp { @media (max-width: 900px) { @@ -52,7 +75,13 @@ @media (max-width: 1100px) { padding-right: 10px; } - @media (max-width: 800px) { + @media (max-width: 875px) { + display: none; + } +} +.txs.widget { + padding-right: 0; + @media (max-width: 650px) { display: none; } } @@ -62,9 +91,8 @@ display: none; } } - -.pool { - width: 17%; +.fees.widget { + width: 20%; } .reward { @@ -73,6 +101,13 @@ padding-right: 30px; } } +.reward.widget { + width: 20%; + @media (max-width: 576px) { + width: 30%; + padding-right: 0; + } +} .size { width: 12%; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 3143b4f61..3606cc122 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; import { BehaviorSubject, Observable, timer } from 'rxjs'; import { delayWhen, map, retryWhen, switchMap, tap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; @@ -12,6 +12,8 @@ import { StateService } from 'src/app/services/state.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class BlocksList implements OnInit { + @Input() widget: boolean = false; + blocks$: Observable = undefined isLoading = true; @@ -20,17 +22,18 @@ export class BlocksList implements OnInit { page = 1; blocksCount: number; fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromBlockHeight); + skeletonLines: number[] = []; constructor( private apiService: ApiService, public stateService: StateService, ) { - } ngOnInit(): void { + this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()]; this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; - + this.blocks$ = this.fromHeightSubject.pipe( switchMap(() => { this.isLoading = true; @@ -48,6 +51,9 @@ export class BlocksList implements OnInit { block.extras.pool.logo = `./resources/mining-pools/` + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; } + if (this.widget) { + return blocks.slice(0, 5); + } return blocks; }), retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 68209fed0..1c8fb2c9d 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -95,7 +95,7 @@
-
+ + + +
+
+
+
+ Latest blocks +
+ + +
+
@@ -115,7 +129,7 @@ Adjustments -
From d6a0d84d7114baaa881f02f4efeeb47746f46eef Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 12 Mar 2022 15:55:19 +0100 Subject: [PATCH 4/6] Update mining/blocks in real time --- .../blocks-list/blocks-list.component.html | 2 +- .../blocks-list/blocks-list.component.ts | 91 ++++++++++++------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index d1327653b..f14261190 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -19,7 +19,7 @@ - {{ block.height + {{ block.height }} diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 3606cc122..72727b734 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; -import { BehaviorSubject, Observable, timer } from 'rxjs'; -import { delayWhen, map, retryWhen, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, timer } from 'rxjs'; +import { delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; +import { WebsocketService } from 'src/app/services/websocket.service'; @Component({ selector: 'app-blocks-list', @@ -14,57 +15,81 @@ import { StateService } from 'src/app/services/state.service'; export class BlocksList implements OnInit { @Input() widget: boolean = false; - blocks$: Observable = undefined + blocks$: Observable = undefined; isLoading = true; fromBlockHeight = undefined; paginationMaxSize: number; page = 1; + lastPage = 1; blocksCount: number; fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromBlockHeight); skeletonLines: number[] = []; constructor( private apiService: ApiService, + private websocketService: WebsocketService, public stateService: StateService, ) { } ngOnInit(): void { - this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()]; + this.websocketService.want(['blocks']); + + this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()]; this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; - - this.blocks$ = this.fromHeightSubject.pipe( - switchMap(() => { - this.isLoading = true; - return this.apiService.getBlocks$(this.fromBlockHeight) - .pipe( - tap(blocks => { - if (this.blocksCount === undefined) { - this.blocksCount = blocks[0].height; - } - this.isLoading = false; - }), - map(blocks => { - for (const block of blocks) { - // @ts-ignore: Need to add an extra field for the template - block.extras.pool.logo = `./resources/mining-pools/` + - block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; - } - if (this.widget) { - return blocks.slice(0, 5); - } - return blocks; - }), - retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) - ) - }) - ); + + this.blocks$ = combineLatest([ + this.fromHeightSubject.pipe( + switchMap((fromBlockHeight) => { + this.isLoading = true; + return this.apiService.getBlocks$(this.page === 1 ? undefined : fromBlockHeight) + .pipe( + tap(blocks => { + if (this.blocksCount === undefined) { + this.blocksCount = blocks[0].height; + } + this.isLoading = false; + }), + map(blocks => { + for (const block of blocks) { + // @ts-ignore: Need to add an extra field for the template + block.extras.pool.logo = `./resources/mining-pools/` + + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } + if (this.widget) { + return blocks.slice(0, 5); + } + return blocks; + }), + retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) + ) + }) + ), + this.stateService.blocks$ + .pipe( + skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1), + ), + ]) + .pipe( + scan((acc, blocks) => { + if (this.page > 1 || acc.length === 0 || (this.page === 1 && this.lastPage !== 1)) { + this.lastPage = this.page; + return blocks[0]; + } + this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height); + // @ts-ignore: Need to add an extra field for the template + blocks[1][0].extras.pool.logo = `./resources/mining-pools/` + + blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + acc.unshift(blocks[1][0]); + acc = acc.slice(0, this.widget ? 5 : 15); + return acc; + }, []) + ); } pageChange(page: number) { - this.fromBlockHeight = this.blocksCount - (page - 1) * 15; - this.fromHeightSubject.next(this.fromBlockHeight); + this.fromHeightSubject.next(this.blocksCount - (page - 1) * 15); } trackByBlock(index: number, block: BlockExtended) { From 11de94cf905247e9e95fc6e2cda160daf8817751 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 12 Mar 2022 16:44:21 +0100 Subject: [PATCH 5/6] Pool icon clickable - blocks list pagination margin --- .../components/blocks-list/blocks-list.component.html | 10 +++++----- .../components/blocks-list/blocks-list.component.scss | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index f14261190..7cc7b2e17 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -23,11 +23,11 @@ }} - + - {{ - block.extras.pool.name }} + {{ block.extras.pool.name }} + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} @@ -86,7 +86,7 @@ - diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index ba64fd6ed..b9a19f927 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -118,3 +118,10 @@ td { width: 20%; } } + +.pagination-container { + margin-bottom: 40px; + @media (max-width: 992px) { + margin-bottom: 80px; + } +} \ No newline at end of file From a893e8734765537f139b9f15c04f696ff1cd8118 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 12 Mar 2022 17:33:07 +0100 Subject: [PATCH 6/6] Add more padding to the blocks list page --- .../blocks-list/blocks-list.component.html | 142 +++++++++--------- .../blocks-list/blocks-list.component.scss | 13 +- 2 files changed, 77 insertions(+), 78 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 7cc7b2e17..f50a5fff2 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -3,92 +3,94 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + +
Height - PoolTimestampMined - RewardFeesTxsSize
- {{ block.height - }} - - - - {{ block.extras.pool.name }} - - - ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} - - - - - - - - {{ block.tx_count | number }} - -
-
-
-
-
- +
+ + + + + + + + + + + + + + - -
Height + PoolTimestampMined + RewardFeesTxsSize
+ {{ block.height + }} - - + + + {{ block.extras.pool.name }} + - + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} - + - + - + - + {{ block.tx_count | number }} - +
+
+
+
- - - + +
+ + + + + + + + + + + + + + + + +
+ + +
+ \ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index b9a19f927..9414348c1 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -1,9 +1,10 @@ .container-xl { max-width: 1400px; - padding-bottom: 0; + padding-bottom: 100px; } .container-xl.widget { padding-left: 0px; + padding-bottom: 0px; } .container { @@ -117,11 +118,7 @@ td { @media (max-width: 650px) { width: 20%; } -} - -.pagination-container { - margin-bottom: 40px; - @media (max-width: 992px) { - margin-bottom: 80px; + @media (max-width: 450px) { + display: none; } -} \ No newline at end of file +}