Add mining pool preview
This commit is contained in:
		
							parent
							
								
									0694e71b14
								
							
						
					
					
						commit
						f7cd401e7a
					
				
							
								
								
									
										34
									
								
								frontend/src/app/components/pool/pool-preview.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/src/app/components/pool/pool-preview.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<div class="box preview-box" *ngIf="poolStats$ | async as poolStats">
 | 
			
		||||
  <app-preview-title>
 | 
			
		||||
    <span i18n="mining.pools">mining pool</span>
 | 
			
		||||
  </app-preview-title>
 | 
			
		||||
  <div class="row d-flex justify-content-between full-width-row">
 | 
			
		||||
    <div class="title-wrapper">
 | 
			
		||||
      <h1 class="title">{{ poolStats.pool.name }}</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="logo-wrapper">
 | 
			
		||||
      <img width="62" height="62" src="/resources/mining-pools/default.svg">
 | 
			
		||||
      <img [class.noimg]="!imageLoaded" width="62" height="62" src="{{ poolStats['logo'] }}"
 | 
			
		||||
        (load)="onImageLoad()" (error)="onImageFail()">
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row full-width-row">
 | 
			
		||||
      <div class="stats">
 | 
			
		||||
        <div class="stat-box">
 | 
			
		||||
          <div class="label" i18n="mining.tags">Tags</div>
 | 
			
		||||
          <div *ngIf="poolStats.pool.regexes.length else nodata" class="data">{{ poolStats.pool.regexes }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="stat-box">
 | 
			
		||||
          <div class="label" i18n="mining.hashrate">Hashrate</div>
 | 
			
		||||
          <div class="data">{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row hash-chart full-width-row">
 | 
			
		||||
    <div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #nodata>
 | 
			
		||||
  <div>~</div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
							
								
								
									
										78
									
								
								frontend/src/app/components/pool/pool-preview.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/src/app/components/pool/pool-preview.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
.stats {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: flex-start;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  margin: 15px 0;
 | 
			
		||||
  font-size: 32px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .stat-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    flex-wrap: nowrap;
 | 
			
		||||
    align-items: baseline;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin-left: 15px;
 | 
			
		||||
    background: #181b2d;
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
 | 
			
		||||
    &:first-child {
 | 
			
		||||
      margin-left: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .label {
 | 
			
		||||
      flex-shrink: 0;
 | 
			
		||||
      flex-grow: 0;
 | 
			
		||||
      margin-right: 1em;
 | 
			
		||||
    }
 | 
			
		||||
    .data {
 | 
			
		||||
      flex-shrink: 1;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 315px;
 | 
			
		||||
  background: #181b2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row {
 | 
			
		||||
  margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full-width-row {
 | 
			
		||||
  padding-left: 15px;
 | 
			
		||||
  flex-wrap: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo-wrapper {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 62px;
 | 
			
		||||
  height: 62px;
 | 
			
		||||
  margin-left: 1em;
 | 
			
		||||
 | 
			
		||||
  img {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    background: #24273e;
 | 
			
		||||
 | 
			
		||||
    &.noimg {
 | 
			
		||||
      opacity: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep .symbol {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								frontend/src/app/components/pool/pool-preview.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								frontend/src/app/components/pool/pool-preview.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { Observable, of } from 'rxjs';
 | 
			
		||||
import { map, switchMap, catchError } from 'rxjs/operators';
 | 
			
		||||
import { PoolStat } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-pool-preview',
 | 
			
		||||
  templateUrl: './pool-preview.component.html',
 | 
			
		||||
  styleUrls: ['./pool-preview.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush
 | 
			
		||||
})
 | 
			
		||||
export class PoolPreviewComponent implements OnInit {
 | 
			
		||||
  formatNumber = formatNumber;
 | 
			
		||||
  poolStats$: Observable<PoolStat>;
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  imageLoaded = false;
 | 
			
		||||
  lastImgSrc: string = '';
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
  chartInitOptions = {
 | 
			
		||||
    renderer: 'svg',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  slug: string = undefined;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(LOCALE_ID) public locale: string,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private openGraphService: OpenGraphService,
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.poolStats$ = this.route.params.pipe(map((params) => params.slug))
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((slug: any) => {
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          this.imageLoaded = false;
 | 
			
		||||
          this.slug = slug;
 | 
			
		||||
          this.openGraphService.waitFor('pool-hash-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-stats-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-chart-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-img-' + this.slug);
 | 
			
		||||
          return this.apiService.getPoolHashrate$(this.slug)
 | 
			
		||||
            .pipe(
 | 
			
		||||
              switchMap((data) => {
 | 
			
		||||
                this.isLoading = false;
 | 
			
		||||
                this.prepareChartOptions(data.map(val => [val.timestamp * 1000, val.avgHashrate]));
 | 
			
		||||
                this.openGraphService.waitOver('pool-hash-' + this.slug);
 | 
			
		||||
                return [slug];
 | 
			
		||||
              }),
 | 
			
		||||
              catchError(() => {
 | 
			
		||||
                this.isLoading = false;
 | 
			
		||||
                this.openGraphService.fail('pool-hash-' + this.slug);
 | 
			
		||||
                return of([slug]);
 | 
			
		||||
              })
 | 
			
		||||
            );
 | 
			
		||||
        }),
 | 
			
		||||
        switchMap((slug) => {
 | 
			
		||||
          return this.apiService.getPoolStats$(slug).pipe(
 | 
			
		||||
            catchError(() => {
 | 
			
		||||
              this.isLoading = false;
 | 
			
		||||
              this.openGraphService.fail('pool-stats-' + this.slug);
 | 
			
		||||
              return of(null);
 | 
			
		||||
            })
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((poolStats) => {
 | 
			
		||||
          if (poolStats == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.seoService.setTitle(poolStats.pool.name);
 | 
			
		||||
          let regexes = '"';
 | 
			
		||||
          for (const regex of poolStats.pool.regexes) {
 | 
			
		||||
            regexes += regex + '", "';
 | 
			
		||||
          }
 | 
			
		||||
          poolStats.pool.regexes = regexes.slice(0, -3);
 | 
			
		||||
          poolStats.pool.addresses = poolStats.pool.addresses;
 | 
			
		||||
 | 
			
		||||
          if (poolStats.reportedHashrate) {
 | 
			
		||||
            poolStats.luck = poolStats.estimatedHashrate / poolStats.reportedHashrate * 100;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.openGraphService.waitOver('pool-stats-' + this.slug);
 | 
			
		||||
 | 
			
		||||
          const logoSrc = `/resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
 | 
			
		||||
          if (logoSrc === this.lastImgSrc) {
 | 
			
		||||
            this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
          }
 | 
			
		||||
          this.lastImgSrc = logoSrc;
 | 
			
		||||
          return Object.assign({
 | 
			
		||||
            logo: logoSrc
 | 
			
		||||
          }, poolStats);
 | 
			
		||||
        }),
 | 
			
		||||
        catchError(() => {
 | 
			
		||||
          this.isLoading = false;
 | 
			
		||||
          this.openGraphService.fail('pool-stats-' + this.slug);
 | 
			
		||||
          return of(null);
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(data) {
 | 
			
		||||
    let title: object;
 | 
			
		||||
    if (data.length === 0) {
 | 
			
		||||
      title = {
 | 
			
		||||
        textStyle: {
 | 
			
		||||
          color: 'grey',
 | 
			
		||||
          fontSize: 15
 | 
			
		||||
        },
 | 
			
		||||
        text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`,
 | 
			
		||||
        left: 'center',
 | 
			
		||||
        top: 'center'
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      title: title,
 | 
			
		||||
      animation: false,
 | 
			
		||||
      color: [
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
			
		||||
          { offset: 0, color: '#F4511E' },
 | 
			
		||||
          { offset: 0.25, color: '#FB8C00' },
 | 
			
		||||
          { offset: 0.5, color: '#FFB300' },
 | 
			
		||||
          { offset: 0.75, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#7CB342' }
 | 
			
		||||
        ]),
 | 
			
		||||
        '#D81B60',
 | 
			
		||||
      ],
 | 
			
		||||
      grid: {
 | 
			
		||||
        left: 15,
 | 
			
		||||
        right: 15,
 | 
			
		||||
        bottom: 15,
 | 
			
		||||
        top: 15,
 | 
			
		||||
        show: false,
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: data.length === 0 ? undefined : {
 | 
			
		||||
        type: 'time',
 | 
			
		||||
        show: false,
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: data.length === 0 ? undefined : [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'value',
 | 
			
		||||
          show: false,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      series: data.length === 0 ? undefined : [
 | 
			
		||||
        {
 | 
			
		||||
          zlevel: 0,
 | 
			
		||||
          name: 'Hashrate',
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          symbol: 'none',
 | 
			
		||||
          data: data,
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 4,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChartReady(): void {
 | 
			
		||||
    this.openGraphService.waitOver('pool-chart-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onImageLoad(): void {
 | 
			
		||||
    this.imageLoaded = true;
 | 
			
		||||
    this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onImageFail(): void {
 | 
			
		||||
    this.imageLoaded = false;
 | 
			
		||||
    this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -7,20 +7,22 @@ import { PreviewsRoutingModule } from './previews.routing.module';
 | 
			
		||||
import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
			
		||||
import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
			
		||||
import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
			
		||||
import { PoolPreviewComponent } from './components/pool/pool-preview.component';
 | 
			
		||||
import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    TransactionPreviewComponent,
 | 
			
		||||
    BlockPreviewComponent,
 | 
			
		||||
    AddressPreviewComponent,
 | 
			
		||||
    PoolPreviewComponent,
 | 
			
		||||
    MasterPagePreviewComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    SharedModule,
 | 
			
		||||
    RouterModule,
 | 
			
		||||
    GraphsModule,
 | 
			
		||||
    PreviewsRoutingModule,
 | 
			
		||||
    GraphsModule,
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class PreviewsModule { }
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
			
		||||
import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
			
		||||
import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
			
		||||
import { PoolPreviewComponent } from './components/pool/pool-preview.component';
 | 
			
		||||
import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
@ -24,6 +25,10 @@ const routes: Routes = [
 | 
			
		||||
        children: [],
 | 
			
		||||
        component: TransactionPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'mining/pool/:slug',
 | 
			
		||||
        component: PoolPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'lightning',
 | 
			
		||||
        loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,16 @@ const routes = {
 | 
			
		||||
  },
 | 
			
		||||
  mining: {
 | 
			
		||||
    title: "Mining",
 | 
			
		||||
    fallbackImg: '/resources/previews/mining.png'
 | 
			
		||||
    fallbackImg: '/resources/previews/mining.png',
 | 
			
		||||
    routes: {
 | 
			
		||||
      pool: {
 | 
			
		||||
        render: true,
 | 
			
		||||
        params: 1,
 | 
			
		||||
        getTitle(path) {
 | 
			
		||||
          return `Mining Pool: ${path[0]}`;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user