Refactor top nodes widgets
This commit is contained in:
		
							parent
							
								
									7f48416dc3
								
							
						
					
					
						commit
						7520e3beba
					
				@ -2,6 +2,7 @@ import logger from '../../logger';
 | 
			
		||||
import DB from '../../database';
 | 
			
		||||
import { ResultSetHeader } from 'mysql2';
 | 
			
		||||
import { ILightningApi } from '../lightning/lightning-api.interface';
 | 
			
		||||
import { TopNodesPerCapacity, TopNodesPerChannels } from '../../mempool.interfaces';
 | 
			
		||||
 | 
			
		||||
class NodesApi {
 | 
			
		||||
  public async $getNode(public_key: string): Promise<any> {
 | 
			
		||||
@ -112,18 +113,19 @@ class NodesApi {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getTopCapacityNodes(): Promise<any> {
 | 
			
		||||
  public async $getTopCapacityNodes(): Promise<TopNodesPerCapacity[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      let [rows]: any[] = await DB.query('SELECT UNIX_TIMESTAMP(MAX(added)) as maxAdded FROM node_stats');
 | 
			
		||||
      const latestDate = rows[0].maxAdded;
 | 
			
		||||
 | 
			
		||||
      const query = `
 | 
			
		||||
        SELECT nodes.public_key, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias, node_stats.capacity, node_stats.channels
 | 
			
		||||
        SELECT nodes.public_key, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias,
 | 
			
		||||
          node_stats.capacity
 | 
			
		||||
        FROM node_stats
 | 
			
		||||
        JOIN nodes ON nodes.public_key = node_stats.public_key
 | 
			
		||||
        WHERE added = FROM_UNIXTIME(${latestDate})
 | 
			
		||||
        ORDER BY capacity DESC
 | 
			
		||||
        LIMIT 10;
 | 
			
		||||
        LIMIT 100;
 | 
			
		||||
      `;
 | 
			
		||||
      [rows] = await DB.query(query);
 | 
			
		||||
 | 
			
		||||
@ -134,18 +136,19 @@ class NodesApi {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getTopChannelsNodes(): Promise<any> {
 | 
			
		||||
  public async $getTopChannelsNodes(): Promise<TopNodesPerChannels[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      let [rows]: any[] = await DB.query('SELECT UNIX_TIMESTAMP(MAX(added)) as maxAdded FROM node_stats');
 | 
			
		||||
      const latestDate = rows[0].maxAdded;
 | 
			
		||||
 | 
			
		||||
      const query = `
 | 
			
		||||
        SELECT nodes.public_key, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias, node_stats.capacity, node_stats.channels
 | 
			
		||||
        SELECT nodes.public_key, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias,
 | 
			
		||||
          node_stats.channels
 | 
			
		||||
        FROM node_stats
 | 
			
		||||
        JOIN nodes ON nodes.public_key = node_stats.public_key
 | 
			
		||||
        WHERE added = FROM_UNIXTIME(${latestDate})
 | 
			
		||||
        ORDER BY channels DESC
 | 
			
		||||
        LIMIT 10;
 | 
			
		||||
        LIMIT 100;
 | 
			
		||||
      `;
 | 
			
		||||
      [rows] = await DB.query(query);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import config from '../../config';
 | 
			
		||||
import { Application, Request, Response } from 'express';
 | 
			
		||||
import nodesApi from './nodes.api';
 | 
			
		||||
import DB from '../../database';
 | 
			
		||||
import { INodesRanking } from '../../mempool.interfaces';
 | 
			
		||||
 | 
			
		||||
class NodesRoutes {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
@ -10,7 +11,7 @@ class NodesRoutes {
 | 
			
		||||
    app
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/country/:country', this.$getNodesPerCountry)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/search/:search', this.$searchNode)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/top', this.$getTopNodes)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings', this.$getNodesRanking)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp-ranking', this.$getISPRanking)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp/:isp', this.$getNodesPerISP)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/countries', this.$getNodesCountries)
 | 
			
		||||
@ -56,11 +57,11 @@ class NodesRoutes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getTopNodes(req: Request, res: Response) {
 | 
			
		||||
  private async $getNodesRanking(req: Request, res: Response): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const topCapacityNodes = await nodesApi.$getTopCapacityNodes();
 | 
			
		||||
      const topChannelsNodes = await nodesApi.$getTopChannelsNodes();
 | 
			
		||||
      res.json({
 | 
			
		||||
      res.json(<INodesRanking>{
 | 
			
		||||
        topByCapacity: topCapacityNodes,
 | 
			
		||||
        topByChannels: topChannelsNodes,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@ -251,3 +251,20 @@ export interface RewardStats {
 | 
			
		||||
  totalFee: number;
 | 
			
		||||
  totalTx: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TopNodesPerChannels {
 | 
			
		||||
  public_key: string,
 | 
			
		||||
  alias: string,
 | 
			
		||||
  channels: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TopNodesPerCapacity {
 | 
			
		||||
  public_key: string,
 | 
			
		||||
  alias: string,
 | 
			
		||||
  capacity: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface INodesRanking {
 | 
			
		||||
  topByCapacity: TopNodesPerCapacity[];
 | 
			
		||||
  topByChannels: TopNodesPerChannels[];
 | 
			
		||||
}
 | 
			
		||||
@ -151,3 +151,20 @@ export interface RewardStats {
 | 
			
		||||
  totalFee: number;
 | 
			
		||||
  totalTx: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ITopNodesPerChannels {
 | 
			
		||||
  public_key: string,
 | 
			
		||||
  alias: string,
 | 
			
		||||
  channels: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ITopNodesPerCapacity {
 | 
			
		||||
  public_key: string,
 | 
			
		||||
  alias: string,
 | 
			
		||||
  capacity: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface INodesRanking {
 | 
			
		||||
  topByCapacity: ITopNodesPerCapacity[];
 | 
			
		||||
  topByChannels: ITopNodesPerChannels[];
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
import { HttpClient, HttpParams } from '@angular/common/http';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
import { INodesRanking } from '../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
@ -48,8 +49,8 @@ export class LightningApiService {
 | 
			
		||||
    return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listTopNodes$(): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/top');
 | 
			
		||||
  getNodesRanking$(): Observable<INodesRanking> {
 | 
			
		||||
    return this.httpClient.get<INodesRanking>(this.apiBasePath + '/api/v1/lightning/nodes/rankings');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listChannelStats$(publicKey: string): Observable<any> {
 | 
			
		||||
 | 
			
		||||
@ -56,9 +56,13 @@
 | 
			
		||||
    <div class="col">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title">Top Capacity Nodes</h5>
 | 
			
		||||
          <app-nodes-list [nodes$]="nodesByCapacity$" [show]="'mobile-capacity'"></app-nodes-list>
 | 
			
		||||
          <!-- <div><a [routerLink]="['/lightning/nodes' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div> -->
 | 
			
		||||
          <a class="title-link" href="" [routerLink]="['/lightning/nodes/ranking' | relativeUrl]">
 | 
			
		||||
            <h5 class="card-title d-inline" i18n="lightning.top-capacity-nodes">Top Capacity Nodes</h5>
 | 
			
		||||
            <span> </span>
 | 
			
		||||
            <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
 | 
			
		||||
          </a>
 | 
			
		||||
          <h5 class="card-title"></h5>
 | 
			
		||||
          <app-top-nodes-per-capacity [nodes$]="nodesRanking$" [widget]="true"></app-top-nodes-per-capacity>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -66,9 +70,12 @@
 | 
			
		||||
    <div class="col">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title">Most Connected Nodes</h5>
 | 
			
		||||
          <app-nodes-list [nodes$]="nodesByChannels$" [show]="'mobile-channels'"></app-nodes-list>
 | 
			
		||||
          <!-- <div><a [routerLink]="['/lightning/nodes' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div> -->
 | 
			
		||||
          <a class="title-link" href="" [routerLink]="['/lightning/nodes/ranking' | relativeUrl]">
 | 
			
		||||
            <h5 class="card-title d-inline" i18n="lightning.top-channels-nodes">Most connected nodes</h5>
 | 
			
		||||
            <span> </span>
 | 
			
		||||
            <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
 | 
			
		||||
          </a>
 | 
			
		||||
          <app-top-nodes-per-channels [nodes$]="nodesRanking$" [widget]="true"></app-top-nodes-per-channels>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share } from 'rxjs/operators';
 | 
			
		||||
import { share } from 'rxjs/operators';
 | 
			
		||||
import { INodesRanking } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
 | 
			
		||||
@ -11,9 +12,8 @@ import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class LightningDashboardComponent implements OnInit {
 | 
			
		||||
  nodesByCapacity$: Observable<any>;
 | 
			
		||||
  nodesByChannels$: Observable<any>;
 | 
			
		||||
  statistics$: Observable<any>;
 | 
			
		||||
  nodesRanking$: Observable<INodesRanking>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private lightningApiService: LightningApiService,
 | 
			
		||||
@ -23,18 +23,7 @@ export class LightningDashboardComponent implements OnInit {
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.seoService.setTitle($localize`Lightning Dashboard`);
 | 
			
		||||
 | 
			
		||||
    const sharedObservable = this.lightningApiService.listTopNodes$().pipe(share());
 | 
			
		||||
 | 
			
		||||
    this.nodesByCapacity$ = sharedObservable
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((object) => object.topByCapacity),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    this.nodesByChannels$ = sharedObservable
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((object) => object.topByChannels),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
 | 
			
		||||
    this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@ import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component';
 | 
			
		||||
import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component';
 | 
			
		||||
import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
 | 
			
		||||
import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels-map.component';
 | 
			
		||||
import { TopNodesPerChannels } from '../lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component';
 | 
			
		||||
import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component';
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    LightningDashboardComponent,
 | 
			
		||||
@ -45,6 +47,8 @@ import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels
 | 
			
		||||
    NodesPerCountryChartComponent,
 | 
			
		||||
    NodesMap,
 | 
			
		||||
    NodesChannelsMap,
 | 
			
		||||
    TopNodesPerChannels,
 | 
			
		||||
    TopNodesPerCapacity,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -73,6 +77,8 @@ import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels
 | 
			
		||||
    NodesPerCountryChartComponent,
 | 
			
		||||
    NodesMap,
 | 
			
		||||
    NodesChannelsMap,
 | 
			
		||||
    TopNodesPerChannels,
 | 
			
		||||
    TopNodesPerCapacity,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    LightningApiService,
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { NodeComponent } from './node/node.component';
 | 
			
		||||
import { ChannelComponent } from './channel/channel.component';
 | 
			
		||||
import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component';
 | 
			
		||||
import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component';
 | 
			
		||||
import { NodesRanking } from './nodes-ranking/nodes-ranking.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
@ -32,6 +33,10 @@ const routes: Routes = [
 | 
			
		||||
          path: 'nodes/isp/:isp',
 | 
			
		||||
          component: NodesPerISP,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: 'nodes/ranking',
 | 
			
		||||
          component: NodesRanking,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: '**',
 | 
			
		||||
          redirectTo: ''
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
<div style="width: 100%">
 | 
			
		||||
 | 
			
		||||
  <table class="table table-borderless" style="width: 100%">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th class="" i18n="" style="width: 33%">Rank</th>
 | 
			
		||||
      <th class="" i18n="" style="width: 33%">Alias</th>
 | 
			
		||||
      <th class="" i18n="" style="width: 33%">Placeholder</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <ng-template #skeleton>
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
 | 
			
		||||
        <td class="">
 | 
			
		||||
          <span class="skeleton-loader"></span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="">
 | 
			
		||||
          <span class="skeleton-loader"></span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="">
 | 
			
		||||
          <span class="skeleton-loader"></span>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,15 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-nodes-ranking',
 | 
			
		||||
  templateUrl: './nodes-ranking.component.html',
 | 
			
		||||
  styleUrls: ['./nodes-ranking.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class NodesRanking implements OnInit {
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    console.log('hi');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<div>
 | 
			
		||||
  <table class="table table-borderless">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th class="rank"></th>
 | 
			
		||||
      <th class="alias text-left" i18n="nodes.alias">Alias</th>
 | 
			
		||||
      <th class="capacity text-right" i18n="node.capacity">Capacity</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody *ngIf="topNodesPerCapacity$ | async as nodes; else skeleton">
 | 
			
		||||
      <tr *ngFor="let node of nodes; let i = index;">
 | 
			
		||||
        <td class="rank text-left">
 | 
			
		||||
          {{ i + 1 }}
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="alias text-left">
 | 
			
		||||
          <a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.alias }}</a>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="capacity text-right">
 | 
			
		||||
          <app-amount [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
    <ng-template #skeleton>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr *ngFor="let item of skeletonRows">
 | 
			
		||||
          <td class="rank text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="alias text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="capacity text-right">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
.table td, .table th {
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rank {
 | 
			
		||||
  @media (min-width: 767.98px) {
 | 
			
		||||
    width: 13%;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    padding-left: 0px;
 | 
			
		||||
    padding-right: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alias {
 | 
			
		||||
  width: 55%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  max-width: 350px;
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    max-width: 175px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.capacity {
 | 
			
		||||
  width: 32%;
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    padding-left: 0px;
 | 
			
		||||
    padding-right: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { map, Observable } from 'rxjs';
 | 
			
		||||
import { INodesRanking, ITopNodesPerCapacity } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-top-nodes-per-capacity',
 | 
			
		||||
  templateUrl: './top-nodes-per-capacity.component.html',
 | 
			
		||||
  styleUrls: ['./top-nodes-per-capacity.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class TopNodesPerCapacity implements OnInit {
 | 
			
		||||
  @Input() nodes$: Observable<INodesRanking>;
 | 
			
		||||
  @Input() widget: boolean = false;
 | 
			
		||||
  
 | 
			
		||||
  topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>;
 | 
			
		||||
  skeletonRows: number[] = [];
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    for (let i = 1; i <= (this.widget ? 10 : 100); ++i) {
 | 
			
		||||
      this.skeletonRows.push(i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.topNodesPerCapacity$ = this.nodes$.pipe(
 | 
			
		||||
      map((ranking) => {
 | 
			
		||||
        if (this.widget === true) {
 | 
			
		||||
          return ranking.topByCapacity.slice(0, 10);
 | 
			
		||||
        } else {
 | 
			
		||||
          return ranking.topByCapacity;
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<div>
 | 
			
		||||
  <table class="table table-borderless">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th class="rank"></th>
 | 
			
		||||
      <th class="alias text-left" i18n="nodes.alias">Alias</th>
 | 
			
		||||
      <th class="channels text-right" i18n="node.channels">Channels</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody *ngIf="topNodesPerChannels$ | async as nodes; else skeleton">
 | 
			
		||||
      <tr *ngFor="let node of nodes; let i = index;">
 | 
			
		||||
        <td class="rank text-left">
 | 
			
		||||
          {{ i + 1 }}
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="alias text-left">
 | 
			
		||||
          <a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.alias }}</a>
 | 
			
		||||
        </td>
 | 
			
		||||
          <td class="channels text-right">
 | 
			
		||||
          {{ node.channels | number }}
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
    <ng-template #skeleton>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr *ngFor="let item of skeletonRows">
 | 
			
		||||
          <td class="rank text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="alias text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="channels text-right">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
.table td, .table th {
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rank {
 | 
			
		||||
  @media (min-width: 767.98px) {
 | 
			
		||||
    width: 13%;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    padding-left: 0px;
 | 
			
		||||
    padding-right: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alias {
 | 
			
		||||
  width: 60%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  max-width: 350px;
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    max-width: 175px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.channels {
 | 
			
		||||
  width: 27%;
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    padding-left: 0px;
 | 
			
		||||
    padding-right: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { map, Observable } from 'rxjs';
 | 
			
		||||
import { INodesRanking, ITopNodesPerChannels } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-top-nodes-per-channels',
 | 
			
		||||
  templateUrl: './top-nodes-per-channels.component.html',
 | 
			
		||||
  styleUrls: ['./top-nodes-per-channels.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class TopNodesPerChannels implements OnInit {
 | 
			
		||||
  @Input() nodes$: Observable<INodesRanking>;
 | 
			
		||||
  @Input() widget: boolean = false;
 | 
			
		||||
  
 | 
			
		||||
  topNodesPerChannels$: Observable<ITopNodesPerChannels[]>;
 | 
			
		||||
  skeletonRows: number[] = [];
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    for (let i = 1; i <= (this.widget ? 10 : 100); ++i) {
 | 
			
		||||
      this.skeletonRows.push(i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.topNodesPerChannels$ = this.nodes$.pipe(
 | 
			
		||||
      map((ranking) => {
 | 
			
		||||
        if (this.widget === true) {
 | 
			
		||||
          return ranking.topByChannels.slice(0, 10);
 | 
			
		||||
        } else {
 | 
			
		||||
          return ranking.topByChannels;
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user