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 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<BlockExtended> { | ||||
|     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<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
 | ||||
|     // 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 <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 { | ||||
|     return this.lastDifficultyAdjustmentTime; | ||||
|   } | ||||
|  | ||||
| @ -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); | ||||
| @ -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<object[]> { | ||||
|   public async $getBlocksByPool(poolId: number, startHeight: number | undefined = undefined): Promise<object[]> { | ||||
|     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 <object[]>rows) { | ||||
|         delete block['blockTimestamp']; | ||||
|       const blocks: BlockExtended[] = []; | ||||
|       for (let block of <object[]>rows) { | ||||
|         blocks.push(prepareBlock(block)); | ||||
|       } | ||||
| 
 | ||||
|       return <object[]>rows; | ||||
|       return blocks; | ||||
|     } catch (e) { | ||||
|       connection.release(); | ||||
|       logger.err('$getBlocksByPool() error' + (e instanceof Error ? e.message : e)); | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
							
								
								
									
										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"> | ||||
|     <h1 class="m-0 mb-2"> | ||||
|     <div style="display:flex"> | ||||
|       <img width="50" height="50" src="{{ poolStats['logo'] }}" | ||||
|         onError="this.src = './resources/mining-pools/default.svg'" class="mr-3"> | ||||
|       {{ poolStats.pool.name }} | ||||
|     </h1> | ||||
|         onError="this.src = './resources/mining-pools/default.svg'" class="mr-3 mb-3"> | ||||
|       <h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="box"> | ||||
|       <div class="row"> | ||||
|         <div class="col-lg-7"> | ||||
|         <div class="col-lg-9"> | ||||
|           <table class="table table-borderless table-striped" style="table-layout: fixed;"> | ||||
|             <tbody> | ||||
|               <tr> | ||||
|                 <td class="label">Tags</td> | ||||
|                 <td class="text-truncate"> | ||||
|                 <td class="text-truncate" *ngIf="poolStats.pool.regexes.length else nodata"> | ||||
|                   <div class="scrollable"> | ||||
|                     {{ poolStats.pool.regexes }} | ||||
|                   </div> | ||||
| @ -22,21 +22,21 @@ | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <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"> | ||||
|                     <a *ngFor="let address of poolStats.pool.addresses" | ||||
|                       [routerLink]="['/address' | relativeUrl, address]">{{ | ||||
|                       address }}<br></a> | ||||
|                   </div> | ||||
|                 </td> | ||||
|                 <ng-template #noaddress> | ||||
|                   <td>~</td> | ||||
|                 <ng-template #nodata> | ||||
|                   <td class="right-mobile">~</td> | ||||
|                 </ng-template> | ||||
|               </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|         </div> | ||||
|         <div class="col-lg-5"> | ||||
|         <div class="col-lg-3"> | ||||
|           <table class="table table-borderless table-striped"> | ||||
|             <tbody> | ||||
|               <tr> | ||||
| @ -59,31 +59,48 @@ | ||||
|     <div class="spinner-border text-light"></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" | ||||
|     (scrolled)="loadMore()"> | ||||
|     <thead> | ||||
|       <th style="width: 15%;" i18n="latest-blocks.height">Height</th> | ||||
|       <th class="d-none d-md-block" style="width: 20%;" i18n="latest-blocks.timestamp">Timestamp</th> | ||||
|       <th style="width: 20%;" i18n="latest-blocks.mined">Mined</th> | ||||
|       <th class="text-right" style="width: 10%; padding-right: 30px" i18n="latest-blocks.reward">Reward</th> | ||||
|       <th class="d-none d-lg-block text-right" style="width: 15%; padding-right: 40px" | ||||
|         i18n="latest-blocks.transactions"> | ||||
|         Transactions</th> | ||||
|       <th style="width: 20%;" i18n="latest-blocks.size">Size</th> | ||||
|       <th class="height" i18n="latest-blocks.height">Height</th> | ||||
|       <th class="timestamp" i18n="latest-blocks.timestamp">Timestamp</th> | ||||
|       <th class="mined" i18n="latest-blocks.mined">Mined</th> | ||||
|       <th class="coinbase text-left" i18n="latest-blocks.coinbasetag"> | ||||
|         Coinbase Tag</th> | ||||
|       <th class="reward text-right" i18n="latest-blocks.reward"> | ||||
|         Reward</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> | ||||
|     <tbody> | ||||
|       <tr *ngFor="let block of blocks"> | ||||
|         <td><a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a></td> | ||||
|         <td class="d-none d-md-block">‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td> | ||||
|         <td> | ||||
|     <tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''"> | ||||
|       <tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock"> | ||||
|         <td class="height"> | ||||
|           <a [routerLink]="['/block' | relativeUrl, block.height]">{{ block.height | ||||
|             }}</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> | ||||
|         </td> | ||||
|         <td class="text-right" style="padding-right: 30px"> | ||||
|           <app-amount [satoshis]="block['reward']" digitsInfo="1.2-2" [noFiat]="true"></app-amount> | ||||
|         <td class="coinbase"> | ||||
|           <span class="badge badge-secondary scriptmessage longer"> | ||||
|             {{ block.extras.coinbaseRaw | hex2ascii }} | ||||
|           </span> | ||||
|         </td> | ||||
|         <td class="d-none d-lg-block text-right" style="padding-right: 40px">{{ block.tx_count | number }}</td> | ||||
|         <td> | ||||
|         <td class="reward text-right"> | ||||
|           <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-bar progress-mempool" role="progressbar" | ||||
|               [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div> | ||||
| @ -92,16 +109,46 @@ | ||||
|         </td> | ||||
|       </tr> | ||||
|     </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> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <ng-template #loadingMain> | ||||
|   <div> | ||||
|     <h1 class="m-0 mb-2"> | ||||
|       <img width="50" height="50" src="./resources/mining-pools/default.svg" class="mr-3"> | ||||
|       <div class="skeleton-loader"></div> | ||||
|     </h1> | ||||
|     <div style="display:flex"> | ||||
|       <img width="50" height="50" src="./resources/mining-pools/default.svg" class="mr-3 mb-3"> | ||||
|       <h1 class="m-0 pt-1 pt-md-0"><div class="skeleton-loader"></div></h1> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="box"> | ||||
|       <div class="row"> | ||||
| @ -109,22 +156,22 @@ | ||||
|           <table class="table table-borderless table-striped" style="table-layout: fixed;"> | ||||
|             <tbody> | ||||
|               <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"> | ||||
|                   <div class="scrollable"> | ||||
|                     <div class="skeleton-loader"></div> | ||||
|                   </div> | ||||
|                 </td> | ||||
|                 <ng-template #noaddress> | ||||
|                 <ng-template #nodata> | ||||
|                   <td>~</td> | ||||
|                 </ng-template> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <td class="col-4 col-lg-3">Coinbase Tags</td> | ||||
|                 <td class="text-truncate"> | ||||
|                   <div class="skeleton-loader"></div> | ||||
|                 </td> | ||||
|               </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|         </div> | ||||
| @ -132,14 +179,14 @@ | ||||
|           <table class="table table-borderless table-striped"> | ||||
|             <tbody> | ||||
|               <tr> | ||||
|                 <td class="col-4 col-lg-8">Mined Blocks</td> | ||||
|                 <td class="text-left"> | ||||
|                 <td class="label">Mined Blocks</td> | ||||
|                 <td class="text-truncate"> | ||||
|                   <div class="skeleton-loader"></div> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <td class="col-4 col-lg-8">Empty Blocks</td> | ||||
|                 <td class="text-left"> | ||||
|                 <td class="label">Empty Blocks</td> | ||||
|                 <td class="text-truncate"> | ||||
|                   <div class="skeleton-loader"></div> | ||||
|                 </td> | ||||
|               </tr> | ||||
|  | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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<PoolStat>; | ||||
|   blocks$: Observable<BlockExtended[]>; | ||||
|   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<number> = new BehaviorSubject(this.fromHeight); | ||||
|   loadMoreSubject: BehaviorSubject<undefined> = 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 ` | ||||
|             <b style="color: white; margin-left: 18px">${data[0].axisValueLabel}</b><br> | ||||
|             <span>${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br> | ||||
|             <b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br> | ||||
|             <span>${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br> | ||||
|           `;
 | ||||
|         }.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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user