From 02d67e84061c4380a42f33f440dc31c1d4a480d4 Mon Sep 17 00:00:00 2001 From: Simon Lindh Date: Wed, 6 Nov 2019 15:35:02 +0800 Subject: [PATCH] Explorer page with latest blocks. WIP --- .../bitcoin/bitcoin-api-abstract-factory.ts | 3 ++ backend/src/api/bitcoin/bitcoind-api.ts | 8 +++++ backend/src/api/bitcoin/esplora-api.ts | 22 ++++++++++++ backend/src/index.ts | 10 +++++- backend/src/routes.ts | 15 ++++++++ frontend/src/app/app-routing.module.ts | 4 +++ .../blockchain-blocks.component.html | 2 +- .../blockchain-blocks.component.ts | 17 ---------- .../app/explorer/block/block.component.html | 1 + .../app/explorer/block/block.component.scss | 0 .../src/app/explorer/block/block.component.ts | 15 ++++++++ frontend/src/app/explorer/explorer.module.ts | 32 +++++++++++++++++ .../explorer/explorer/explorer.component.html | 34 +++++++++++++++++++ .../explorer/explorer/explorer.component.scss | 0 .../explorer/explorer/explorer.component.ts | 33 ++++++++++++++++++ .../transaction/transaction.component.html | 1 + .../transaction/transaction.component.scss | 0 .../transaction/transaction.component.ts | 15 ++++++++ .../master-page/master-page.component.html | 5 ++- frontend/src/app/services/api.service.ts | 4 +++ .../pipes/time-since/time-since.pipe.ts | 21 ++++++++++++ frontend/src/app/shared/shared.module.ts | 3 ++ 22 files changed, 225 insertions(+), 20 deletions(-) create mode 100644 frontend/src/app/explorer/block/block.component.html create mode 100644 frontend/src/app/explorer/block/block.component.scss create mode 100644 frontend/src/app/explorer/block/block.component.ts create mode 100644 frontend/src/app/explorer/explorer.module.ts create mode 100644 frontend/src/app/explorer/explorer/explorer.component.html create mode 100644 frontend/src/app/explorer/explorer/explorer.component.scss create mode 100644 frontend/src/app/explorer/explorer/explorer.component.ts create mode 100644 frontend/src/app/explorer/transaction/transaction.component.html create mode 100644 frontend/src/app/explorer/transaction/transaction.component.scss create mode 100644 frontend/src/app/explorer/transaction/transaction.component.ts create mode 100644 frontend/src/app/shared/pipes/time-since/time-since.pipe.ts diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 29361f260..ae7191c82 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -7,4 +7,7 @@ export interface AbstractBitcoinApi { getBlockCount(): Promise; getBlock(hash: string): Promise; getBlockHash(height: number): Promise; + + getBlocks(): Promise; + getBlocksFromHeight(height: number): Promise; } diff --git a/backend/src/api/bitcoin/bitcoind-api.ts b/backend/src/api/bitcoin/bitcoind-api.ts index e047014ac..372db12d2 100644 --- a/backend/src/api/bitcoin/bitcoind-api.ts +++ b/backend/src/api/bitcoin/bitcoind-api.ts @@ -80,6 +80,14 @@ class BitcoindApi implements AbstractBitcoinApi { }); }); } + + getBlocks(): Promise { + throw new Error('Method not implemented.'); + } + + getBlocksFromHeight(height: number): Promise { + throw new Error('Method not implemented.'); + } } export default BitcoindApi; diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 4709aed0c..77683fd4d 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -94,6 +94,28 @@ class EsploraApi implements AbstractBitcoinApi { } }); } + + getBlocks(): Promise { + return new Promise(async (resolve, reject) => { + try { + const response: AxiosResponse = await this.client.get('/blocks'); + resolve(response.data); + } catch (error) { + reject(error); + } + }); + } + + getBlocksFromHeight(height: number): Promise { + return new Promise(async (resolve, reject) => { + try { + const response: AxiosResponse = await this.client.get('/blocks/' + height); + resolve(response.data); + } catch (error) { + reject(error); + } + }); + } } export default EsploraApi; diff --git a/backend/src/index.ts b/backend/src/index.ts index 861682a4c..85ddbb19e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -263,7 +263,15 @@ class MempoolSpace { .get(config.API_ENDPOINT + 'statistics/3m', routes.get3MStatistics.bind(routes)) .get(config.API_ENDPOINT + 'statistics/6m', routes.get6MStatistics.bind(routes)) ; + + if (config.BACKEND_API === 'esplora') { + this.app + .get(config.API_ENDPOINT + 'explorer/blocks', routes.getBlocks) + .get(config.API_ENDPOINT + 'explorer/blocks/:height', routes.getBlocks) + ; + } + + } } -} const mempoolSpace = new MempoolSpace(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 371240eca..7229b659b 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1,6 +1,7 @@ import statistics from './api/statistics'; import feeApi from './api/fee-api'; import projectedBlocks from './api/projected-blocks'; +import bitcoinApi from './api/bitcoin/bitcoin-api-factory'; class Routes { private cache = {}; @@ -75,6 +76,20 @@ class Routes { res.status(500).send(e.message); } } + + public async getBlocks(req, res) { + try { + let result: string; + if (req.params.height) { + result = await bitcoinApi.getBlocksFromHeight(req.params.height); + } else { + result = await bitcoinApi.getBlocks(); + } + res.send(result); + } catch (e) { + res.status(500).send(e.message); + } + } } export default new Routes(); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 76da3254d..f969aea85 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -34,6 +34,10 @@ const routes: Routes = [ path: 'graphs', component: StatisticsComponent, }, + { + path: 'explorer', + loadChildren: './explorer/explorer.module#ExplorerModule', + }, ], }, { diff --git a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.html index 1c0841085..d62f8c838 100644 --- a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.html @@ -13,7 +13,7 @@
{{ block.size | bytes: 2 }}
{{ block.nTx }} transactions


-
{{ getTimeSinceMined(block) }} ago
+
{{ block.time | timeSince }} ago
diff --git a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts index 3e43ff83e..e788e9237 100644 --- a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts @@ -34,23 +34,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { this.blocksSubscription.unsubscribe(); } - getTimeSinceMined(block: IBlock): string { - const minutes = ((new Date().getTime()) - (new Date(block.time * 1000).getTime())) / 1000 / 60; - if (minutes >= 120) { - return Math.floor(minutes / 60) + ' hours'; - } - if (minutes >= 60) { - return Math.floor(minutes / 60) + ' hour'; - } - if (minutes <= 1) { - return '< 1 minute'; - } - if (minutes === 1) { - return '1 minute'; - } - return Math.round(minutes) + ' minutes'; - } - trackByBlocksFn(index: number, item: IBlock) { return item.height; } diff --git a/frontend/src/app/explorer/block/block.component.html b/frontend/src/app/explorer/block/block.component.html new file mode 100644 index 000000000..44579d51c --- /dev/null +++ b/frontend/src/app/explorer/block/block.component.html @@ -0,0 +1 @@ +

block works!

diff --git a/frontend/src/app/explorer/block/block.component.scss b/frontend/src/app/explorer/block/block.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/explorer/block/block.component.ts b/frontend/src/app/explorer/block/block.component.ts new file mode 100644 index 000000000..d45fb1abe --- /dev/null +++ b/frontend/src/app/explorer/block/block.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-block', + templateUrl: './block.component.html', + styleUrls: ['./block.component.scss'] +}) +export class BlockComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/frontend/src/app/explorer/explorer.module.ts b/frontend/src/app/explorer/explorer.module.ts new file mode 100644 index 000000000..b3952d03b --- /dev/null +++ b/frontend/src/app/explorer/explorer.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ExplorerComponent } from './explorer/explorer.component'; +import { TransactionComponent } from './transaction/transaction.component'; +import { RouterModule, Routes } from '@angular/router'; +import { SharedModule } from '../shared/shared.module'; +import { BlockComponent } from './block/block.component'; + +const routes: Routes = [ + { + path: '', + component: ExplorerComponent, + }, + { + path: 'block/:id', + component: BlockComponent, + }, + { + path: 'tx/:id', + component: TransactionComponent, + }, +]; + +@NgModule({ + declarations: [ExplorerComponent, TransactionComponent, BlockComponent], + imports: [ + SharedModule, + CommonModule, + RouterModule.forChild(routes), + ] +}) +export class ExplorerModule { } diff --git a/frontend/src/app/explorer/explorer/explorer.component.html b/frontend/src/app/explorer/explorer/explorer.component.html new file mode 100644 index 000000000..7e0485baa --- /dev/null +++ b/frontend/src/app/explorer/explorer/explorer.component.html @@ -0,0 +1,34 @@ +
+

Latest blocks

+ + + + + + + + + + + + + + + + + + + + +
HeightTimestampMinedTransactionsSize (kB)Weight (kWU)
#{{ block.height }}{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ block.timestamp | timeSince }} ago {{ block.tx_count }}{{ block.size | bytes: 2 }}{{ block.weight | bytes: 2 }}
+ +
+ +
+

+
+ +
+ +
+
diff --git a/frontend/src/app/explorer/explorer/explorer.component.scss b/frontend/src/app/explorer/explorer/explorer.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/explorer/explorer/explorer.component.ts b/frontend/src/app/explorer/explorer/explorer.component.ts new file mode 100644 index 000000000..5331c9d91 --- /dev/null +++ b/frontend/src/app/explorer/explorer/explorer.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { ApiService } from 'src/app/services/api.service'; + +@Component({ + selector: 'app-explorer', + templateUrl: './explorer.component.html', + styleUrls: ['./explorer.component.scss'] +}) +export class ExplorerComponent implements OnInit { + blocks: any[] = []; + isLoading = true; + + constructor( + private apiService: ApiService, + ) { } + + ngOnInit() { + this.apiService.listBlocks$() + .subscribe((blocks) => { + this.blocks = blocks; + this.isLoading = false; + }); + } + + loadMore() { + this.isLoading = true; + this.apiService.listBlocks$(this.blocks[this.blocks.length - 1].height - 1) + .subscribe((blocks) => { + this.blocks = this.blocks.concat(blocks); + this.isLoading = false; + }); + } +} diff --git a/frontend/src/app/explorer/transaction/transaction.component.html b/frontend/src/app/explorer/transaction/transaction.component.html new file mode 100644 index 000000000..4873937bc --- /dev/null +++ b/frontend/src/app/explorer/transaction/transaction.component.html @@ -0,0 +1 @@ +

transaction works!

diff --git a/frontend/src/app/explorer/transaction/transaction.component.scss b/frontend/src/app/explorer/transaction/transaction.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/explorer/transaction/transaction.component.ts b/frontend/src/app/explorer/transaction/transaction.component.ts new file mode 100644 index 000000000..7d3999445 --- /dev/null +++ b/frontend/src/app/explorer/transaction/transaction.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-transaction', + templateUrl: './transaction.component.html', + styleUrls: ['./transaction.component.scss'] +}) +export class TransactionComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/frontend/src/app/master-page/master-page.component.html b/frontend/src/app/master-page/master-page.component.html index da882113d..f5c773a67 100644 --- a/frontend/src/app/master-page/master-page.component.html +++ b/frontend/src/app/master-page/master-page.component.html @@ -18,12 +18,15 @@ +
- +
diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 32baab713..4d58e33e2 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -162,4 +162,8 @@ export class ApiService { return this.httpClient.get(API_BASE_URL + '/statistics/6m'); } + listBlocks$(height?: number): Observable { + return this.httpClient.get(API_BASE_URL + '/explorer/blocks/' + (height || '')); + } + } diff --git a/frontend/src/app/shared/pipes/time-since/time-since.pipe.ts b/frontend/src/app/shared/pipes/time-since/time-since.pipe.ts new file mode 100644 index 000000000..ff56f4645 --- /dev/null +++ b/frontend/src/app/shared/pipes/time-since/time-since.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'timeSince' }) +export class TimeSincePipe implements PipeTransform { + transform(timestamp: number) { + const minutes = ((new Date().getTime()) - (new Date(timestamp * 1000).getTime())) / 1000 / 60; + if (minutes >= 120) { + return Math.floor(minutes / 60) + ' hours'; + } + if (minutes >= 60) { + return Math.floor(minutes / 60) + ' hour'; + } + if (minutes <= 1) { + return '< 1 minute'; + } + if (minutes === 1) { + return '1 minute'; + } + return Math.round(minutes) + ' minutes'; + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 492dde3a8..f83791ad4 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -7,6 +7,7 @@ import { VbytesPipe } from './pipes/bytes-pipe/vbytes.pipe'; import { RoundPipe } from './pipes/math-round-pipe/math-round.pipe'; import { CeilPipe } from './pipes/math-ceil/math-ceil.pipe'; import { ChartistComponent } from '../statistics/chartist.component'; +import { TimeSincePipe } from './pipes/time-since/time-since.pipe'; @NgModule({ imports: [ @@ -20,12 +21,14 @@ import { ChartistComponent } from '../statistics/chartist.component'; CeilPipe, BytesPipe, VbytesPipe, + TimeSincePipe, ], exports: [ RoundPipe, CeilPipe, BytesPipe, VbytesPipe, + TimeSincePipe, NgbButtonsModule, NgbModalModule, ChartistComponent,