diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 922c0e639..b32a1689e 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -12,6 +12,7 @@ import poolsRepository from '../repositories/PoolsRepository'; import blocksRepository from '../repositories/BlocksRepository'; import loadingIndicators from './loading-indicators'; import BitcoinApi from './bitcoin/bitcoin-api'; +import { prepareBlock } from '../utils/blocks-utils'; class Blocks { private blocks: BlockExtended[] = []; @@ -336,7 +337,7 @@ class Blocks { public async $indexBlock(height: number): Promise { const dbBlock = await blocksRepository.$getBlockByHeight(height); if (dbBlock != null) { - return this.prepareBlock(dbBlock); + return prepareBlock(dbBlock); } const blockHash = await bitcoinApi.$getBlockHash(height); @@ -346,10 +347,11 @@ class Blocks { await blocksRepository.$saveBlockInDatabase(blockExtended); - return this.prepareBlock(blockExtended); + return prepareBlock(blockExtended); } - public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise { + public async $getBlocksExtras(fromHeight: number, limit: number = 15, + poolId: number | undefined= undefined): 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 @@ -378,7 +380,7 @@ class Blocks { if (!block && Common.indexingEnabled()) { block = await this.$indexBlock(currentHeight); } else if (!block) { - block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash)); + block = prepareBlock(await bitcoinApi.$getBlock(nextHash)); } returnBlocks.push(block); nextHash = block.previousblockhash; @@ -393,34 +395,6 @@ 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, - extras: { - coinbaseRaw: block.coinbase_raw ?? block.extras.coinbaseRaw, - 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, - } : undefined), - } - }; - } - public getLastDifficultyAdjustmentTime(): number { return this.lastDifficultyAdjustmentTime; } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 4ac0a9d9d..40c705bdb 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -2,6 +2,7 @@ import { BlockExtended, PoolTag } from '../mempool.interfaces'; import { DB } from '../database'; import logger from '../logger'; import { Common } from '../api/common'; +import { prepareBlock } from '../utils/blocks-utils'; class BlocksRepository { /** @@ -153,7 +154,6 @@ class BlocksRepository { query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - // logger.debug(query); const connection = await DB.getConnection(); try { const [rows] = await connection.query(query, params); @@ -169,10 +169,10 @@ class BlocksRepository { /** * Get blocks count between two dates - * @param poolId + * @param poolId * @param from - The oldest timestamp * @param to - The newest timestamp - * @returns + * @returns */ public async $blockCountBetweenTimestamp(poolId: number | null, from: number, to: number): Promise { const params: any[] = []; @@ -193,7 +193,6 @@ class BlocksRepository { } query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`; - // logger.debug(query); const connection = await DB.getConnection(); try { const [rows] = await connection.query(query, params); @@ -216,7 +215,6 @@ class BlocksRepository { ORDER BY height LIMIT 1;`; - // logger.debug(query); const connection = await DB.getConnection(); try { const [rows]: any[] = await connection.query(query); @@ -237,14 +235,15 @@ class BlocksRepository { /** * Get blocks mined by a specific mining pool */ - public async $getBlocksByPool(poolId: number, startHeight: number | null = null): Promise { + public async $getBlocksByPool(poolId: number, startHeight: number | undefined = undefined): Promise { const params: any[] = []; - let query = `SELECT height, hash as id, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, reward + let query = ` SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, + previous_block_hash as previousblockhash FROM blocks WHERE pool_id = ?`; params.push(poolId); - if (startHeight) { + if (startHeight !== undefined) { query += ` AND height < ?`; params.push(startHeight); } @@ -252,17 +251,17 @@ class BlocksRepository { query += ` ORDER BY height DESC LIMIT 10`; - // logger.debug(query); const connection = await DB.getConnection(); try { const [rows] = await connection.query(query, params); connection.release(); - for (const block of rows) { - delete block['blockTimestamp']; + const blocks: BlockExtended[] = []; + for (let block of rows) { + blocks.push(prepareBlock(block)); } - return rows; + return blocks; } catch (e) { connection.release(); logger.err('$getBlocksByPool() error' + (e instanceof Error ? e.message : e)); @@ -314,7 +313,7 @@ class BlocksRepository { let query = ` SELECT * - FROM + FROM ( SELECT UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty, height, @@ -322,7 +321,7 @@ class BlocksRepository { IF(@prevStatus := YT.difficulty, @rn := 1, @rn := 1) ) AS rn FROM blocks YT - CROSS JOIN + CROSS JOIN ( SELECT @prevStatus := -1, @rn := 1 ) AS var diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 7f3a98296..870c62ab8 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -553,7 +553,7 @@ class Routes { try { const poolBlocks = await BlocksRepository.$getBlocksByPool( parseInt(req.params.poolId, 10), - parseInt(req.params.height, 10) ?? null, + req.params.height === undefined ? undefined : parseInt(req.params.height, 10), ); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); diff --git a/backend/src/utils/blocks-utils.ts b/backend/src/utils/blocks-utils.ts new file mode 100644 index 000000000..107099ba3 --- /dev/null +++ b/backend/src/utils/blocks-utils.ts @@ -0,0 +1,29 @@ +import { BlockExtended } from "../mempool.interfaces"; + +export function 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, + extras: { + coinbaseRaw: block.coinbase_raw ?? block.extras.coinbaseRaw, + 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, + } : undefined), + } + }; +} diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index af71351ad..bef441a85 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,20 +1,20 @@ -
+
-

+
- {{ poolStats.pool.name }} -

+ onError="this.src = './resources/mining-pools/default.svg'" class="mr-3 mb-3"> +

{{ poolStats.pool.name }}

+
-
+
- - - - + +
Tags +
{{ poolStats.pool.regexes }}
@@ -22,21 +22,21 @@
Addresses + ~~
-
+
@@ -59,31 +59,48 @@
-
- - - - - - + + + + + + + + - - - - - + + + + - - - + + + + + + + + + + + + + + + + +
HeightTimestampMinedReward - TransactionsSizeHeightTimestampMined + Coinbase Tag + RewardFeesTxsSize
{{ block.height }}‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} +
+ {{ block.height + }} + + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + - + + + {{ block.extras.coinbaseRaw | hex2ascii }} + {{ block.tx_count | number }} + + + + + + {{ block.tx_count | number }} +
@@ -92,16 +109,46 @@
+ + + + + + + + + + + + + + + +
-

- -
-

+
+ +

+
@@ -109,22 +156,22 @@ - + + + + + - + - - - -
AddressesTags +
+
Addresses
~
Coinbase Tags -
-
@@ -132,14 +179,14 @@ - - + - - + diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 291177f73..2e8a4f154 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -1,5 +1,6 @@ -.progress { - background-color: #2d3348; +.container-xl { + max-width: 1400px; + padding-bottom: 100px; } @media (min-width: 768px) { @@ -39,28 +40,100 @@ div.scrollable { max-height: 75px; } -.skeleton-loader { - width: 100%; - max-width: 90px; -} - -.table { - margin: 0px auto; - max-width: 900px; -} - .box { - padding-bottom: 0px; + padding-bottom: 5px; } .label { - max-width: 50px; width: 30%; } .data { - text-align: center; - @media (max-width: 767.98px) { + text-align: left; + padding-left: 25%; + @media (max-width: 991px) { + padding-left: 12px; + } + @media (max-width: 450px) { + text-align: right; + } +} + +.progress { + background-color: #2d3348; +} + +.coinbase { + width: 20%; + @media (max-width: 875px) { + display: none; + } +} + +.height { + width: 10%; +} + +.timestamp { + @media (max-width: 875px) { + padding-left: 50px; + } + @media (max-width: 650px) { + display: none; + } +} + +.mined { + width: 13%; + @media (max-width: 1100px) { + display: none; + } +} + +.txs { + padding-right: 40px; + @media (max-width: 1100px) { + padding-right: 10px; + } + @media (max-width: 875px) { + padding-right: 50px; + } + @media (max-width: 567px) { + padding-right: 10px; + } +} + +.fees { + width: 0%; +} + +.size { + width: 12%; + @media (max-width: 1000px) { + width: 15%; + } + @media (max-width: 875px) { + width: 20%; + } + @media (max-width: 650px) { + width: 20%; + } + @media (max-width: 450px) { + display: none; + } +} + +.scriptmessage { + overflow: hidden; + display: inline-block; + text-overflow: ellipsis; + vertical-align: middle; + width: auto; + text-align: left; +} + +.right-mobile { + @media (max-width: 450px) { text-align: right; } } \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index da05f0403..529298f65 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } import { ActivatedRoute } from '@angular/router'; import { EChartsOption, graphic } from 'echarts'; import { BehaviorSubject, Observable } from 'rxjs'; -import { distinctUntilChanged, map, switchMap, tap, toArray } from 'rxjs/operators'; +import { map, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -31,6 +31,7 @@ export class PoolComponent implements OnInit { poolStats$: Observable; blocks$: Observable; isLoading = true; + skeletonLines: number[] = [...Array(5).keys()]; chartOptions: EChartsOption = {}; chartInitOptions = { @@ -39,8 +40,7 @@ export class PoolComponent implements OnInit { height: 'auto', }; - fromHeight: number = -1; - fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromHeight); + loadMoreSubject: BehaviorSubject = new BehaviorSubject(undefined); blocks: BlockExtended[] = []; poolId: number = undefined; @@ -66,12 +66,9 @@ export class PoolComponent implements OnInit { this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); return poolId; }), - ) + ); }), switchMap(() => { - if (this.blocks.length === 0) { - this.fromHeightSubject.next(undefined); - } return this.apiService.getPoolStats$(this.poolId); }), map((poolStats) => { @@ -88,17 +85,16 @@ export class PoolComponent implements OnInit { }) ); - this.blocks$ = this.fromHeightSubject + this.blocks$ = this.loadMoreSubject .pipe( - distinctUntilChanged(), - switchMap((fromHeight) => { - return this.apiService.getPoolBlocks$(this.poolId, fromHeight); + switchMap((flag) => { + return this.apiService.getPoolBlocks$(this.poolId, this.blocks[this.blocks.length - 1]?.height); }), tap((newBlocks) => { this.blocks = this.blocks.concat(newBlocks); }), map(() => this.blocks) - ) + ); } prepareChartOptions(data) { @@ -133,18 +129,18 @@ export class PoolComponent implements OnInit { align: 'left', }, borderColor: '#000', - formatter: function (data) { + formatter: function(ticks: any[]) { let hashratePowerOfTen: any = selectPowerOfTen(1); - let hashrate = data[0].data[1]; + let hashrate = ticks[0].data[1]; if (this.isMobile()) { - hashratePowerOfTen = selectPowerOfTen(data[0].data[1]); - hashrate = Math.round(data[0].data[1] / hashratePowerOfTen.divider); + hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1]); + hashrate = Math.round(ticks[0].data[1] / hashratePowerOfTen.divider); } return ` - ${data[0].axisValueLabel}
- ${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
+ ${ticks[0].axisValueLabel}
+ ${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
`; }.bind(this) }, @@ -154,11 +150,10 @@ export class PoolComponent implements OnInit { }, yAxis: [ { - min: function (value) { + min: (value) => { return value.min * 0.9; }, type: 'value', - name: 'Hashrate', axisLabel: { color: 'rgb(110, 112, 121)', formatter: (val) => { @@ -192,7 +187,7 @@ export class PoolComponent implements OnInit { } loadMore() { - this.fromHeightSubject.next(this.blocks[this.blocks.length - 1]?.height); + this.loadMoreSubject.next(undefined); } trackByBlock(index: number, block: BlockExtended) {
Mined Blocks + Mined Blocks
Empty Blocks + Empty Blocks