diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 9d82dc83d..cbd70a34f 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -434,12 +434,14 @@ class NodesApi { SELECT nodes.public_key, CAST(COALESCE(nodes.capacity, 0) as INT) as capacity, CAST(COALESCE(nodes.channels, 0) as INT) as channels, nodes.alias, UNIX_TIMESTAMP(nodes.first_seen) as first_seen, UNIX_TIMESTAMP(nodes.updated_at) as updated_at, geo_names_city.names as city, geo_names_country.names as country, - geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision + geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision, + nodes.longitude, nodes.latitude, nodes.as_number, geo_names_isp.names as isp FROM nodes 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' LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' LEFT JOIN geo_names geo_names_subdivision on geo_names_subdivision.id = nodes.subdivision_id AND geo_names_subdivision.type = 'division' + LEFT JOIN geo_names geo_names_isp on geo_names_isp.id = nodes.as_number AND geo_names_isp.type = 'as_organization' WHERE geo_names_country.id = ? ORDER BY capacity DESC `; @@ -449,6 +451,7 @@ class NodesApi { rows[i].country = JSON.parse(rows[i].country); rows[i].city = JSON.parse(rows[i].city); rows[i].subdivision = JSON.parse(rows[i].subdivision); + rows[i].isp = JSON.parse(rows[i].isp); } return rows; } catch (e) { @@ -463,7 +466,8 @@ class NodesApi { SELECT nodes.public_key, CAST(COALESCE(nodes.capacity, 0) as INT) as capacity, CAST(COALESCE(nodes.channels, 0) as INT) as channels, nodes.alias, UNIX_TIMESTAMP(nodes.first_seen) as first_seen, UNIX_TIMESTAMP(nodes.updated_at) as updated_at, geo_names_city.names as city, geo_names_country.names as country, - geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision + geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision, + nodes.longitude, nodes.latitude FROM nodes 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' diff --git a/frontend/src/app/lightning/channel/channel.component.html b/frontend/src/app/lightning/channel/channel.component.html index 9a0c424fb..3942cda6e 100644 --- a/frontend/src/app/lightning/channel/channel.component.html +++ b/frontend/src/app/lightning/channel/channel.component.html @@ -16,7 +16,8 @@
-Capacity | +
+ |
+
Nodes | +{{ countryNodes.nodes.length }} | +
Liquidity | +
+ |
+
Channels | +{{ countryNodes.sumChannels }} | +
ISP Count | +{{ countryNodes.ispCount }} | +
Top ISP | ++ + {{ countryNodes.topIsp.name }} [ASN {{ countryNodes.topIsp.id }}] + + | +
Channels | Location | - - -||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{{ node.alias }} | diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts index e0bf5eb66..19394a828 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { map, Observable } from 'rxjs'; +import { map, Observable, share } from 'rxjs'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; import { getFlagEmoji } from 'src/app/shared/common.utils'; @@ -32,6 +32,8 @@ export class NodesPerCountry implements OnInit { this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country) .pipe( map(response => { + this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`); + this.country = { name: response.country.en, flag: getFlagEmoji(this.route.snapshot.params.country) @@ -45,14 +47,50 @@ export class NodesPerCountry implements OnInit { iso: response.nodes[i].iso_code, }; } - - this.seoService.setTitle($localize`Lightning nodes in ${this.country.name}`); - return response.nodes; - }) + + const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0); + const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0); + const isps = {}; + const topIsp = { + count: 0, + id: '', + name: '', + }; + for (const node of response.nodes) { + if (!node.isp) { + continue; + } + if (!isps[node.isp]) { + isps[node.isp] = { + count: 0, + asns: [], + }; + } + if (isps[node.isp].asns.indexOf(node.as_number) === -1) { + isps[node.isp].asns.push(node.as_number); + } + isps[node.isp].count++; + + if (isps[node.isp].count > topIsp.count) { + topIsp.count = isps[node.isp].count; + topIsp.id = isps[node.isp].asns.join(','); + topIsp.name = node.isp; + } + } + + return { + nodes: response.nodes, + sumLiquidity: sumLiquidity, + sumChannels: sumChannels, + topIsp: topIsp, + ispCount: Object.keys(isps).length + }; + }), + share() ); } - trackByPublicKey(index: number, node: any) { + trackByPublicKey(index: number, node: any): string { return node.public_key; } } diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html index 4ea3c2d11..441dc429e 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html @@ -1,5 +1,54 @@
ASN | +{{ isp?.id }} | +
Nodes | +{{ ispNodes.nodes.length }} | +
Liquidity | +
+ |
+
Channels | +{{ ispNodes.sumChannels }} | +
Top country | ++ + {{ ispNodes.topCountry.country }} {{ ispNodes.topCountry.flag }} + + | +
Channels | Location | - - -
---|
{{ node.alias }} | diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss index 02b47e8be..b829c5b59 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss @@ -59,4 +59,4 @@ @media (max-width: 576px) { display: none } -} \ No newline at end of file +} diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts index f7edf783a..24664aab0 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts @@ -1,8 +1,9 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { map, Observable } from 'rxjs'; +import { map, Observable, share } from 'rxjs'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; +import { getFlagEmoji } from 'src/app/shared/common.utils'; import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; @Component({ @@ -33,7 +34,7 @@ export class NodesPerISP implements OnInit { map(response => { this.isp = { name: response.isp, - id: this.route.snapshot.params.isp + id: this.route.snapshot.params.isp.split(',').join(', ') }; this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); @@ -46,12 +47,40 @@ export class NodesPerISP implements OnInit { }; } - return response.nodes; - }) + const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0); + const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0); + const countries = {}; + const topCountry = { + count: 0, + country: '', + iso: '', + flag: '', + }; + for (const node of response.nodes) { + if (!node.geolocation.iso) { + continue; + } + countries[node.geolocation.iso] = countries[node.geolocation.iso] ?? 0 + 1; + if (countries[node.geolocation.iso] > topCountry.count) { + topCountry.count = countries[node.geolocation.iso]; + topCountry.country = node.geolocation.country; + topCountry.iso = node.geolocation.iso; + } + } + topCountry.flag = getFlagEmoji(topCountry.iso); + + return { + nodes: response.nodes, + sumLiquidity: sumLiquidity, + sumChannels: sumChannels, + topCountry: topCountry, + }; + }), + share() ); } - trackByPublicKey(index: number, node: any) { + trackByPublicKey(index: number, node: any): string { return node.public_key; } }