Merge pull request #1381 from mempool/nymkappa/feature/pool-page-blocks
Updated blocks list in pool details page
This commit is contained in:
		
						commit
						5c4aa6efac
					
				| @ -12,6 +12,7 @@ import poolsRepository from '../repositories/PoolsRepository'; | |||||||
| import blocksRepository from '../repositories/BlocksRepository'; | import blocksRepository from '../repositories/BlocksRepository'; | ||||||
| import loadingIndicators from './loading-indicators'; | import loadingIndicators from './loading-indicators'; | ||||||
| import BitcoinApi from './bitcoin/bitcoin-api'; | import BitcoinApi from './bitcoin/bitcoin-api'; | ||||||
|  | import { prepareBlock } from '../utils/blocks-utils'; | ||||||
| 
 | 
 | ||||||
| class Blocks { | class Blocks { | ||||||
|   private blocks: BlockExtended[] = []; |   private blocks: BlockExtended[] = []; | ||||||
| @ -336,7 +337,7 @@ class Blocks { | |||||||
|   public async $indexBlock(height: number): Promise<BlockExtended> { |   public async $indexBlock(height: number): Promise<BlockExtended> { | ||||||
|     const dbBlock = await blocksRepository.$getBlockByHeight(height); |     const dbBlock = await blocksRepository.$getBlockByHeight(height); | ||||||
|     if (dbBlock != null) { |     if (dbBlock != null) { | ||||||
|       return this.prepareBlock(dbBlock); |       return prepareBlock(dbBlock); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const blockHash = await bitcoinApi.$getBlockHash(height); |     const blockHash = await bitcoinApi.$getBlockHash(height); | ||||||
| @ -346,10 +347,11 @@ class Blocks { | |||||||
| 
 | 
 | ||||||
|     await blocksRepository.$saveBlockInDatabase(blockExtended); |     await blocksRepository.$saveBlockInDatabase(blockExtended); | ||||||
| 
 | 
 | ||||||
|     return this.prepareBlock(blockExtended); |     return prepareBlock(blockExtended); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise<BlockExtended[]> { |   public async $getBlocksExtras(fromHeight: number, limit: number = 15, | ||||||
|  |     poolId: number | undefined= undefined): Promise<BlockExtended[]> { | ||||||
|     // Note - This API is breaking if indexing is not available. For now it is okay because we only
 |     // 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.
 |     // 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
 |     // I'll need to fix it before we refactor the block(s) related pages
 | ||||||
| @ -378,7 +380,7 @@ class Blocks { | |||||||
|         if (!block && Common.indexingEnabled()) { |         if (!block && Common.indexingEnabled()) { | ||||||
|           block = await this.$indexBlock(currentHeight); |           block = await this.$indexBlock(currentHeight); | ||||||
|         } else if (!block) { |         } else if (!block) { | ||||||
|           block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash)); |           block = prepareBlock(await bitcoinApi.$getBlock(nextHash)); | ||||||
|         } |         } | ||||||
|         returnBlocks.push(block); |         returnBlocks.push(block); | ||||||
|         nextHash = block.previousblockhash; |         nextHash = block.previousblockhash; | ||||||
| @ -393,34 +395,6 @@ class Blocks { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private prepareBlock(block: any): BlockExtended { |  | ||||||
|     return <BlockExtended>{ |  | ||||||
|       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 { |   public getLastDifficultyAdjustmentTime(): number { | ||||||
|     return this.lastDifficultyAdjustmentTime; |     return this.lastDifficultyAdjustmentTime; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import { BlockExtended, PoolTag } from '../mempool.interfaces'; | |||||||
| import { DB } from '../database'; | import { DB } from '../database'; | ||||||
| import logger from '../logger'; | import logger from '../logger'; | ||||||
| import { Common } from '../api/common'; | import { Common } from '../api/common'; | ||||||
|  | import { prepareBlock } from '../utils/blocks-utils'; | ||||||
| 
 | 
 | ||||||
| class BlocksRepository { | class BlocksRepository { | ||||||
|   /** |   /** | ||||||
| @ -153,7 +154,6 @@ class BlocksRepository { | |||||||
|       query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; |       query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // logger.debug(query);
 |  | ||||||
|     const connection = await DB.getConnection(); |     const connection = await DB.getConnection(); | ||||||
|     try { |     try { | ||||||
|       const [rows] = await connection.query(query, params); |       const [rows] = await connection.query(query, params); | ||||||
| @ -193,7 +193,6 @@ class BlocksRepository { | |||||||
|     } |     } | ||||||
|     query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`; |     query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`; | ||||||
| 
 | 
 | ||||||
|     // logger.debug(query);
 |  | ||||||
|     const connection = await DB.getConnection(); |     const connection = await DB.getConnection(); | ||||||
|     try { |     try { | ||||||
|       const [rows] = await connection.query(query, params); |       const [rows] = await connection.query(query, params); | ||||||
| @ -216,7 +215,6 @@ class BlocksRepository { | |||||||
|       ORDER BY height |       ORDER BY height | ||||||
|       LIMIT 1;`;
 |       LIMIT 1;`;
 | ||||||
| 
 | 
 | ||||||
|     // logger.debug(query);
 |  | ||||||
|     const connection = await DB.getConnection(); |     const connection = await DB.getConnection(); | ||||||
|     try { |     try { | ||||||
|       const [rows]: any[] = await connection.query(query); |       const [rows]: any[] = await connection.query(query); | ||||||
| @ -237,14 +235,15 @@ class BlocksRepository { | |||||||
|   /** |   /** | ||||||
|    * Get blocks mined by a specific mining pool |    * Get blocks mined by a specific mining pool | ||||||
|    */ |    */ | ||||||
|   public async $getBlocksByPool(poolId: number, startHeight: number | null = null): Promise<object[]> { |   public async $getBlocksByPool(poolId: number, startHeight: number | undefined = undefined): Promise<object[]> { | ||||||
|     const params: any[] = []; |     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 |       FROM blocks | ||||||
|       WHERE pool_id = ?`;
 |       WHERE pool_id = ?`;
 | ||||||
|     params.push(poolId); |     params.push(poolId); | ||||||
| 
 | 
 | ||||||
|     if (startHeight) { |     if (startHeight !== undefined) { | ||||||
|       query += ` AND height < ?`; |       query += ` AND height < ?`; | ||||||
|       params.push(startHeight); |       params.push(startHeight); | ||||||
|     } |     } | ||||||
| @ -252,17 +251,17 @@ class BlocksRepository { | |||||||
|     query += ` ORDER BY height DESC
 |     query += ` ORDER BY height DESC
 | ||||||
|       LIMIT 10`;
 |       LIMIT 10`;
 | ||||||
| 
 | 
 | ||||||
|     // logger.debug(query);
 |  | ||||||
|     const connection = await DB.getConnection(); |     const connection = await DB.getConnection(); | ||||||
|     try { |     try { | ||||||
|       const [rows] = await connection.query(query, params); |       const [rows] = await connection.query(query, params); | ||||||
|       connection.release(); |       connection.release(); | ||||||
| 
 | 
 | ||||||
|       for (const block of <object[]>rows) { |       const blocks: BlockExtended[] = []; | ||||||
|         delete block['blockTimestamp']; |       for (let block of <object[]>rows) { | ||||||
|  |         blocks.push(prepareBlock(block)); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return <object[]>rows; |       return blocks; | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       connection.release(); |       connection.release(); | ||||||
|       logger.err('$getBlocksByPool() error' + (e instanceof Error ? e.message : e)); |       logger.err('$getBlocksByPool() error' + (e instanceof Error ? e.message : e)); | ||||||
|  | |||||||
| @ -553,7 +553,7 @@ class Routes { | |||||||
|     try { |     try { | ||||||
|       const poolBlocks = await BlocksRepository.$getBlocksByPool( |       const poolBlocks = await BlocksRepository.$getBlocksByPool( | ||||||
|         parseInt(req.params.poolId, 10), |         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('Pragma', 'public'); | ||||||
|       res.header('Cache-control', 'public'); |       res.header('Cache-control', 'public'); | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								backend/src/utils/blocks-utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								backend/src/utils/blocks-utils.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | import { BlockExtended } from "../mempool.interfaces"; | ||||||
|  | 
 | ||||||
|  | export function prepareBlock(block: any): BlockExtended { | ||||||
|  |   return <BlockExtended>{ | ||||||
|  |     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), | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
| @ -1,20 +1,20 @@ | |||||||
| <div class="container"> | <div class="container-xl"> | ||||||
| 
 | 
 | ||||||
|   <div *ngIf="poolStats$ | async as poolStats; else loadingMain"> |   <div *ngIf="poolStats$ | async as poolStats; else loadingMain"> | ||||||
|     <h1 class="m-0 mb-2"> |     <div style="display:flex"> | ||||||
|       <img width="50" height="50" src="{{ poolStats['logo'] }}" |       <img width="50" height="50" src="{{ poolStats['logo'] }}" | ||||||
|         onError="this.src = './resources/mining-pools/default.svg'" class="mr-3"> |         onError="this.src = './resources/mining-pools/default.svg'" class="mr-3 mb-3"> | ||||||
|       {{ poolStats.pool.name }} |       <h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1> | ||||||
|     </h1> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div class="box"> |     <div class="box"> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-lg-7"> |         <div class="col-lg-9"> | ||||||
|           <table class="table table-borderless table-striped" style="table-layout: fixed;"> |           <table class="table table-borderless table-striped" style="table-layout: fixed;"> | ||||||
|             <tbody> |             <tbody> | ||||||
|               <tr> |               <tr> | ||||||
|                 <td class="label">Tags</td> |                 <td class="label">Tags</td> | ||||||
|                 <td class="text-truncate"> |                 <td class="text-truncate" *ngIf="poolStats.pool.regexes.length else nodata"> | ||||||
|                   <div class="scrollable"> |                   <div class="scrollable"> | ||||||
|                     {{ poolStats.pool.regexes }} |                     {{ poolStats.pool.regexes }} | ||||||
|                   </div> |                   </div> | ||||||
| @ -22,21 +22,21 @@ | |||||||
|               </tr> |               </tr> | ||||||
|               <tr> |               <tr> | ||||||
|                 <td class="label">Addresses</td> |                 <td class="label">Addresses</td> | ||||||
|                 <td class="text-truncate" *ngIf="poolStats.pool.addresses.length else noaddress"> |                 <td class="text-truncate" *ngIf="poolStats.pool.addresses.length else nodata"> | ||||||
|                   <div class="scrollable"> |                   <div class="scrollable"> | ||||||
|                     <a *ngFor="let address of poolStats.pool.addresses" |                     <a *ngFor="let address of poolStats.pool.addresses" | ||||||
|                       [routerLink]="['/address' | relativeUrl, address]">{{ |                       [routerLink]="['/address' | relativeUrl, address]">{{ | ||||||
|                       address }}<br></a> |                       address }}<br></a> | ||||||
|                   </div> |                   </div> | ||||||
|                 </td> |                 </td> | ||||||
|                 <ng-template #noaddress> |                 <ng-template #nodata> | ||||||
|                   <td>~</td> |                   <td class="right-mobile">~</td> | ||||||
|                 </ng-template> |                 </ng-template> | ||||||
|               </tr> |               </tr> | ||||||
|             </tbody> |             </tbody> | ||||||
|           </table> |           </table> | ||||||
|         </div> |         </div> | ||||||
|         <div class="col-lg-5"> |         <div class="col-lg-3"> | ||||||
|           <table class="table table-borderless table-striped"> |           <table class="table table-borderless table-striped"> | ||||||
|             <tbody> |             <tbody> | ||||||
|               <tr> |               <tr> | ||||||
| @ -59,31 +59,48 @@ | |||||||
|     <div class="spinner-border text-light"></div> |     <div class="spinner-border text-light"></div> | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <table *ngIf="blocks$ | async as blocks" class="table table-borderless" [alwaysCallback]="true" infiniteScroll |   <table class="table table-borderless" [alwaysCallback]="true" infiniteScroll | ||||||
|     [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" |     [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" | ||||||
|     (scrolled)="loadMore()"> |     (scrolled)="loadMore()"> | ||||||
|     <thead> |     <thead> | ||||||
|       <th style="width: 15%;" i18n="latest-blocks.height">Height</th> |       <th class="height" i18n="latest-blocks.height">Height</th> | ||||||
|       <th class="d-none d-md-block" style="width: 20%;" i18n="latest-blocks.timestamp">Timestamp</th> |       <th class="timestamp" i18n="latest-blocks.timestamp">Timestamp</th> | ||||||
|       <th style="width: 20%;" i18n="latest-blocks.mined">Mined</th> |       <th class="mined" i18n="latest-blocks.mined">Mined</th> | ||||||
|       <th class="text-right" style="width: 10%; padding-right: 30px" i18n="latest-blocks.reward">Reward</th> |       <th class="coinbase text-left" i18n="latest-blocks.coinbasetag"> | ||||||
|       <th class="d-none d-lg-block text-right" style="width: 15%; padding-right: 40px" |         Coinbase Tag</th> | ||||||
|         i18n="latest-blocks.transactions"> |       <th class="reward text-right" i18n="latest-blocks.reward"> | ||||||
|         Transactions</th> |         Reward</th> | ||||||
|       <th style="width: 20%;" i18n="latest-blocks.size">Size</th> |       <th class="fees text-right" i18n="latest-blocks.fees">Fees</th> | ||||||
|  |       <th class="txs text-right" i18n="latest-blocks.transactions">Txs</th> | ||||||
|  |       <th class="size" i18n="latest-blocks.size">Size</th> | ||||||
|     </thead> |     </thead> | ||||||
|     <tbody> |     <tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''"> | ||||||
|       <tr *ngFor="let block of blocks"> |       <tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock"> | ||||||
|         <td><a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a></td> |         <td class="height"> | ||||||
|         <td class="d-none d-md-block">‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td> |           <a [routerLink]="['/block' | relativeUrl, block.height]">{{ block.height | ||||||
|         <td> |             }}</a> | ||||||
|  |         </td> | ||||||
|  |         <td class="timestamp"> | ||||||
|  |           ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} | ||||||
|  |         </td> | ||||||
|  |         <td class="mined"> | ||||||
|           <app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> |           <app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> | ||||||
|         </td> |         </td> | ||||||
|         <td class="text-right" style="padding-right: 30px"> |         <td class="coinbase"> | ||||||
|           <app-amount [satoshis]="block['reward']" digitsInfo="1.2-2" [noFiat]="true"></app-amount> |           <span class="badge badge-secondary scriptmessage longer"> | ||||||
|  |             {{ block.extras.coinbaseRaw | hex2ascii }} | ||||||
|  |           </span> | ||||||
|         </td> |         </td> | ||||||
|         <td class="d-none d-lg-block text-right" style="padding-right: 40px">{{ block.tx_count | number }}</td> |         <td class="reward text-right"> | ||||||
|         <td> |           <app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2"></app-amount> | ||||||
|  |         </td> | ||||||
|  |         <td class="fees text-right"> | ||||||
|  |           <app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2"></app-amount> | ||||||
|  |         </td> | ||||||
|  |         <td class="txs text-right"> | ||||||
|  |           {{ block.tx_count | number }} | ||||||
|  |         </td> | ||||||
|  |         <td class="size"> | ||||||
|           <div class="progress"> |           <div class="progress"> | ||||||
|             <div class="progress-bar progress-mempool" role="progressbar" |             <div class="progress-bar progress-mempool" role="progressbar" | ||||||
|               [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div> |               [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div> | ||||||
| @ -92,16 +109,46 @@ | |||||||
|         </td> |         </td> | ||||||
|       </tr> |       </tr> | ||||||
|     </tbody> |     </tbody> | ||||||
|  |     <ng-template #skeleton> | ||||||
|  |       <tbody> | ||||||
|  |         <tr *ngFor="let item of skeletonLines"> | ||||||
|  |           <td class="height"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="timestamp"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="mined"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="coinbase"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="reward text-right"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="fees text-right"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="txs text-right"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |           <td class="size text-right"> | ||||||
|  |             <span class="skeleton-loader"></span> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |       </tbody> | ||||||
|  |     </ng-template> | ||||||
|   </table> |   </table> | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <ng-template #loadingMain> | <ng-template #loadingMain> | ||||||
|   <div> |   <div> | ||||||
|     <h1 class="m-0 mb-2"> |     <div style="display:flex"> | ||||||
|       <img width="50" height="50" src="./resources/mining-pools/default.svg" class="mr-3"> |       <img width="50" height="50" src="./resources/mining-pools/default.svg" class="mr-3 mb-3"> | ||||||
|       <div class="skeleton-loader"></div> |       <h1 class="m-0 pt-1 pt-md-0"><div class="skeleton-loader"></div></h1> | ||||||
|     </h1> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div class="box"> |     <div class="box"> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
| @ -109,22 +156,22 @@ | |||||||
|           <table class="table table-borderless table-striped" style="table-layout: fixed;"> |           <table class="table table-borderless table-striped" style="table-layout: fixed;"> | ||||||
|             <tbody> |             <tbody> | ||||||
|               <tr> |               <tr> | ||||||
|                 <td class="col-4 col-lg-3">Addresses</td> |                 <td class="label">Tags</td> | ||||||
|  |                 <td class="text-truncate"> | ||||||
|  |                   <div class="skeleton-loader"></div> | ||||||
|  |                 </td> | ||||||
|  |               </tr> | ||||||
|  |               <tr> | ||||||
|  |                 <td class="label">Addresses</td> | ||||||
|                 <td class="text-truncate"> |                 <td class="text-truncate"> | ||||||
|                   <div class="scrollable"> |                   <div class="scrollable"> | ||||||
|                     <div class="skeleton-loader"></div> |                     <div class="skeleton-loader"></div> | ||||||
|                   </div> |                   </div> | ||||||
|                 </td> |                 </td> | ||||||
|                 <ng-template #noaddress> |                 <ng-template #nodata> | ||||||
|                   <td>~</td> |                   <td>~</td> | ||||||
|                 </ng-template> |                 </ng-template> | ||||||
|               </tr> |               </tr> | ||||||
|               <tr> |  | ||||||
|                 <td class="col-4 col-lg-3">Coinbase Tags</td> |  | ||||||
|                 <td class="text-truncate"> |  | ||||||
|                   <div class="skeleton-loader"></div> |  | ||||||
|                 </td> |  | ||||||
|               </tr> |  | ||||||
|             </tbody> |             </tbody> | ||||||
|           </table> |           </table> | ||||||
|         </div> |         </div> | ||||||
| @ -132,14 +179,14 @@ | |||||||
|           <table class="table table-borderless table-striped"> |           <table class="table table-borderless table-striped"> | ||||||
|             <tbody> |             <tbody> | ||||||
|               <tr> |               <tr> | ||||||
|                 <td class="col-4 col-lg-8">Mined Blocks</td> |                 <td class="label">Mined Blocks</td> | ||||||
|                 <td class="text-left"> |                 <td class="text-truncate"> | ||||||
|                   <div class="skeleton-loader"></div> |                   <div class="skeleton-loader"></div> | ||||||
|                 </td> |                 </td> | ||||||
|               </tr> |               </tr> | ||||||
|               <tr> |               <tr> | ||||||
|                 <td class="col-4 col-lg-8">Empty Blocks</td> |                 <td class="label">Empty Blocks</td> | ||||||
|                 <td class="text-left"> |                 <td class="text-truncate"> | ||||||
|                   <div class="skeleton-loader"></div> |                   <div class="skeleton-loader"></div> | ||||||
|                 </td> |                 </td> | ||||||
|               </tr> |               </tr> | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| .progress { | .container-xl { | ||||||
|   background-color: #2d3348; |   max-width: 1400px; | ||||||
|  |   padding-bottom: 100px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @media (min-width: 768px) { | @media (min-width: 768px) { | ||||||
| @ -39,28 +40,100 @@ div.scrollable { | |||||||
|   max-height: 75px; |   max-height: 75px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .skeleton-loader { |  | ||||||
|   width: 100%; |  | ||||||
|   max-width: 90px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .table { |  | ||||||
|   margin: 0px auto; |  | ||||||
|   max-width: 900px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .box { | .box { | ||||||
|   padding-bottom: 0px; |   padding-bottom: 5px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .label { | .label { | ||||||
|   max-width: 50px; |  | ||||||
|   width: 30%; |   width: 30%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .data { | .data { | ||||||
|   text-align: center; |   text-align: left; | ||||||
|   @media (max-width: 767.98px) { |   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; |     text-align: right; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } | |||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { EChartsOption, graphic } from 'echarts'; | import { EChartsOption, graphic } from 'echarts'; | ||||||
| import { BehaviorSubject, Observable } from 'rxjs'; | 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 { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; | ||||||
| import { ApiService } from 'src/app/services/api.service'; | import { ApiService } from 'src/app/services/api.service'; | ||||||
| import { StateService } from 'src/app/services/state.service'; | import { StateService } from 'src/app/services/state.service'; | ||||||
| @ -31,6 +31,7 @@ export class PoolComponent implements OnInit { | |||||||
|   poolStats$: Observable<PoolStat>; |   poolStats$: Observable<PoolStat>; | ||||||
|   blocks$: Observable<BlockExtended[]>; |   blocks$: Observable<BlockExtended[]>; | ||||||
|   isLoading = true; |   isLoading = true; | ||||||
|  |   skeletonLines: number[] = [...Array(5).keys()]; | ||||||
| 
 | 
 | ||||||
|   chartOptions: EChartsOption = {}; |   chartOptions: EChartsOption = {}; | ||||||
|   chartInitOptions = { |   chartInitOptions = { | ||||||
| @ -39,8 +40,7 @@ export class PoolComponent implements OnInit { | |||||||
|     height: 'auto', |     height: 'auto', | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   fromHeight: number = -1; |   loadMoreSubject: BehaviorSubject<undefined> = new BehaviorSubject(undefined); | ||||||
|   fromHeightSubject: BehaviorSubject<number> = new BehaviorSubject(this.fromHeight); |  | ||||||
| 
 | 
 | ||||||
|   blocks: BlockExtended[] = []; |   blocks: BlockExtended[] = []; | ||||||
|   poolId: number = undefined; |   poolId: number = undefined; | ||||||
| @ -66,12 +66,9 @@ export class PoolComponent implements OnInit { | |||||||
|                 this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); |                 this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); | ||||||
|                 return poolId; |                 return poolId; | ||||||
|               }), |               }), | ||||||
|             ) |             ); | ||||||
|         }), |         }), | ||||||
|         switchMap(() => { |         switchMap(() => { | ||||||
|           if (this.blocks.length === 0) { |  | ||||||
|             this.fromHeightSubject.next(undefined); |  | ||||||
|           } |  | ||||||
|           return this.apiService.getPoolStats$(this.poolId); |           return this.apiService.getPoolStats$(this.poolId); | ||||||
|         }), |         }), | ||||||
|         map((poolStats) => { |         map((poolStats) => { | ||||||
| @ -88,17 +85,16 @@ export class PoolComponent implements OnInit { | |||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     this.blocks$ = this.fromHeightSubject |     this.blocks$ = this.loadMoreSubject | ||||||
|       .pipe( |       .pipe( | ||||||
|         distinctUntilChanged(), |         switchMap((flag) => { | ||||||
|         switchMap((fromHeight) => { |           return this.apiService.getPoolBlocks$(this.poolId, this.blocks[this.blocks.length - 1]?.height); | ||||||
|           return this.apiService.getPoolBlocks$(this.poolId, fromHeight); |  | ||||||
|         }), |         }), | ||||||
|         tap((newBlocks) => { |         tap((newBlocks) => { | ||||||
|           this.blocks = this.blocks.concat(newBlocks); |           this.blocks = this.blocks.concat(newBlocks); | ||||||
|         }), |         }), | ||||||
|         map(() => this.blocks) |         map(() => this.blocks) | ||||||
|       ) |       ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   prepareChartOptions(data) { |   prepareChartOptions(data) { | ||||||
| @ -133,18 +129,18 @@ export class PoolComponent implements OnInit { | |||||||
|           align: 'left', |           align: 'left', | ||||||
|         }, |         }, | ||||||
|         borderColor: '#000', |         borderColor: '#000', | ||||||
|         formatter: function (data) { |         formatter: function(ticks: any[]) { | ||||||
|           let hashratePowerOfTen: any = selectPowerOfTen(1); |           let hashratePowerOfTen: any = selectPowerOfTen(1); | ||||||
|           let hashrate = data[0].data[1]; |           let hashrate = ticks[0].data[1]; | ||||||
| 
 | 
 | ||||||
|           if (this.isMobile()) { |           if (this.isMobile()) { | ||||||
|             hashratePowerOfTen = selectPowerOfTen(data[0].data[1]); |             hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1]); | ||||||
|             hashrate = Math.round(data[0].data[1] / hashratePowerOfTen.divider); |             hashrate = Math.round(ticks[0].data[1] / hashratePowerOfTen.divider); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           return ` |           return ` | ||||||
|             <b style="color: white; margin-left: 18px">${data[0].axisValueLabel}</b><br> |             <b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br> | ||||||
|             <span>${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br> |             <span>${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br> | ||||||
|           `;
 |           `;
 | ||||||
|         }.bind(this) |         }.bind(this) | ||||||
|       }, |       }, | ||||||
| @ -154,11 +150,10 @@ export class PoolComponent implements OnInit { | |||||||
|       }, |       }, | ||||||
|       yAxis: [ |       yAxis: [ | ||||||
|         { |         { | ||||||
|           min: function (value) { |           min: (value) => { | ||||||
|             return value.min * 0.9; |             return value.min * 0.9; | ||||||
|           }, |           }, | ||||||
|           type: 'value', |           type: 'value', | ||||||
|           name: 'Hashrate', |  | ||||||
|           axisLabel: { |           axisLabel: { | ||||||
|             color: 'rgb(110, 112, 121)', |             color: 'rgb(110, 112, 121)', | ||||||
|             formatter: (val) => { |             formatter: (val) => { | ||||||
| @ -192,7 +187,7 @@ export class PoolComponent implements OnInit { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   loadMore() { |   loadMore() { | ||||||
|     this.fromHeightSubject.next(this.blocks[this.blocks.length - 1]?.height); |     this.loadMoreSubject.next(undefined); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   trackByBlock(index: number, block: BlockExtended) { |   trackByBlock(index: number, block: BlockExtended) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user