diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts
index ef111c6a9..5f75e31b8 100644
--- a/backend/src/api/explorer/nodes.api.ts
+++ b/backend/src/api/explorer/nodes.api.ts
@@ -96,7 +96,7 @@ class NodesApi {
public async $getNodesAsShare() {
try {
- let query = `SELECT names, COUNT(DISTINCT nodes.public_key) as nodesCount, SUM(capacity) as capacity
+ let query = `SELECT nodes.as_number as ispId, geo_names.names as names, COUNT(DISTINCT nodes.public_key) as nodesCount, SUM(capacity) 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
@@ -111,6 +111,7 @@ class NodesApi {
const nodesPerAs: any[] = [];
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,
@@ -154,6 +155,37 @@ class NodesApi {
throw e;
}
}
+
+ public async $getNodesPerISP(ISPId: string) {
+ try {
+ const query = `
+ SELECT node_stats.public_key, node_stats.capacity, node_stats.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
+ FROM node_stats
+ JOIN (
+ SELECT public_key, MAX(added) as last_added
+ FROM node_stats
+ GROUP BY public_key
+ ) as b ON b.public_key = node_stats.public_key AND b.last_added = node_stats.added
+ JOIN nodes ON nodes.public_key = node_stats.public_key
+ 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 nodes.as_number = ?
+ ORDER BY capacity DESC
+ `;
+
+ const [rows]: any = await DB.query(query, [ISPId]);
+ 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) {
+ logger.err(`Cannot get nodes for ISP id ${ISPId}. Reason: ${e instanceof Error ? e.message : e}`);
+ throw e;
+ }
+ }
}
export default new NodesApi();
diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts
index 44a4f42b9..4d82ed725 100644
--- a/backend/src/api/explorer/nodes.routes.ts
+++ b/backend/src/api/explorer/nodes.routes.ts
@@ -9,6 +9,7 @@ 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/asShare', this.$getNodesAsShare)
@@ -100,6 +101,33 @@ class NodesRoutes {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
+
+ private async $getNodesPerISP(req: Request, res: Response) {
+ try {
+ const [isp]: any[] = await DB.query(
+ `SELECT geo_names.names as isp_name
+ FROM geo_names
+ WHERE geo_names.type = 'as_organization' AND geo_names.id = ?`,
+ [req.params.isp]
+ );
+
+ if (isp.length === 0) {
+ res.status(404).send(`This ISP does not exist or does not host any lightning nodes on clearnet`);
+ return;
+ }
+
+ const nodes = await nodesApi.$getNodesPerISP(req.params.isp);
+ res.header('Pragma', 'public');
+ res.header('Cache-control', 'public');
+ res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
+ res.json({
+ isp: JSON.parse(isp[0].isp_name),
+ nodes: nodes,
+ });
+ } catch (e) {
+ res.status(500).send(e instanceof Error ? e.message : e);
+ }
+ }
}
export default new NodesRoutes();
diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts
index c4fa1bfb0..f417839d4 100644
--- a/frontend/src/app/lightning/lightning.module.ts
+++ b/frontend/src/app/lightning/lightning.module.ts
@@ -20,6 +20,7 @@ import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networ
import { ChannelsStatisticsComponent } from './channels-statistics/channels-statistics.component';
import { NodesPerAsChartComponent } from '../lightning/nodes-per-as-chart/nodes-per-as-chart.component';
import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component';
+import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component';
@NgModule({
declarations: [
LightningDashboardComponent,
@@ -37,6 +38,7 @@ import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component
ChannelsStatisticsComponent,
NodesPerAsChartComponent,
NodesPerCountry,
+ NodesPerISP,
],
imports: [
CommonModule,
diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts
index be6de3afd..8bfb467af 100644
--- a/frontend/src/app/lightning/lightning.routing.module.ts
+++ b/frontend/src/app/lightning/lightning.routing.module.ts
@@ -5,6 +5,7 @@ import { LightningWrapperComponent } from './lightning-wrapper/lightning-wrapper
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';
const routes: Routes = [
{
@@ -27,6 +28,10 @@ const routes: Routes = [
path: 'nodes/country/:country',
component: NodesPerCountry,
},
+ {
+ path: 'nodes/isp/:isp',
+ component: NodesPerISP,
+ },
{
path: '**',
redirectTo: ''
diff --git a/frontend/src/app/lightning/nodes-per-as-chart/nodes-per-as-chart.component.html b/frontend/src/app/lightning/nodes-per-as-chart/nodes-per-as-chart.component.html
index 3658eea18..24d634a3f 100644
--- a/frontend/src/app/lightning/nodes-per-as-chart/nodes-per-as-chart.component.html
+++ b/frontend/src/app/lightning/nodes-per-as-chart/nodes-per-as-chart.component.html
@@ -25,7 +25,7 @@
Rank
- Name
+ ISP
Share
Nodes
Capacity
@@ -34,7 +34,9 @@
Alias | +First seen | +Last update | +Capacity | +Channels | +City | + + +
---|---|---|---|---|---|
+ {{ node.alias }} + | +
+ |
+
+ |
+
+ |
+ + {{ node.channels }} + | ++ {{ node?.city?.en ?? '-' }} + | +