From 7520e3beba9442ec6cbb6853507fc11f4b538eb5 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 17 Aug 2022 12:53:26 +0200 Subject: [PATCH 1/3] Refactor top nodes widgets --- backend/src/api/explorer/nodes.api.ts | 15 +++++--- backend/src/api/explorer/nodes.routes.ts | 7 ++-- backend/src/mempool.interfaces.ts | 17 +++++++++ .../src/app/interfaces/node-api.interface.ts | 17 +++++++++ .../app/lightning/lightning-api.service.ts | 5 ++- .../lightning-dashboard.component.html | 19 +++++++--- .../lightning-dashboard.component.ts | 19 ++-------- .../src/app/lightning/lightning.module.ts | 6 +++ .../app/lightning/lightning.routing.module.ts | 5 +++ .../nodes-ranking.component.html | 29 +++++++++++++++ .../nodes-ranking.component.scss | 0 .../nodes-ranking/nodes-ranking.component.ts | 15 ++++++++ .../top-nodes-per-capacity.component.html | 37 +++++++++++++++++++ .../top-nodes-per-capacity.component.scss | 30 +++++++++++++++ .../top-nodes-per-capacity.component.ts | 34 +++++++++++++++++ .../top-nodes-per-channels.component.html | 37 +++++++++++++++++++ .../top-nodes-per-channels.component.scss | 30 +++++++++++++++ .../top-nodes-per-channels.component.ts | 34 +++++++++++++++++ 18 files changed, 324 insertions(+), 32 deletions(-) create mode 100644 frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html create mode 100644 frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.scss create mode 100644 frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss create mode 100644 frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 2d838524e..33de5ae8e 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -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 { @@ -112,18 +113,19 @@ class NodesApi { } } - public async $getTopCapacityNodes(): Promise { + public async $getTopCapacityNodes(): Promise { 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 { + public async $getTopChannelsNodes(): Promise { 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); diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 5e0f95acb..e0b145bf1 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -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 { try { const topCapacityNodes = await nodesApi.$getTopCapacityNodes(); const topChannelsNodes = await nodesApi.$getTopChannelsNodes(); - res.json({ + res.json({ topByCapacity: topCapacityNodes, topByChannels: topChannelsNodes, }); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index c2d2ee747..5c6abf276 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -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[]; +} \ No newline at end of file diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 5f3506db5..905cd37a2 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -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[]; +} \ No newline at end of file diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 2a6634558..294a2fb60 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -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(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics'); } - listTopNodes$(): Observable { - return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/top'); + getNodesRanking$(): Observable { + return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/rankings'); } listChannelStats$(publicKey: string): Observable { diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html index ff00f5b15..e2e2731d3 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html @@ -56,9 +56,13 @@
-
Top Capacity Nodes
- - + +
Top Capacity Nodes
+   + +
+
+
@@ -66,9 +70,12 @@
-
Most Connected Nodes
- - + +
Most connected nodes
+   + +
+
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index 5d4685fb8..d601606fd 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -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; - nodesByChannels$: Observable; statistics$: Observable; + nodesRanking$: Observable; 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()); } diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index c01792815..85961edc2 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -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, diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts index 8bfb467af..cae5c1380 100644 --- a/frontend/src/app/lightning/lightning.routing.module.ts +++ b/frontend/src/app/lightning/lightning.routing.module.ts @@ -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: '' diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html new file mode 100644 index 000000000..b0b01d16a --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html @@ -0,0 +1,29 @@ +
+ + + + + + + + + +
RankAliasPlaceholder
+ + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.scss b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts new file mode 100644 index 000000000..b16dc3ce4 --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts @@ -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'); + } + +} diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html new file mode 100644 index 000000000..bb1864ba1 --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -0,0 +1,37 @@ +
+ + + + + + + + + + + + + + + + + + + + + + +
AliasCapacity
+ {{ i + 1 }} + + {{ node.alias }} + + +
+ + + + + +
+
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss new file mode 100644 index 000000000..4662b72ff --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss @@ -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; + } +} diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts new file mode 100644 index 000000000..b3c9171c0 --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -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; + @Input() widget: boolean = false; + + topNodesPerCapacity$: Observable; + 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; + } + }) + ) + } + +} diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html new file mode 100644 index 000000000..f581dacff --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html @@ -0,0 +1,37 @@ +
+ + + + + + + + + + + + + + + + + + + + + + +
AliasChannels
+ {{ i + 1 }} + + {{ node.alias }} + + {{ node.channels | number }} +
+ + + + + +
+
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss new file mode 100644 index 000000000..5335991e3 --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss @@ -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; + } +} diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts new file mode 100644 index 000000000..46cf21eaa --- /dev/null +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts @@ -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; + @Input() widget: boolean = false; + + topNodesPerChannels$: Observable; + 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; + } + }) + ) + } + +} From 2359e44b1606b2a2ea2f870fab9fa9d60c3faf29 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 17 Aug 2022 16:00:30 +0200 Subject: [PATCH 2/3] Create top 100 node per capacity component --- backend/src/api/explorer/nodes.api.ts | 55 +++++++++---- backend/src/api/explorer/nodes.routes.ts | 20 ++++- backend/src/mempool.interfaces.ts | 7 +- .../src/app/interfaces/node-api.interface.ts | 7 +- .../app/lightning/lightning-api.service.ts | 8 +- .../lightning-dashboard.component.html | 6 +- .../src/app/lightning/lightning.module.ts | 3 + .../app/lightning/lightning.routing.module.ts | 5 +- .../nodes-ranking.component.html | 31 +------- .../nodes-ranking/nodes-ranking.component.ts | 9 ++- .../top-nodes-per-capacity.component.html | 78 ++++++++++++------- .../top-nodes-per-capacity.component.scss | 60 +++++++++++++- .../top-nodes-per-capacity.component.ts | 21 +++-- 13 files changed, 216 insertions(+), 94 deletions(-) diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 33de5ae8e..c7b98f3a8 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -10,10 +10,10 @@ class NodesApi { // General info let query = ` SELECT public_key, alias, UNIX_TIMESTAMP(first_seen) AS first_seen, - UNIX_TIMESTAMP(updated_at) AS updated_at, color, sockets as sockets, - as_number, city_id, country_id, subdivision_id, longitude, latitude, - geo_names_iso.names as iso_code, geo_names_as.names as as_organization, geo_names_city.names as city, - geo_names_country.names as country, geo_names_subdivision.names as subdivision + UNIX_TIMESTAMP(updated_at) AS updated_at, color, sockets as sockets, + as_number, city_id, country_id, subdivision_id, longitude, latitude, + geo_names_iso.names as iso_code, geo_names_as.names as as_organization, geo_names_city.names as city, + geo_names_country.names as country, geo_names_subdivision.names as subdivision FROM nodes LEFT JOIN geo_names geo_names_as on geo_names_as.id = as_number LEFT JOIN geo_names geo_names_city on geo_names_city.id = city_id @@ -113,21 +113,46 @@ class NodesApi { } } - public async $getTopCapacityNodes(): Promise { + public async $getTopCapacityNodes(full: boolean): Promise { 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 - FROM node_stats - JOIN nodes ON nodes.public_key = node_stats.public_key - WHERE added = FROM_UNIXTIME(${latestDate}) - ORDER BY capacity DESC - LIMIT 100; - `; - [rows] = await DB.query(query); + let query: string; + if (full === false) { + query = ` + SELECT nodes.public_key AS publicKey, 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 100 + `; + + [rows] = await DB.query(query); + } else { + query = ` + SELECT node_stats.public_key AS publicKey, IF(nodes.alias = '', SUBSTRING(node_stats.public_key, 1, 20), alias) as alias, + CAST(COALESCE(node_stats.capacity, 0) as INT) as capacity, + CAST(COALESCE(node_stats.channels, 0) as INT) as channels, + UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt, + geo_names_city.names as city, geo_names_country.names as country + FROM node_stats + RIGHT JOIN nodes ON nodes.public_key = node_stats.public_key + LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' + LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' + WHERE added = FROM_UNIXTIME(${latestDate}) + ORDER BY capacity DESC + LIMIT 100 + `; + + [rows] = await DB.query(query); + for (let i = 0; i < rows.length; ++i) { + rows[i].country = JSON.parse(rows[i].country); + rows[i].city = JSON.parse(rows[i].city); + } + } return rows; } catch (e) { diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index e0b145bf1..a490d946b 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -11,10 +11,11 @@ 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/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) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings', this.$getNodesRanking) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/capacity', this.$getTopNodesByCapacity) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) ; @@ -59,8 +60,11 @@ class NodesRoutes { private async $getNodesRanking(req: Request, res: Response): Promise { try { - const topCapacityNodes = await nodesApi.$getTopCapacityNodes(); + const topCapacityNodes = await nodesApi.$getTopCapacityNodes(false); const topChannelsNodes = await nodesApi.$getTopChannelsNodes(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json({ topByCapacity: topCapacityNodes, topByChannels: topChannelsNodes, @@ -70,6 +74,18 @@ class NodesRoutes { } } + private async $getTopNodesByCapacity(req: Request, res: Response): Promise { + try { + const topCapacityNodes = await nodesApi.$getTopCapacityNodes(true); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(topCapacityNodes); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getISPRanking(req: Request, res: Response): Promise { try { const groupBy = req.query.groupBy as string; diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 5c6abf276..1067f9b6d 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -259,9 +259,14 @@ export interface TopNodesPerChannels { } export interface TopNodesPerCapacity { - public_key: string, + publicKey: string, alias: string, capacity: number, + channels?: number, + firstSeen?: number, + updatedAt?: number, + city?: any, + country?: any, } export interface INodesRanking { diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 905cd37a2..4238be7bb 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -159,9 +159,14 @@ export interface ITopNodesPerChannels { } export interface ITopNodesPerCapacity { - public_key: string, + publicKey: string, alias: string, capacity: number, + channels?: number, + firstSeen?: number, + updatedAt?: number, + city?: any, + country?: any, } export interface INodesRanking { diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 294a2fb60..49cc7b61e 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -2,7 +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'; +import { INodesRanking, ITopNodesPerCapacity } from '../interfaces/node-api.interface'; @Injectable({ providedIn: 'root' @@ -63,4 +63,10 @@ export class LightningApiService { (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } + + getTopNodesByCapacity$(): Observable { + return this.httpClient.get( + this.apiBasePath + '/api/v1/lightning/nodes/rankings/capacity' + ); + } } diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html index e2e2731d3..3aaeed3dc 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html @@ -56,8 +56,8 @@
- -
Top Capacity Nodes
+
+
Top capacity nodes
 
@@ -70,7 +70,7 @@
- +
Most connected nodes
  diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index 85961edc2..48d54c49c 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -24,6 +24,7 @@ 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 { NodesRanking } from '../lightning/nodes-ranking/nodes-ranking.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({ @@ -47,6 +48,7 @@ import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-ca NodesPerCountryChartComponent, NodesMap, NodesChannelsMap, + NodesRanking, TopNodesPerChannels, TopNodesPerCapacity, ], @@ -77,6 +79,7 @@ import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-ca NodesPerCountryChartComponent, NodesMap, NodesChannelsMap, + NodesRanking, TopNodesPerChannels, TopNodesPerCapacity, ], diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts index cae5c1380..a3885ae7c 100644 --- a/frontend/src/app/lightning/lightning.routing.module.ts +++ b/frontend/src/app/lightning/lightning.routing.module.ts @@ -34,8 +34,11 @@ const routes: Routes = [ component: NodesPerISP, }, { - path: 'nodes/ranking', + path: 'nodes/top-capacity', component: NodesRanking, + data: { + type: 'capacity' + }, }, { path: '**', diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html index b0b01d16a..9ca4ed6dc 100644 --- a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html @@ -1,29 +1,2 @@ -
- - - - - - - - - -
RankAliasPlaceholder
- - - - - - - - - - - - - - - - - -
\ No newline at end of file + + \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts index b16dc3ce4..373751be9 100644 --- a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-nodes-ranking', @@ -7,9 +8,13 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class NodesRanking implements OnInit { + type: string; + + constructor(private route: ActivatedRoute) {} ngOnInit(): void { - console.log('hi'); + this.route.data.subscribe(data => { + this.type = data.type; + }); } - } diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html index bb1864ba1..4f84b8134 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -1,37 +1,59 @@ -
- - - - - - - - - - - - - - - - +
+

+ Top 100 nodes by capacity +

+ +
+
AliasCapacity
- {{ i + 1 }} - - {{ node.alias }} - - -
+ + + + + + + + + + + + + + + - -
AliasCapacityChannelsFirst seenLast updateLocation
- + {{ i + 1 }} - + {{ node.alias }} - + + + {{ node.channels | number }} + + + + + + {{ node?.city?.en ?? '-' }}
+ + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss index 4662b72ff..2b927db7f 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss @@ -1,8 +1,20 @@ +.container-xl { + max-width: 1400px; + padding-bottom: 100px; + @media (min-width: 767.98px) { + padding-left: 50px; + padding-right: 50px; + } +} + .table td, .table th { padding: 0.5rem; } -.rank { +.full .rank { + width: 5%; +} +.widget .rank { @media (min-width: 767.98px) { width: 13%; } @@ -12,7 +24,16 @@ } } -.alias { +.full .alias { + width: 10%; + overflow: hidden; + text-overflow: ellipsis; + max-width: 350px; + @media (max-width: 767.98px) { + max-width: 175px; + } +} +.widget .alias { width: 55%; overflow: hidden; text-overflow: ellipsis; @@ -21,10 +42,43 @@ max-width: 175px; } } -.capacity { + +.full .capacity { + width: 10%; +} +.widget .capacity { width: 32%; @media (max-width: 767.98px) { padding-left: 0px; padding-right: 0px; } } + +.full .channels { + width: 15%; + padding-right: 50px; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .timestamp-first { + width: 15%; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .timestamp-update { + width: 15%; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .location { + width: 10%; + @media (max-width: 767.98px) { + display: none; + } +} \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts index b3c9171c0..1f72b6974 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { INodesRanking, ITopNodesPerCapacity } from 'src/app/interfaces/node-api.interface'; +import { LightningApiService } from '../../lightning-api.service'; @Component({ selector: 'app-top-nodes-per-capacity', @@ -15,20 +16,24 @@ export class TopNodesPerCapacity implements OnInit { topNodesPerCapacity$: Observable; skeletonRows: number[] = []; + constructor(private apiService: LightningApiService) {} + 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) { + console.log(this.widget); + + if (this.widget === false) { + this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$(); + } else { + this.topNodesPerCapacity$ = this.nodes$.pipe( + map((ranking) => { return ranking.topByCapacity.slice(0, 10); - } else { - return ranking.topByCapacity; - } - }) - ) + }) + ); + } } } From 6421bc82f2eeb7c44c9c6beb702b46920ae18457 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 17 Aug 2022 16:19:01 +0200 Subject: [PATCH 3/3] Create top 100 node per channel count component --- backend/src/api/explorer/nodes.api.ts | 51 ++++++++--- backend/src/api/explorer/nodes.routes.ts | 15 +++- backend/src/mempool.interfaces.ts | 17 ++-- .../src/app/interfaces/node-api.interface.ts | 9 +- .../app/lightning/lightning-api.service.ts | 8 +- .../app/lightning/lightning.routing.module.ts | 7 ++ .../nodes-ranking.component.html | 5 +- .../top-nodes-per-capacity.component.html | 12 +++ .../top-nodes-per-capacity.component.ts | 2 - .../top-nodes-per-channels.component.html | 90 +++++++++++++------ .../top-nodes-per-channels.component.scss | 64 +++++++++++-- .../top-nodes-per-channels.component.ts | 19 ++-- 12 files changed, 232 insertions(+), 67 deletions(-) diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index c7b98f3a8..c0a891a0d 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -2,7 +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'; +import { ITopNodesPerCapacity, ITopNodesPerChannels } from '../../mempool.interfaces'; class NodesApi { public async $getNode(public_key: string): Promise { @@ -113,7 +113,7 @@ class NodesApi { } } - public async $getTopCapacityNodes(full: boolean): Promise { + public async $getTopCapacityNodes(full: boolean): Promise { try { let [rows]: any[] = await DB.query('SELECT UNIX_TIMESTAMP(MAX(added)) as maxAdded FROM node_stats'); const latestDate = rows[0].maxAdded; @@ -161,21 +161,46 @@ class NodesApi { } } - public async $getTopChannelsNodes(): Promise { + public async $getTopChannelsNodes(full: boolean): Promise { 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.channels - FROM node_stats - JOIN nodes ON nodes.public_key = node_stats.public_key - WHERE added = FROM_UNIXTIME(${latestDate}) - ORDER BY channels DESC - LIMIT 100; - `; - [rows] = await DB.query(query); + let query: string; + if (full === false) { + query = ` + 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 100; + `; + + [rows] = await DB.query(query); + } else { + query = ` + SELECT node_stats.public_key AS publicKey, IF(nodes.alias = '', SUBSTRING(node_stats.public_key, 1, 20), alias) as alias, + CAST(COALESCE(node_stats.channels, 0) as INT) as channels, + CAST(COALESCE(node_stats.capacity, 0) as INT) as capacity, + UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt, + geo_names_city.names as city, geo_names_country.names as country + FROM node_stats + RIGHT JOIN nodes ON nodes.public_key = node_stats.public_key + LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' + LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' + WHERE added = FROM_UNIXTIME(${latestDate}) + ORDER BY channels DESC + LIMIT 100 + `; + + [rows] = await DB.query(query); + for (let i = 0; i < rows.length; ++i) { + rows[i].country = JSON.parse(rows[i].country); + rows[i].city = JSON.parse(rows[i].city); + } + } return rows; } catch (e) { diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index a490d946b..aaafe679a 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -16,6 +16,7 @@ class NodesRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/countries', this.$getNodesCountries) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings', this.$getNodesRanking) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/capacity', this.$getTopNodesByCapacity) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/channels', this.$getTopNodesByChannels) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) ; @@ -61,7 +62,7 @@ class NodesRoutes { private async $getNodesRanking(req: Request, res: Response): Promise { try { const topCapacityNodes = await nodesApi.$getTopCapacityNodes(false); - const topChannelsNodes = await nodesApi.$getTopChannelsNodes(); + const topChannelsNodes = await nodesApi.$getTopChannelsNodes(false); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); @@ -86,6 +87,18 @@ class NodesRoutes { } } + private async $getTopNodesByChannels(req: Request, res: Response): Promise { + try { + const topCapacityNodes = await nodesApi.$getTopChannelsNodes(true); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(topCapacityNodes); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getISPRanking(req: Request, res: Response): Promise { try { const groupBy = req.query.groupBy as string; diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 1067f9b6d..d4f904772 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -252,13 +252,18 @@ export interface RewardStats { totalTx: number; } -export interface TopNodesPerChannels { - public_key: string, +export interface ITopNodesPerChannels { + publicKey: string, alias: string, - channels: number, + channels?: number, + capacity: number, + firstSeen?: number, + updatedAt?: number, + city?: any, + country?: any, } -export interface TopNodesPerCapacity { +export interface ITopNodesPerCapacity { publicKey: string, alias: string, capacity: number, @@ -270,6 +275,6 @@ export interface TopNodesPerCapacity { } export interface INodesRanking { - topByCapacity: TopNodesPerCapacity[]; - topByChannels: TopNodesPerChannels[]; + topByCapacity: ITopNodesPerCapacity[]; + topByChannels: ITopNodesPerChannels[]; } \ No newline at end of file diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 4238be7bb..2d1ff8f43 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -153,9 +153,14 @@ export interface RewardStats { } export interface ITopNodesPerChannels { - public_key: string, + publicKey: string, alias: string, - channels: number, + channels?: number, + capacity: number, + firstSeen?: number, + updatedAt?: number, + city?: any, + country?: any, } export interface ITopNodesPerCapacity { diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 49cc7b61e..1aacf7b89 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -2,7 +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, ITopNodesPerCapacity } from '../interfaces/node-api.interface'; +import { INodesRanking, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface'; @Injectable({ providedIn: 'root' @@ -69,4 +69,10 @@ export class LightningApiService { this.apiBasePath + '/api/v1/lightning/nodes/rankings/capacity' ); } + + getTopNodesByChannels$(): Observable { + return this.httpClient.get( + this.apiBasePath + '/api/v1/lightning/nodes/rankings/channels' + ); + } } diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts index a3885ae7c..9e9405234 100644 --- a/frontend/src/app/lightning/lightning.routing.module.ts +++ b/frontend/src/app/lightning/lightning.routing.module.ts @@ -40,6 +40,13 @@ const routes: Routes = [ type: 'capacity' }, }, + { + path: 'nodes/top-channels', + component: NodesRanking, + data: { + type: 'channels' + }, + }, { path: '**', redirectTo: '' diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html index 9ca4ed6dc..1720e9be6 100644 --- a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html @@ -1,2 +1,5 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html index 4f84b8134..a9043f9d1 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -51,6 +51,18 @@ + + + + + + + + + + + + diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts index 1f72b6974..2d90817f9 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -23,8 +23,6 @@ export class TopNodesPerCapacity implements OnInit { this.skeletonRows.push(i); } - console.log(this.widget); - if (this.widget === false) { this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$(); } else { diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html index f581dacff..72c2aa5b1 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html @@ -1,37 +1,71 @@ -
- - - - - - - - - - - - - - - - +
+

+ Top 100 nodes by channel count +

+ +
+
AliasChannels
- {{ i + 1 }} - - {{ node.alias }} - - {{ node.channels | number }} -
+ + + + + + + + + + + + + + + - -
AliasChannelsCapacityFirst seenLast updateLocation
- + {{ i + 1 }} - + {{ node.alias }} - + {{ node.channels | number }} + + + + + + + + {{ node?.city?.en ?? '-' }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss index 5335991e3..5af0e8339 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss @@ -1,8 +1,20 @@ +.container-xl { + max-width: 1400px; + padding-bottom: 100px; + @media (min-width: 767.98px) { + padding-left: 50px; + padding-right: 50px; + } +} + .table td, .table th { padding: 0.5rem; } -.rank { +.full .rank { + width: 5%; +} +.widget .rank { @media (min-width: 767.98px) { width: 13%; } @@ -12,8 +24,8 @@ } } -.alias { - width: 60%; +.full .alias { + width: 10%; overflow: hidden; text-overflow: ellipsis; max-width: 350px; @@ -21,10 +33,52 @@ max-width: 175px; } } -.channels { - width: 27%; +.widget .alias { + width: 55%; + overflow: hidden; + text-overflow: ellipsis; + max-width: 350px; + @media (max-width: 767.98px) { + max-width: 175px; + } +} + +.full .channels { + width: 10%; +} +.widget .channels { + width: 32%; @media (max-width: 767.98px) { padding-left: 0px; padding-right: 0px; } } + +.full .capacity { + width: 15%; + padding-right: 50px; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .timestamp-first { + width: 15%; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .timestamp-update { + width: 15%; + @media (max-width: 767.98px) { + display: none; + } +} + +.full .location { + width: 10%; + @media (max-width: 767.98px) { + display: none; + } +} \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts index 46cf21eaa..c2821c596 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { INodesRanking, ITopNodesPerChannels } from 'src/app/interfaces/node-api.interface'; +import { LightningApiService } from '../../lightning-api.service'; @Component({ selector: 'app-top-nodes-per-channels', @@ -15,20 +16,22 @@ export class TopNodesPerChannels implements OnInit { topNodesPerChannels$: Observable; skeletonRows: number[] = []; + constructor(private apiService: LightningApiService) {} + 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) { + if (this.widget === false) { + this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$(); + } else { + this.topNodesPerChannels$ = this.nodes$.pipe( + map((ranking) => { return ranking.topByChannels.slice(0, 10); - } else { - return ranking.topByChannels; - } - }) - ) + }) + ); + } } }