From 12eea0e4cc699658f8fc7351421d71505ecf0e09 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 27 Jul 2022 13:20:54 +0200 Subject: [PATCH] [LN ISP chart] Adds toogle to order by nodes/capacity and show/hide Tor --- backend/src/api/explorer/nodes.api.ts | 48 ++++++++++++--- backend/src/api/explorer/nodes.routes.ts | 17 ++++-- .../app/lightning/node/node.component.html | 4 +- .../nodes-per-isp-chart.component.html | 10 ++- .../nodes-per-isp-chart.component.scss | 14 ++++- .../nodes-per-isp-chart.component.ts | 61 ++++++++++++------- frontend/src/app/services/api.service.ts | 5 +- .../components/toggle/toggle.component.html | 2 +- .../components/toggle/toggle.component.ts | 8 ++- 9 files changed, 123 insertions(+), 46 deletions(-) diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 9899e20fc..517ab82f6 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -98,29 +98,59 @@ class NodesApi { } } - public async $getNodesISP() { + public async $getNodesISP(groupBy: string, showTor: boolean) { try { - let query = `SELECT GROUP_CONCAT(DISTINCT(nodes.as_number)) as ispId, geo_names.names as names, COUNT(DISTINCT nodes.public_key) as nodesCount, SUM(capacity) as capacity + const orderBy = groupBy === 'capacity' ? `CAST(SUM(capacity) as INT)` : `COUNT(DISTINCT nodes.public_key)`; + + // Clearnet + let query = `SELECT GROUP_CONCAT(DISTINCT(nodes.as_number)) as ispId, geo_names.names as names, + COUNT(DISTINCT nodes.public_key) as nodesCount, CAST(SUM(capacity) as INT) as capacity FROM nodes JOIN geo_names ON geo_names.id = nodes.as_number JOIN channels ON channels.node1_public_key = nodes.public_key OR channels.node2_public_key = nodes.public_key GROUP BY geo_names.names - ORDER BY COUNT(DISTINCT nodes.public_key) DESC - `; + ORDER BY ${orderBy} DESC + `; const [nodesCountPerAS]: any = await DB.query(query); - query = `SELECT COUNT(*) as total FROM nodes WHERE as_number IS NOT NULL`; - const [nodesWithAS]: any = await DB.query(query); - + let total = 0; const nodesPerAs: any[] = []; + + for (const asGroup of nodesCountPerAS) { + if (groupBy === 'capacity') { + total += asGroup.capacity; + } else { + total += asGroup.nodesCount; + } + } + + // Tor + if (showTor) { + query = `SELECT COUNT(DISTINCT nodes.public_key) as nodesCount, CAST(SUM(capacity) as INT) as capacity + FROM nodes + JOIN channels ON channels.node1_public_key = nodes.public_key OR channels.node2_public_key = nodes.public_key + ORDER BY ${orderBy} DESC + `; + const [nodesCountTor]: any = await DB.query(query); + + total += groupBy === 'capacity' ? nodesCountTor[0].capacity : nodesCountTor[0].nodesCount; + nodesPerAs.push({ + ispId: null, + name: 'Tor', + count: nodesCountTor[0].nodesCount, + share: Math.floor((groupBy === 'capacity' ? nodesCountTor[0].capacity : nodesCountTor[0].nodesCount) / total * 10000) / 100, + capacity: nodesCountTor[0].capacity, + }); + } + for (const as of nodesCountPerAS) { nodesPerAs.push({ ispId: as.ispId, name: JSON.parse(as.names), count: as.nodesCount, - share: Math.floor(as.nodesCount / nodesWithAS[0].total * 10000) / 100, + share: Math.floor((groupBy === 'capacity' ? as.capacity : as.nodesCount) / total * 10000) / 100, capacity: as.capacity, - }) + }); } return nodesPerAs; diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index bbc8efb5a..83e3c393e 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -9,10 +9,10 @@ class NodesRoutes { public initRoutes(app: Application) { app .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/country/:country', this.$getNodesPerCountry) - .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp/:isp', this.$getNodesPerISP) .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/isp', this.$getNodesISP) + .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/:public_key/statistics', this.$getHistoricalNodeStats) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) @@ -63,9 +63,18 @@ class NodesRoutes { } } - private async $getNodesISP(req: Request, res: Response) { + private async $getISPRanking(req: Request, res: Response): Promise { try { - const nodesPerAs = await nodesApi.$getNodesISP(); + const groupBy = req.query.groupBy as string; + const showTor = req.query.showTor as string === 'true' ? true : false; + + if (!['capacity', 'node-count'].includes(groupBy)) { + res.status(400).send(`groupBy must be one of 'capacity' or 'node-count'`); + return; + } + + const nodesPerAs = await nodesApi.$getNodesISP(groupBy, showTor); + res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString()); diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html index af6fca655..cb0e5ed43 100644 --- a/frontend/src/app/lightning/node/node.component.html +++ b/frontend/src/app/lightning/node/node.component.html @@ -140,7 +140,9 @@

Channels ({{ channelsListStatus === 'open' ? node.channel_active_count : node.channel_closed_count }})

- +
+ +
diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html index 28d314b9c..a80b8665e 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html @@ -21,6 +21,11 @@
+
+ + +
+ @@ -34,8 +39,9 @@ - diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.scss b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.scss index 8e9a9903b..10ad39372 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.scss +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.scss @@ -45,7 +45,7 @@ .name { width: 25%; @media (max-width: 576px) { - width: 80%; + width: 70%; max-width: 150px; padding-left: 0; padding-right: 0; @@ -69,7 +69,17 @@ .capacity { width: 20%; @media (max-width: 576px) { - width: 10%; + width: 20%; max-width: 100px; } +} + +.toggle { + justify-content: space-between; + padding-top: 15px; + @media (min-width: 576px) { + padding-bottom: 15px; + padding-left: 105px; + padding-right: 105px; + } } \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts index 63665f69a..f6d876e6b 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core'; import { Router } from '@angular/router'; import { EChartsOption, PieSeriesOption } from 'echarts'; -import { map, Observable, share, tap } from 'rxjs'; +import { combineLatest, map, Observable, share, Subject, switchMap, tap } from 'rxjs'; import { chartColors } from 'src/app/app.constants'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; @@ -17,19 +17,19 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url. changeDetection: ChangeDetectionStrategy.OnPush, }) export class NodesPerISPChartComponent implements OnInit { - miningWindowPreference: string; - isLoading = true; chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', }; timespan = ''; - chartInstance: any = undefined; + chartInstance = undefined; @HostBinding('attr.dir') dir = 'ltr'; nodesPerAsObservable$: Observable; + groupBySubject = new Subject(); + showTorSubject = new Subject(); constructor( private apiService: ApiService, @@ -44,23 +44,31 @@ export class NodesPerISPChartComponent implements OnInit { ngOnInit(): void { this.seoService.setTitle($localize`Lightning nodes per ISP`); - this.nodesPerAsObservable$ = this.apiService.getNodesPerAs() + this.nodesPerAsObservable$ = combineLatest([this.groupBySubject, this.showTorSubject]) .pipe( - tap(data => { - this.isLoading = false; - this.prepareChartOptions(data); - }), - map(data => { - for (let i = 0; i < data.length; ++i) { - data[i].rank = i + 1; - } - return data.slice(0, 100); + switchMap((selectedFilters) => { + return this.apiService.getNodesPerAs( + selectedFilters[0] ? 'capacity' : 'node-count', + selectedFilters[1] // Show Tor nodes + ) + .pipe( + tap(data => { + this.isLoading = false; + this.prepareChartOptions(data); + }), + map(data => { + for (let i = 0; i < data.length; ++i) { + data[i].rank = i + 1; + } + return data.slice(0, 100); + }) + ); }), share() ); } - generateChartSerieData(as) { + generateChartSerieData(as): PieSeriesOption[] { const shareThreshold = this.isMobile() ? 2 : 0.5; const data: object[] = []; let totalShareOther = 0; @@ -78,6 +86,9 @@ export class NodesPerISPChartComponent implements OnInit { return; } data.push({ + itemStyle: { + color: as.ispId === null ? '#7D4698' : undefined, + }, value: as.share, name: as.name + (this.isMobile() ? `` : ` (${as.share}%)`), label: { @@ -138,14 +149,14 @@ export class NodesPerISPChartComponent implements OnInit { return data; } - prepareChartOptions(as) { + prepareChartOptions(as): void { let pieSize = ['20%', '80%']; // Desktop if (this.isMobile()) { pieSize = ['15%', '60%']; } this.chartOptions = { - color: chartColors, + color: chartColors.slice(3), tooltip: { trigger: 'item', textStyle: { @@ -191,18 +202,18 @@ export class NodesPerISPChartComponent implements OnInit { }; } - isMobile() { + isMobile(): boolean { return (window.innerWidth <= 767.98); } - onChartInit(ec) { + onChartInit(ec): void { if (this.chartInstance !== undefined) { return; } this.chartInstance = ec; this.chartInstance.on('click', (e) => { - if (e.data.data === 9999) { // "Other" + if (e.data.data === 9999 || e.data.data === null) { // "Other" or Tor return; } this.zone.run(() => { @@ -212,7 +223,7 @@ export class NodesPerISPChartComponent implements OnInit { }); } - onSaveChart() { + onSaveChart(): void { const now = new Date(); this.chartOptions.backgroundColor = '#11131f'; this.chartInstance.setOption(this.chartOptions); @@ -224,8 +235,12 @@ export class NodesPerISPChartComponent implements OnInit { this.chartInstance.setOption(this.chartOptions); } - isEllipsisActive(e) { - return (e.offsetWidth < e.scrollWidth); + onTorToggleStatusChanged(e): void { + this.showTorSubject.next(e); + } + + onGroupToggleStatusChanged(e): void { + this.groupBySubject.next(e); } } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index fdb2714bd..844451574 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -255,8 +255,9 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/search', { params }); } - getNodesPerAs(): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp'); + getNodesPerAs(groupBy: 'capacity' | 'node-count', showTorNodes: boolean): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp-ranking' + + `?groupBy=${groupBy}&showTor=${showTorNodes}`); } getNodeForCountry$(country: string): Observable { diff --git a/frontend/src/app/shared/components/toggle/toggle.component.html b/frontend/src/app/shared/components/toggle/toggle.component.html index 6f8889501..dac33c9d8 100644 --- a/frontend/src/app/shared/components/toggle/toggle.component.html +++ b/frontend/src/app/shared/components/toggle/toggle.component.html @@ -1,4 +1,4 @@ -
+
{{ textLeft }} 
{{ asEntry.rank }} - {{ asEntry.name }} + + {{ asEntry.name }} + {{ asEntry.name }} {{ asEntry.count }}