Replacing footer and latest blocks with a stats dashboard.
This commit is contained in:
		
							parent
							
								
									8146939f0f
								
							
						
					
					
						commit
						6c1d28a9ac
					
				@ -7,6 +7,7 @@ import { Common } from './common';
 | 
			
		||||
class Blocks {
 | 
			
		||||
  private blocks: Block[] = [];
 | 
			
		||||
  private currentBlockHeight = 0;
 | 
			
		||||
  private lastDifficultyAdjustmentTime = 0;
 | 
			
		||||
  private newBlockCallback: ((block: Block, txIds: string[], transactions: TransactionExtended[]) => void) | undefined;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
@ -38,6 +39,13 @@ class Blocks {
 | 
			
		||||
        this.currentBlockHeight = blockHeightTip - config.INITIAL_BLOCK_AMOUNT;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this.lastDifficultyAdjustmentTime) {
 | 
			
		||||
        const heightDiff = blockHeightTip % 2016;
 | 
			
		||||
        const blockHash = await bitcoinApi.getBlockHash(blockHeightTip - heightDiff);
 | 
			
		||||
        const block = await bitcoinApi.getBlock(blockHash);
 | 
			
		||||
        this.lastDifficultyAdjustmentTime = block.timestamp;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      while (this.currentBlockHeight < blockHeightTip) {
 | 
			
		||||
        if (this.currentBlockHeight === 0) {
 | 
			
		||||
          this.currentBlockHeight = blockHeightTip;
 | 
			
		||||
@ -78,6 +86,10 @@ class Blocks {
 | 
			
		||||
        block.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
 | 
			
		||||
        block.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions.slice(0, transactions.length - 1), 8) : [0, 0];
 | 
			
		||||
 | 
			
		||||
        if (block.height % 2016 === 0) {
 | 
			
		||||
          this.lastDifficultyAdjustmentTime = block.timestamp;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.blocks.push(block);
 | 
			
		||||
        if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {
 | 
			
		||||
          this.blocks.shift();
 | 
			
		||||
@ -93,6 +105,10 @@ class Blocks {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getLastDifficultyAdjustmentTime(): number {
 | 
			
		||||
    return this.lastDifficultyAdjustmentTime;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
 | 
			
		||||
    return {
 | 
			
		||||
      vin: [{
 | 
			
		||||
 | 
			
		||||
@ -84,6 +84,7 @@ class WebsocketHandler {
 | 
			
		||||
            client.send(JSON.stringify({
 | 
			
		||||
              'mempoolInfo': memPool.getMempoolInfo(),
 | 
			
		||||
              'vBytesPerSecond': memPool.getVBytesPerSecond(),
 | 
			
		||||
              'lastDifficultyAdjustment': blocks.getLastDifficultyAdjustmentTime(),
 | 
			
		||||
              'blocks': _blocks.slice(Math.max(_blocks.length - config.INITIAL_BLOCK_AMOUNT, 0)),
 | 
			
		||||
              'conversions': fiatConversion.getTickers()['BTCUSD'],
 | 
			
		||||
              'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
 | 
			
		||||
@ -270,6 +271,7 @@ class WebsocketHandler {
 | 
			
		||||
      const response = {
 | 
			
		||||
        'block': block,
 | 
			
		||||
        'mempoolInfo': memPool.getMempoolInfo(),
 | 
			
		||||
        'lastDifficultyAdjustment': blocks.getLastDifficultyAdjustmentTime(),
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (mBlocks && client['want-mempool-blocks']) {
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,10 @@ import { AboutComponent } from './components/about/about.component';
 | 
			
		||||
import { TelevisionComponent } from './components/television/television.component';
 | 
			
		||||
import { StatisticsComponent } from './components/statistics/statistics.component';
 | 
			
		||||
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
 | 
			
		||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
 | 
			
		||||
import { AssetComponent } from './components/asset/asset.component';
 | 
			
		||||
import { AssetsComponent } from './assets/assets.component';
 | 
			
		||||
import { StatusViewComponent } from './components/status-view/status-view.component';
 | 
			
		||||
import { DashboardComponent } from './dashboard/dashboard.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
  {
 | 
			
		||||
@ -25,7 +25,7 @@ const routes: Routes = [
 | 
			
		||||
        children: [
 | 
			
		||||
          {
 | 
			
		||||
            path: '',
 | 
			
		||||
            component: LatestBlocksComponent
 | 
			
		||||
            component: DashboardComponent,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'tx/:id',
 | 
			
		||||
@ -69,7 +69,7 @@ const routes: Routes = [
 | 
			
		||||
            children: [
 | 
			
		||||
              {
 | 
			
		||||
                path: '',
 | 
			
		||||
                component: LatestBlocksComponent
 | 
			
		||||
                component: DashboardComponent
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                path: 'tx/:id',
 | 
			
		||||
@ -134,7 +134,7 @@ const routes: Routes = [
 | 
			
		||||
            children: [
 | 
			
		||||
              {
 | 
			
		||||
                path: '',
 | 
			
		||||
                component: LatestBlocksComponent
 | 
			
		||||
                component: DashboardComponent
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                path: 'tx/:id',
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@ import { StateService } from './services/state.service';
 | 
			
		||||
import { BlockComponent } from './components/block/block.component';
 | 
			
		||||
import { AddressComponent } from './components/address/address.component';
 | 
			
		||||
import { SearchFormComponent } from './components/search-form/search-form.component';
 | 
			
		||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
 | 
			
		||||
import { WebsocketService } from './services/websocket.service';
 | 
			
		||||
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
 | 
			
		||||
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
 | 
			
		||||
@ -41,6 +40,7 @@ import { MinerComponent } from './components/miner/miner.component';
 | 
			
		||||
import { SharedModule } from './shared/shared.module';
 | 
			
		||||
import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
 | 
			
		||||
import { DashboardComponent } from './dashboard/dashboard.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -58,7 +58,6 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
 | 
			
		||||
    AddressComponent,
 | 
			
		||||
    AmountComponent,
 | 
			
		||||
    SearchFormComponent,
 | 
			
		||||
    LatestBlocksComponent,
 | 
			
		||||
    TimespanComponent,
 | 
			
		||||
    AddressLabelsComponent,
 | 
			
		||||
    MempoolBlocksComponent,
 | 
			
		||||
@ -72,6 +71,7 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
 | 
			
		||||
    MinerComponent,
 | 
			
		||||
    StatusViewComponent,
 | 
			
		||||
    FeesBoxComponent,
 | 
			
		||||
    DashboardComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ export class AddressComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
			
		||||
    this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
 | 
			
		||||
    this.websocketService.want(['blocks', 'mempool-blocks']);
 | 
			
		||||
 | 
			
		||||
    this.mainSubscription = this.route.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ export class AssetComponent implements OnInit, OnDestroy {
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
 | 
			
		||||
    this.websocketService.want(['blocks', 'mempool-blocks']);
 | 
			
		||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
			
		||||
 | 
			
		||||
    this.mainSubscription = this.route.paramMap
 | 
			
		||||
 | 
			
		||||
@ -1,39 +0,0 @@
 | 
			
		||||
<app-fees-box *ngIf="(network$ | async) === ''" class="d-block mr-2 ml-2 mb-4"></app-fees-box>
 | 
			
		||||
 | 
			
		||||
<div class="container-xl">
 | 
			
		||||
<hr>
 | 
			
		||||
 | 
			
		||||
<table class="table table-borderless" [alwaysCallback]="true" [fromRoot]="true" [infiniteScrollContainer]="'body'" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
 | 
			
		||||
  <thead>
 | 
			
		||||
    <th style="width: 15%;">Height</th>
 | 
			
		||||
    <th class="d-none d-md-block" style="width: 20%;">Timestamp</th>
 | 
			
		||||
    <th style="width: 20%;">Mined</th>
 | 
			
		||||
    <th class="d-none d-lg-block" style="width: 15%;">Transactions</th>
 | 
			
		||||
    <th style="width: 20%;">Filled</th>
 | 
			
		||||
  </thead>
 | 
			
		||||
  <tbody>
 | 
			
		||||
    <tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
 | 
			
		||||
      <td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
 | 
			
		||||
      <td class="d-none d-md-block">{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
			
		||||
      <td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
 | 
			
		||||
      <td class="d-none d-lg-block">{{ block.tx_count | number }}</td>
 | 
			
		||||
      <td>
 | 
			
		||||
        <div class="progress position-relative">
 | 
			
		||||
          <div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / 4000000)*100 + '%' }"></div>
 | 
			
		||||
          <div class="progress-text">{{ block.size | bytes: 2 }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <ng-template [ngIf]="isLoading">
 | 
			
		||||
      <tr *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td class="d-none d-lg-block"><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
.progress {
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 768px) {
 | 
			
		||||
  .d-md-block {
 | 
			
		||||
      display: table-cell !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@media (min-width: 992px) {
 | 
			
		||||
  .d-lg-block {
 | 
			
		||||
      display: table-cell !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { LatestBlocksComponent } from './latest-blocks.component';
 | 
			
		||||
 | 
			
		||||
describe('LatestBlocksComponent', () => {
 | 
			
		||||
  let component: LatestBlocksComponent;
 | 
			
		||||
  let fixture: ComponentFixture<LatestBlocksComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ LatestBlocksComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(LatestBlocksComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -1,111 +0,0 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Block } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { Subscription, Observable, merge, of } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-latest-blocks',
 | 
			
		||||
  templateUrl: './latest-blocks.component.html',
 | 
			
		||||
  styleUrls: ['./latest-blocks.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush
 | 
			
		||||
})
 | 
			
		||||
export class LatestBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
  network$: Observable<string>;
 | 
			
		||||
 | 
			
		||||
  blocks: any[] = [];
 | 
			
		||||
  blockSubscription: Subscription;
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  interval: any;
 | 
			
		||||
 | 
			
		||||
  latestBlockHeight: number;
 | 
			
		||||
 | 
			
		||||
  heightOfPageUntilBlocks = 430;
 | 
			
		||||
  heightOfBlocksTableChunk = 470;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private electrsApiService: ElectrsApiService,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private cd: ChangeDetectorRef,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.seoService.resetTitle();
 | 
			
		||||
    this.network$ = merge(of(''), this.stateService.networkChanged$);
 | 
			
		||||
 | 
			
		||||
    this.blockSubscription = this.stateService.blocks$
 | 
			
		||||
      .subscribe(([block]) => {
 | 
			
		||||
        if (block === null || !this.blocks.length) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.latestBlockHeight = block.height;
 | 
			
		||||
 | 
			
		||||
        if (block.height === this.blocks[0].height) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we are out of sync, reload the blocks instead
 | 
			
		||||
        if (block.height > this.blocks[0].height + 1) {
 | 
			
		||||
          this.loadInitialBlocks();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (block.height <= this.blocks[0].height) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.blocks.pop();
 | 
			
		||||
        this.blocks.unshift(block);
 | 
			
		||||
        this.cd.markForCheck();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    this.loadInitialBlocks();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    clearInterval(this.interval);
 | 
			
		||||
    this.blockSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadInitialBlocks() {
 | 
			
		||||
    this.electrsApiService.listBlocks$()
 | 
			
		||||
      .subscribe((blocks) => {
 | 
			
		||||
        this.blocks = blocks;
 | 
			
		||||
        this.isLoading = false;
 | 
			
		||||
 | 
			
		||||
        this.latestBlockHeight = blocks[0].height;
 | 
			
		||||
 | 
			
		||||
        const spaceForBlocks = window.innerHeight - this.heightOfPageUntilBlocks;
 | 
			
		||||
        const chunks = Math.ceil(spaceForBlocks / this.heightOfBlocksTableChunk) - 1;
 | 
			
		||||
        if (chunks > 0) {
 | 
			
		||||
          this.loadMore(chunks);
 | 
			
		||||
        }
 | 
			
		||||
        this.cd.markForCheck();
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadMore(chunks = 0) {
 | 
			
		||||
    if (this.isLoading) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.isLoading = true;
 | 
			
		||||
    this.electrsApiService.listBlocks$(this.blocks[this.blocks.length - 1].height - 1)
 | 
			
		||||
      .subscribe((blocks) => {
 | 
			
		||||
        this.blocks = this.blocks.concat(blocks);
 | 
			
		||||
        this.isLoading = false;
 | 
			
		||||
 | 
			
		||||
        const chunksLeft = chunks - 1;
 | 
			
		||||
        if (chunksLeft > 0) {
 | 
			
		||||
          this.loadMore(chunksLeft);
 | 
			
		||||
        }
 | 
			
		||||
        this.cd.markForCheck();
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  trackByBlock(index: number, block: Block) {
 | 
			
		||||
    return block.height;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -66,10 +66,4 @@
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
 | 
			
		||||
<ng-container *ngIf="network.val !== 'bisq'">
 | 
			
		||||
  <br><br>
 | 
			
		||||
 | 
			
		||||
  <app-footer></app-footer>
 | 
			
		||||
</ng-container>
 | 
			
		||||
 | 
			
		||||
</ng-container>
 | 
			
		||||
@ -108,10 +108,10 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
      switchMap(() => {
 | 
			
		||||
        this.spinnerLoading = true;
 | 
			
		||||
        if (this.radioGroupForm.controls.dateSpan.value === '2h') {
 | 
			
		||||
          this.websocketService.want(['blocks', 'stats', 'live-2h-chart']);
 | 
			
		||||
          this.websocketService.want(['blocks', 'live-2h-chart']);
 | 
			
		||||
          return this.apiService.list2HStatistics$();
 | 
			
		||||
        }
 | 
			
		||||
        this.websocketService.want(['blocks',  'stats']);
 | 
			
		||||
        this.websocketService.want(['blocks']);
 | 
			
		||||
        if (this.radioGroupForm.controls.dateSpan.value === '24h') {
 | 
			
		||||
          return this.apiService.list24HStatistics$();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								frontend/src/app/dashboard/dashboard.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/src/app/dashboard/dashboard.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
<app-fees-box *ngIf="(network$ | async) === ''" class="d-block mr-2 ml-2 mb-5"></app-fees-box>
 | 
			
		||||
 | 
			
		||||
<div class="container-xl">
 | 
			
		||||
 | 
			
		||||
  <div class="row row-cols-1 row-cols-md-2" *ngIf="mempoolInfoData$ | async as mempoolInfoData">
 | 
			
		||||
    <div class="col mb-4">
 | 
			
		||||
      <div class="card text-center">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title mempoolSize">Mempool size</h5>
 | 
			
		||||
          <p class="card-text" *ngIf="(mempoolBlocksData$ | async) as mempoolBlocksData">{{ mempoolBlocksData.size | bytes }} ({{ mempoolBlocksData.blocks }} block<span [hidden]="mempoolBlocksData.blocks <= 1">s</span>)</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col mb-4">
 | 
			
		||||
      <div class="card text-center">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title unconfirmedTx">Unconfirmed transactions</h5>
 | 
			
		||||
          <p class="card-text">{{ mempoolInfoData.memPoolInfo.size | number }}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col mb-4">
 | 
			
		||||
      <div class="card text-center">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title txWeightPerSecond">Tx weight per second</h5>
 | 
			
		||||
            <span *ngIf="mempoolInfoData.vBytesPerSecond === 0; else inSync">
 | 
			
		||||
               <span class="badge badge-pill badge-warning">Backend is synchronizing</span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <ng-template #inSync>
 | 
			
		||||
              <div class="progress sub-text">
 | 
			
		||||
                <div class="progress-bar {{ mempoolInfoData.progressClass }}" style="padding: 4px;" role="progressbar" [ngStyle]="{'width': mempoolInfoData.progressWidth}">{{ mempoolInfoData.vBytesPerSecond | ceil | number }} vB/s</div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col mb-4">
 | 
			
		||||
      <div class="card text-center">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title txPerSecond">Difficulty Epoch</h5>
 | 
			
		||||
          <div class="progress" *ngIf="(difficultyEpoch$ | async) as epochData">
 | 
			
		||||
            <div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"></div>
 | 
			
		||||
            <div class="progress-bar bg-success" role="progressbar" style="width: 0%" [ngStyle]="{'width': epochData.green}"></div>
 | 
			
		||||
            <div class="progress-bar bg-danger" role="progressbar" style="width: 1%; background-color: #f14d80;" [ngStyle]="{'width': epochData.red}"></div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										36
									
								
								frontend/src/app/dashboard/dashboard.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								frontend/src/app/dashboard/dashboard.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
.card {
 | 
			
		||||
  background-color: #1d1f31;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.txWeightPerSecond {
 | 
			
		||||
  color: #4a9ff4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mempoolSize {
 | 
			
		||||
  color: #4a68b9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.txPerSecond {
 | 
			
		||||
  color: #f4bb4a;;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.unconfirmedTx {
 | 
			
		||||
  color: #f14d80;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-block {
 | 
			
		||||
  float: left;
 | 
			
		||||
  width: 350px;
 | 
			
		||||
  line-height: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress {
 | 
			
		||||
  display: inline-flex;
 | 
			
		||||
  width: 250px;
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
  height: 1.1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bg-warning {
 | 
			
		||||
  background-color: #b58800 !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								frontend/src/app/dashboard/dashboard.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								frontend/src/app/dashboard/dashboard.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { combineLatest, merge, Observable, of } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { MempoolInfo } from '../interfaces/websocket.interface';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
 | 
			
		||||
interface MempoolBlocksData {
 | 
			
		||||
  blocks: number;
 | 
			
		||||
  size: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface EpochProgress {
 | 
			
		||||
  base: string;
 | 
			
		||||
  green: string;
 | 
			
		||||
  red: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface MempoolInfoData {
 | 
			
		||||
  memPoolInfo: MempoolInfo;
 | 
			
		||||
  vBytesPerSecond: number;
 | 
			
		||||
  progressWidth: string;
 | 
			
		||||
  progressClass: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-dashboard',
 | 
			
		||||
  templateUrl: './dashboard.component.html',
 | 
			
		||||
  styleUrls: ['./dashboard.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush
 | 
			
		||||
})
 | 
			
		||||
export class DashboardComponent implements OnInit {
 | 
			
		||||
  network$: Observable<string>;
 | 
			
		||||
  mempoolBlocksData$: Observable<MempoolBlocksData>;
 | 
			
		||||
  latestBlockHeight$: Observable<number>;
 | 
			
		||||
  mempoolInfoData$: Observable<MempoolInfoData>;
 | 
			
		||||
  difficultyEpoch$: Observable<EpochProgress>;
 | 
			
		||||
  vBytesPerSecondLimit = 1667;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.network$ = merge(of(''), this.stateService.networkChanged$);
 | 
			
		||||
 | 
			
		||||
    this.mempoolInfoData$ = combineLatest([
 | 
			
		||||
      this.stateService.mempoolInfo$,
 | 
			
		||||
      this.stateService.vbytesPerSecond$
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      map(([mempoolInfo, vbytesPerSecond]) => {
 | 
			
		||||
        const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
 | 
			
		||||
 | 
			
		||||
        let progressClass = 'bg-danger';
 | 
			
		||||
        if (percent <= 75) {
 | 
			
		||||
          progressClass = 'bg-success';
 | 
			
		||||
        } else if (percent <= 99) {
 | 
			
		||||
          progressClass = 'bg-warning';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          memPoolInfo: mempoolInfo,
 | 
			
		||||
          vBytesPerSecond: vbytesPerSecond,
 | 
			
		||||
          progressWidth: percent + '%',
 | 
			
		||||
          progressClass: progressClass,
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.difficultyEpoch$ = combineLatest([
 | 
			
		||||
      this.stateService.blocks$.pipe(map(([block]) => block)),
 | 
			
		||||
      this.stateService.lastDifficultyAdjustment$
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      map(([block, DATime]) => {
 | 
			
		||||
        const now = new Date().getTime() / 1000;
 | 
			
		||||
        const diff = now - DATime;
 | 
			
		||||
        const blocksInEpoch = block.height % 2016;
 | 
			
		||||
        const estimatedBlocks = Math.round(diff / 60 / 10);
 | 
			
		||||
 | 
			
		||||
        let base = 0;
 | 
			
		||||
        let green = 0;
 | 
			
		||||
        let red = 0;
 | 
			
		||||
 | 
			
		||||
        if (blocksInEpoch >= estimatedBlocks) {
 | 
			
		||||
          base = estimatedBlocks / 2016 * 100;
 | 
			
		||||
          green = (blocksInEpoch - estimatedBlocks) / 2016 * 100;
 | 
			
		||||
        } else {
 | 
			
		||||
          base = blocksInEpoch / 2016 * 100;
 | 
			
		||||
          red = (estimatedBlocks - blocksInEpoch) / 2016 * 100;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          base: base + '%',
 | 
			
		||||
          green: green + '%',
 | 
			
		||||
          red: red + '%',
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((mempoolBlocks) => {
 | 
			
		||||
          const size = mempoolBlocks.map((m) => m.blockSize).reduce((a, b) => a + b, 0);
 | 
			
		||||
          const vsize = mempoolBlocks.map((m) => m.blockVSize).reduce((a, b) => a + b, 0);
 | 
			
		||||
 | 
			
		||||
          return {
 | 
			
		||||
            size: size,
 | 
			
		||||
            blocks: Math.ceil(vsize / 1000000)
 | 
			
		||||
          };
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -8,6 +8,7 @@ export interface WebsocketResponse {
 | 
			
		||||
  historicalDate?: string;
 | 
			
		||||
  mempoolInfo?: MempoolInfo;
 | 
			
		||||
  vBytesPerSecond?: number;
 | 
			
		||||
  lastDifficultyAdjustment?: number;
 | 
			
		||||
  action?: string;
 | 
			
		||||
  data?: string[];
 | 
			
		||||
  tx?: Transaction;
 | 
			
		||||
@ -31,8 +32,4 @@ export interface MempoolBlock {
 | 
			
		||||
export interface MempoolInfo {
 | 
			
		||||
  size: number;
 | 
			
		||||
  bytes: number;
 | 
			
		||||
  usage?: number;
 | 
			
		||||
  maxmempool?: number;
 | 
			
		||||
  mempoolminfee?: number;
 | 
			
		||||
  minrelaytxfee?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ export class StateService {
 | 
			
		||||
  blockTransactions$ = new Subject<Transaction>();
 | 
			
		||||
  isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
 | 
			
		||||
  vbytesPerSecond$ = new ReplaySubject<number>(1);
 | 
			
		||||
  lastDifficultyAdjustment$ = new ReplaySubject<number>(1);
 | 
			
		||||
  gitCommit$ = new ReplaySubject<string>(1);
 | 
			
		||||
 | 
			
		||||
  live2Chart$ = new Subject<OptimizedMempoolStats>();
 | 
			
		||||
 | 
			
		||||
@ -141,6 +141,10 @@ export class WebsocketService {
 | 
			
		||||
          this.stateService.vbytesPerSecond$.next(response.vBytesPerSecond);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (response.lastDifficultyAdjustment !== undefined) {
 | 
			
		||||
          this.stateService.lastDifficultyAdjustment$.next(response.lastDifficultyAdjustment);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (response['git-commit']) {
 | 
			
		||||
          this.stateService.gitCommit$.next(response['git-commit']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user