Nodes per country list component

This commit is contained in:
nymkappa 2022-07-16 15:56:36 +02:00
parent fc5fd244d0
commit 376484a937
No known key found for this signature in database
GPG Key ID: E155910B16E8BD04
9 changed files with 172 additions and 4 deletions

View File

@ -127,12 +127,27 @@ class NodesApi {
public async $getNodesPerCountry(country: string) { public async $getNodesPerCountry(country: string) {
try { try {
const query = `SELECT nodes.* FROM nodes const query = `
JOIN geo_names ON geo_names.id = nodes.country_id SELECT node_stats.public_key, node_stats.capacity, node_stats.channels, nodes.alias,
WHERE LOWER(json_extract(names, '$.en')) = ? UNIX_TIMESTAMP(nodes.first_seen) as first_seen, UNIX_TIMESTAMP(nodes.updated_at) as updated_at,
geo_names_city.names as city
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
LEFT 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
LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id
WHERE LOWER(JSON_EXTRACT(geo_names_country.names, '$.en')) = ?
ORDER BY capacity DESC
`; `;
const [rows]: any = await DB.query(query, [`"${country}"`]); const [rows]: any = await DB.query(query, [`"${country}"`]);
for (let i = 0; i < rows.length; ++i) {
rows[i].city = JSON.parse(rows[i].city);
}
return rows; return rows;
} catch (e) { } catch (e) {
logger.err(`Cannot get nodes for country ${country}. Reason: ${e instanceof Error ? e.message : e}`); logger.err(`Cannot get nodes for country ${country}. Reason: ${e instanceof Error ? e.message : e}`);

View File

@ -19,6 +19,7 @@ import { GraphsModule } from '../graphs/graphs.module';
import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networks-chart.component'; import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networks-chart.component';
import { ChannelsStatisticsComponent } from './channels-statistics/channels-statistics.component'; import { ChannelsStatisticsComponent } from './channels-statistics/channels-statistics.component';
import { NodesPerAsChartComponent } from '../lightning/nodes-per-as-chart/nodes-per-as-chart.component'; import { NodesPerAsChartComponent } from '../lightning/nodes-per-as-chart/nodes-per-as-chart.component';
import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
LightningDashboardComponent, LightningDashboardComponent,
@ -35,6 +36,7 @@ import { NodesPerAsChartComponent } from '../lightning/nodes-per-as-chart/nodes-
NodesNetworksChartComponent, NodesNetworksChartComponent,
ChannelsStatisticsComponent, ChannelsStatisticsComponent,
NodesPerAsChartComponent, NodesPerAsChartComponent,
NodesPerCountry,
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -4,6 +4,7 @@ import { LightningDashboardComponent } from './lightning-dashboard/lightning-das
import { LightningWrapperComponent } from './lightning-wrapper/lightning-wrapper.component'; import { LightningWrapperComponent } from './lightning-wrapper/lightning-wrapper.component';
import { NodeComponent } from './node/node.component'; import { NodeComponent } from './node/node.component';
import { ChannelComponent } from './channel/channel.component'; import { ChannelComponent } from './channel/channel.component';
import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -22,6 +23,10 @@ const routes: Routes = [
path: 'channel/:short_id', path: 'channel/:short_id',
component: ChannelComponent, component: ChannelComponent,
}, },
{
path: 'nodes/country/:country',
component: NodesPerCountry,
},
{ {
path: '**', path: '**',
redirectTo: '' redirectTo: ''

View File

@ -0,0 +1,42 @@
<div class="container-xl full-height" style="min-height: 335px">
<h1 class="float-left" i18n="lightning.nodes-in-country">Nodes in {{ country }}</h1>
<div style="min-height: 295px">
<table class="table table-borderless">
<thead>
<th class="alias text-left" i18n="lightning.alias">Alias</th>
<th class="timestamp-first text-left" i18n="lightning.first_seen">First seen</th>
<th class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
<th class="capacity text-right" i18n="lightning.capacity">Capacity</th>
<th class="channels text-right" i18n="lightning.channels">Channels</th>
<th class="city text-right" i18n="lightning.city">City</th>
</thead>
<tbody *ngIf="nodes$ | async as nodes">
<tr *ngFor="let node of nodes; let i= index; trackBy: trackByPublicKey">
<td class="alias text-left text-truncate">
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
</td>
<td class="timestamp-first text-left">
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.first_seen"></app-timestamp>
</td>
<td class="timestamp-update text-left">
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updated_at"></app-timestamp>
</td>
<td class="capacity text-right">
<app-amount *ngIf="node.capacity > 100000000; else smallchannel" [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
<ng-template #smallchannel>
{{ node.capacity | amountShortener: 1 }}
<span class="sats" i18n="shared.sats">sats</span>
</ng-template>
</td>
<td class="channels text-right">
{{ node.channels }}
</td>
<td class="city text-right text-truncate">
{{ node?.city?.en ?? '-' }}
</td>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,62 @@
.container-xl {
max-width: 1400px;
padding-bottom: 100px;
}
.sats {
color: #ffffff66;
font-size: 12px;
top: 0px;
}
.alias {
width: 30%;
max-width: 400px;
padding-right: 70px;
@media (max-width: 576px) {
width: 50%;
max-width: 150px;
padding-right: 0px;
}
}
.timestamp-first {
width: 20%;
@media (max-width: 576px) {
display: none
}
}
.timestamp-update {
width: 16%;
@media (max-width: 576px) {
display: none
}
}
.capacity {
width: 10%;
@media (max-width: 576px) {
width: 25%;
}
}
.channels {
width: 10%;
@media (max-width: 576px) {
width: 25%;
}
}
.city {
max-width: 150px;
@media (max-width: 576px) {
display: none
}
}

View File

@ -0,0 +1,37 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable } from 'rxjs';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-nodes-per-country',
templateUrl: './nodes-per-country.component.html',
styleUrls: ['./nodes-per-country.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesPerCountry implements OnInit {
nodes$: Observable<any>;
country: string;
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
) { }
ngOnInit(): void {
this.country = this.route.snapshot.params.country;
this.country = this.country.charAt(0).toUpperCase() + this.country.slice(1);
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
.pipe(
map(nodes => {
console.log(nodes);
return nodes;
})
);
}
trackByPublicKey(index: number, node: any) {
return node.public_key;
}
}

View File

@ -254,4 +254,8 @@ export class ApiService {
getNodesPerAs(): Observable<any> { getNodesPerAs(): Observable<any> {
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/asShare'); return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/asShare');
} }
getNodeForCountry$(country: string): Observable<any> {
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/country/' + country);
}
} }

View File

@ -1,4 +1,4 @@
&lrm;{{ seconds * 1000 | date:'yyyy-MM-dd HH:mm' }} &lrm;{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
<div class="lg-inline"> <div class="lg-inline">
<i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i> <i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i>
</div> </div>

View File

@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/c
export class TimestampComponent implements OnChanges { export class TimestampComponent implements OnChanges {
@Input() unixTime: number; @Input() unixTime: number;
@Input() dateString: string; @Input() dateString: string;
@Input() customFormat: string;
seconds: number; seconds: number;